blob: 610dd5a716901424fdbe4cd879dd43790b00a0d8 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Dave Borowitz137d0132015-01-02 11:12:54 -080016import contextlib
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080017import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import filecmp
19import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Dave Borowitz137d0132015-01-02 11:12:54 -080029import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070032from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090034from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080035from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080036from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070037from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070038
Shawn O. Pearced237b692009-04-17 18:49:50 -070039from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
David Pursehouse59bbb582013-05-17 10:49:33 +090041from pyversion import is_python3
42if not is_python3():
43 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053044 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090045 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053046
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070047def _lwrite(path, content):
48 lock = '%s.lock' % path
49
Chirayu Desai303a82f2014-08-19 22:57:17 +053050 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070051 try:
52 fd.write(content)
53 finally:
54 fd.close()
55
56 try:
57 os.rename(lock, path)
58 except OSError:
59 os.remove(lock)
60 raise
61
Shawn O. Pearce48244782009-04-16 08:25:57 -070062def _error(fmt, *args):
63 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070064 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070065
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070066def not_rev(r):
67 return '^' + r
68
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080069def sq(r):
70 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080071
Jonathan Nieder93719792015-03-17 11:29:58 -070072_project_hook_list = None
73def _ProjectHooks():
74 """List the hooks present in the 'hooks' directory.
75
76 These hooks are project hooks and are copied to the '.git/hooks' directory
77 of all subprojects.
78
79 This function caches the list of hooks (based on the contents of the
80 'repo/hooks' directory) on the first call.
81
82 Returns:
83 A list of absolute paths to all of the files in the hooks directory.
84 """
85 global _project_hook_list
86 if _project_hook_list is None:
87 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
88 d = os.path.join(d, 'hooks')
89 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
90 return _project_hook_list
91
92
Shawn O. Pearce632768b2008-10-23 11:58:52 -070093class DownloadedChange(object):
94 _commit_cache = None
95
96 def __init__(self, project, base, change_id, ps_id, commit):
97 self.project = project
98 self.base = base
99 self.change_id = change_id
100 self.ps_id = ps_id
101 self.commit = commit
102
103 @property
104 def commits(self):
105 if self._commit_cache is None:
106 self._commit_cache = self.project.bare_git.rev_list(
107 '--abbrev=8',
108 '--abbrev-commit',
109 '--pretty=oneline',
110 '--reverse',
111 '--date-order',
112 not_rev(self.base),
113 self.commit,
114 '--')
115 return self._commit_cache
116
117
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700118class ReviewableBranch(object):
119 _commit_cache = None
120
121 def __init__(self, project, branch, base):
122 self.project = project
123 self.branch = branch
124 self.base = base
125
126 @property
127 def name(self):
128 return self.branch.name
129
130 @property
131 def commits(self):
132 if self._commit_cache is None:
133 self._commit_cache = self.project.bare_git.rev_list(
134 '--abbrev=8',
135 '--abbrev-commit',
136 '--pretty=oneline',
137 '--reverse',
138 '--date-order',
139 not_rev(self.base),
140 R_HEADS + self.name,
141 '--')
142 return self._commit_cache
143
144 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800145 def unabbrev_commits(self):
146 r = dict()
147 for commit in self.project.bare_git.rev_list(
148 not_rev(self.base),
149 R_HEADS + self.name,
150 '--'):
151 r[commit[0:8]] = commit
152 return r
153
154 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700155 def date(self):
156 return self.project.bare_git.log(
157 '--pretty=format:%cd',
158 '-n', '1',
159 R_HEADS + self.name,
160 '--')
161
Bryan Jacobsf609f912013-05-06 13:36:24 -0400162 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800163 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700164 people,
Brian Harring435370c2012-07-28 15:37:04 -0700165 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400166 draft=draft,
167 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700168
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700169 def GetPublishedRefs(self):
170 refs = {}
171 output = self.project.bare_git.ls_remote(
172 self.branch.remote.SshReviewUrl(self.project.UserEmail),
173 'refs/changes/*')
174 for line in output.split('\n'):
175 try:
176 (sha, ref) = line.split()
177 refs[sha] = ref
178 except ValueError:
179 pass
180
181 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182
183class StatusColoring(Coloring):
184 def __init__(self, config):
185 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100186 self.project = self.printer('header', attr='bold')
187 self.branch = self.printer('header', attr='bold')
188 self.nobranch = self.printer('nobranch', fg='red')
189 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700190
Anthony King7bdac712014-07-16 12:56:40 +0100191 self.added = self.printer('added', fg='green')
192 self.changed = self.printer('changed', fg='red')
193 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700194
195
196class DiffColoring(Coloring):
197 def __init__(self, config):
198 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100199 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700200
Anthony King7bdac712014-07-16 12:56:40 +0100201class _Annotation(object):
James W. Mills24c13082012-04-12 15:04:13 -0500202 def __init__(self, name, value, keep):
203 self.name = name
204 self.value = value
205 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206
Anthony King7bdac712014-07-16 12:56:40 +0100207class _CopyFile(object):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800208 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700209 self.src = src
210 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800211 self.abs_src = abssrc
212 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213
214 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800215 src = self.abs_src
216 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 # copy file if it does not exist or is out of date
218 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
219 try:
220 # remove existing file first, since it might be read-only
221 if os.path.exists(dest):
222 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400223 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200224 dest_dir = os.path.dirname(dest)
225 if not os.path.isdir(dest_dir):
226 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227 shutil.copy(src, dest)
228 # make the file read-only
229 mode = os.stat(dest)[stat.ST_MODE]
230 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
231 os.chmod(dest, mode)
232 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700233 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234
Anthony King7bdac712014-07-16 12:56:40 +0100235class _LinkFile(object):
Colin Cross0184dcc2015-05-05 00:24:54 -0700236 def __init__(self, src, dest, relsrc, absdest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500237 self.src = src
238 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700239 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500240 self.abs_dest = absdest
241
242 def _Link(self):
Colin Cross0184dcc2015-05-05 00:24:54 -0700243 src = self.src_rel_to_dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500244 dest = self.abs_dest
245 # link file if it does not exist or is out of date
246 if not os.path.islink(dest) or os.readlink(dest) != src:
247 try:
248 # remove existing file first, since it might be read-only
249 if os.path.exists(dest):
250 os.remove(dest)
251 else:
252 dest_dir = os.path.dirname(dest)
253 if not os.path.isdir(dest_dir):
254 os.makedirs(dest_dir)
255 os.symlink(src, dest)
256 except IOError:
257 _error('Cannot link file %s to %s', src, dest)
258
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700259class RemoteSpec(object):
260 def __init__(self,
261 name,
Anthony King7bdac712014-07-16 12:56:40 +0100262 url=None,
263 review=None,
264 revision=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700265 self.name = name
266 self.url = url
267 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100268 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700269
Doug Anderson37282b42011-03-04 11:54:18 -0800270class RepoHook(object):
271 """A RepoHook contains information about a script to run as a hook.
272
273 Hooks are used to run a python script before running an upload (for instance,
274 to run presubmit checks). Eventually, we may have hooks for other actions.
275
276 This shouldn't be confused with files in the 'repo/hooks' directory. Those
277 files are copied into each '.git/hooks' folder for each project. Repo-level
278 hooks are associated instead with repo actions.
279
280 Hooks are always python. When a hook is run, we will load the hook into the
281 interpreter and execute its main() function.
282 """
283 def __init__(self,
284 hook_type,
285 hooks_project,
286 topdir,
287 abort_if_user_denies=False):
288 """RepoHook constructor.
289
290 Params:
291 hook_type: A string representing the type of hook. This is also used
292 to figure out the name of the file containing the hook. For
293 example: 'pre-upload'.
294 hooks_project: The project containing the repo hooks. If you have a
295 manifest, this is manifest.repo_hooks_project. OK if this is None,
296 which will make the hook a no-op.
297 topdir: Repo's top directory (the one containing the .repo directory).
298 Scripts will run with CWD as this directory. If you have a manifest,
299 this is manifest.topdir
300 abort_if_user_denies: If True, we'll throw a HookError() if the user
301 doesn't allow us to run the hook.
302 """
303 self._hook_type = hook_type
304 self._hooks_project = hooks_project
305 self._topdir = topdir
306 self._abort_if_user_denies = abort_if_user_denies
307
308 # Store the full path to the script for convenience.
309 if self._hooks_project:
310 self._script_fullpath = os.path.join(self._hooks_project.worktree,
311 self._hook_type + '.py')
312 else:
313 self._script_fullpath = None
314
315 def _GetHash(self):
316 """Return a hash of the contents of the hooks directory.
317
318 We'll just use git to do this. This hash has the property that if anything
319 changes in the directory we will return a different has.
320
321 SECURITY CONSIDERATION:
322 This hash only represents the contents of files in the hook directory, not
323 any other files imported or called by hooks. Changes to imported files
324 can change the script behavior without affecting the hash.
325
326 Returns:
327 A string representing the hash. This will always be ASCII so that it can
328 be printed to the user easily.
329 """
330 assert self._hooks_project, "Must have hooks to calculate their hash."
331
332 # We will use the work_git object rather than just calling GetRevisionId().
333 # That gives us a hash of the latest checked in version of the files that
334 # the user will actually be executing. Specifically, GetRevisionId()
335 # doesn't appear to change even if a user checks out a different version
336 # of the hooks repo (via git checkout) nor if a user commits their own revs.
337 #
338 # NOTE: Local (non-committed) changes will not be factored into this hash.
339 # I think this is OK, since we're really only worried about warning the user
340 # about upstream changes.
341 return self._hooks_project.work_git.rev_parse('HEAD')
342
343 def _GetMustVerb(self):
344 """Return 'must' if the hook is required; 'should' if not."""
345 if self._abort_if_user_denies:
346 return 'must'
347 else:
348 return 'should'
349
350 def _CheckForHookApproval(self):
351 """Check to see whether this hook has been approved.
352
353 We'll look at the hash of all of the hooks. If this matches the hash that
354 the user last approved, we're done. If it doesn't, we'll ask the user
355 about approval.
356
357 Note that we ask permission for each individual hook even though we use
358 the hash of all hooks when detecting changes. We'd like the user to be
359 able to approve / deny each hook individually. We only use the hash of all
360 hooks because there is no other easy way to detect changes to local imports.
361
362 Returns:
363 True if this hook is approved to run; False otherwise.
364
365 Raises:
366 HookError: Raised if the user doesn't approve and abort_if_user_denies
367 was passed to the consturctor.
368 """
Doug Anderson37282b42011-03-04 11:54:18 -0800369 hooks_config = self._hooks_project.config
370 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
371
372 # Get the last hash that the user approved for this hook; may be None.
373 old_hash = hooks_config.GetString(git_approval_key)
374
375 # Get the current hash so we can tell if scripts changed since approval.
376 new_hash = self._GetHash()
377
378 if old_hash is not None:
379 # User previously approved hook and asked not to be prompted again.
380 if new_hash == old_hash:
381 # Approval matched. We're done.
382 return True
383 else:
384 # Give the user a reason why we're prompting, since they last told
385 # us to "never ask again".
386 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
387 self._hook_type)
388 else:
389 prompt = ''
390
391 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
392 if sys.stdout.isatty():
393 prompt += ('Repo %s run the script:\n'
394 ' %s\n'
395 '\n'
396 'Do you want to allow this script to run '
397 '(yes/yes-never-ask-again/NO)? ') % (
398 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530399 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900400 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800401
402 # User is doing a one-time approval.
403 if response in ('y', 'yes'):
404 return True
405 elif response == 'yes-never-ask-again':
406 hooks_config.SetString(git_approval_key, new_hash)
407 return True
408
409 # For anything else, we'll assume no approval.
410 if self._abort_if_user_denies:
411 raise HookError('You must allow the %s hook or use --no-verify.' %
412 self._hook_type)
413
414 return False
415
416 def _ExecuteHook(self, **kwargs):
417 """Actually execute the given hook.
418
419 This will run the hook's 'main' function in our python interpreter.
420
421 Args:
422 kwargs: Keyword arguments to pass to the hook. These are often specific
423 to the hook type. For instance, pre-upload hooks will contain
424 a project_list.
425 """
426 # Keep sys.path and CWD stashed away so that we can always restore them
427 # upon function exit.
428 orig_path = os.getcwd()
429 orig_syspath = sys.path
430
431 try:
432 # Always run hooks with CWD as topdir.
433 os.chdir(self._topdir)
434
435 # Put the hook dir as the first item of sys.path so hooks can do
436 # relative imports. We want to replace the repo dir as [0] so
437 # hooks can't import repo files.
438 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
439
440 # Exec, storing global context in the context dict. We catch exceptions
441 # and convert to a HookError w/ just the failing traceback.
442 context = {}
443 try:
Anthony King70f68902014-05-05 21:15:34 +0100444 exec(compile(open(self._script_fullpath).read(),
445 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800446 except Exception:
447 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
448 traceback.format_exc(), self._hook_type))
449
450 # Running the script should have defined a main() function.
451 if 'main' not in context:
452 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
453
454
455 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
456 # We don't actually want hooks to define their main with this argument--
457 # it's there to remind them that their hook should always take **kwargs.
458 # For instance, a pre-upload hook should be defined like:
459 # def main(project_list, **kwargs):
460 #
461 # This allows us to later expand the API without breaking old hooks.
462 kwargs = kwargs.copy()
463 kwargs['hook_should_take_kwargs'] = True
464
465 # Call the main function in the hook. If the hook should cause the
466 # build to fail, it will raise an Exception. We'll catch that convert
467 # to a HookError w/ just the failing traceback.
468 try:
469 context['main'](**kwargs)
470 except Exception:
471 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
472 'above.' % (
473 traceback.format_exc(), self._hook_type))
474 finally:
475 # Restore sys.path and CWD.
476 sys.path = orig_syspath
477 os.chdir(orig_path)
478
479 def Run(self, user_allows_all_hooks, **kwargs):
480 """Run the hook.
481
482 If the hook doesn't exist (because there is no hooks project or because
483 this particular hook is not enabled), this is a no-op.
484
485 Args:
486 user_allows_all_hooks: If True, we will never prompt about running the
487 hook--we'll just assume it's OK to run it.
488 kwargs: Keyword arguments to pass to the hook. These are often specific
489 to the hook type. For instance, pre-upload hooks will contain
490 a project_list.
491
492 Raises:
493 HookError: If there was a problem finding the hook or the user declined
494 to run a required hook (from _CheckForHookApproval).
495 """
496 # No-op if there is no hooks project or if hook is disabled.
497 if ((not self._hooks_project) or
498 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
499 return
500
501 # Bail with a nice error if we can't find the hook.
502 if not os.path.isfile(self._script_fullpath):
503 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
504
505 # Make sure the user is OK with running the hook.
506 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
507 return
508
509 # Run the hook with the same version of python we're using.
510 self._ExecuteHook(**kwargs)
511
512
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700513class Project(object):
514 def __init__(self,
515 manifest,
516 name,
517 remote,
518 gitdir,
David James8d201162013-10-11 17:03:19 -0700519 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700520 worktree,
521 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700522 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800523 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100524 rebase=True,
525 groups=None,
526 sync_c=False,
527 sync_s=False,
528 clone_depth=None,
529 upstream=None,
530 parent=None,
531 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900532 dest_branch=None,
533 optimized_fetch=False):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800534 """Init a Project object.
535
536 Args:
537 manifest: The XmlManifest object.
538 name: The `name` attribute of manifest.xml's project element.
539 remote: RemoteSpec object specifying its remote's properties.
540 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700541 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800542 worktree: Absolute path of git working tree.
543 relpath: Relative path of git working tree to repo's top directory.
544 revisionExpr: The `revision` attribute of manifest.xml's project element.
545 revisionId: git commit id for checking out.
546 rebase: The `rebase` attribute of manifest.xml's project element.
547 groups: The `groups` attribute of manifest.xml's project element.
548 sync_c: The `sync-c` attribute of manifest.xml's project element.
549 sync_s: The `sync-s` attribute of manifest.xml's project element.
550 upstream: The `upstream` attribute of manifest.xml's project element.
551 parent: The parent Project object.
552 is_derived: False if the project was explicitly defined in the manifest;
553 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400554 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900555 optimized_fetch: If True, when a project is set to a sha1 revision, only
556 fetch from the remote if the sha1 is not present locally.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800557 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700558 self.manifest = manifest
559 self.name = name
560 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800561 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700562 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800563 if worktree:
564 self.worktree = worktree.replace('\\', '/')
565 else:
566 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700568 self.revisionExpr = revisionExpr
569
570 if revisionId is None \
571 and revisionExpr \
572 and IsId(revisionExpr):
573 self.revisionId = revisionExpr
574 else:
575 self.revisionId = revisionId
576
Mike Pontillod3153822012-02-28 11:53:24 -0800577 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700578 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700579 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800580 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900581 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700582 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800583 self.parent = parent
584 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900585 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800586 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800587
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700588 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700589 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500590 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500591 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700592 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100593 gitdir=self.gitdir,
594 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700595
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800596 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700597 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800598 else:
599 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700600 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700601 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700602 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400603 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700604
Doug Anderson37282b42011-03-04 11:54:18 -0800605 # This will be filled in if a project is later identified to be the
606 # project containing repo hooks.
607 self.enabled_repo_hooks = []
608
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700609 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800610 def Derived(self):
611 return self.is_derived
612
613 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700614 def Exists(self):
615 return os.path.isdir(self.gitdir)
616
617 @property
618 def CurrentBranch(self):
619 """Obtain the name of the currently checked out branch.
620 The branch name omits the 'refs/heads/' prefix.
621 None is returned if the project is on a detached HEAD.
622 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700623 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700624 if b.startswith(R_HEADS):
625 return b[len(R_HEADS):]
626 return None
627
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700628 def IsRebaseInProgress(self):
629 w = self.worktree
630 g = os.path.join(w, '.git')
631 return os.path.exists(os.path.join(g, 'rebase-apply')) \
632 or os.path.exists(os.path.join(g, 'rebase-merge')) \
633 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200634
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635 def IsDirty(self, consider_untracked=True):
636 """Is the working directory modified in some way?
637 """
638 self.work_git.update_index('-q',
639 '--unmerged',
640 '--ignore-missing',
641 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900642 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700643 return True
644 if self.work_git.DiffZ('diff-files'):
645 return True
646 if consider_untracked and self.work_git.LsOthers():
647 return True
648 return False
649
650 _userident_name = None
651 _userident_email = None
652
653 @property
654 def UserName(self):
655 """Obtain the user's personal name.
656 """
657 if self._userident_name is None:
658 self._LoadUserIdentity()
659 return self._userident_name
660
661 @property
662 def UserEmail(self):
663 """Obtain the user's email address. This is very likely
664 to be their Gerrit login.
665 """
666 if self._userident_email is None:
667 self._LoadUserIdentity()
668 return self._userident_email
669
670 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900671 u = self.bare_git.var('GIT_COMMITTER_IDENT')
672 m = re.compile("^(.*) <([^>]*)> ").match(u)
673 if m:
674 self._userident_name = m.group(1)
675 self._userident_email = m.group(2)
676 else:
677 self._userident_name = ''
678 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679
680 def GetRemote(self, name):
681 """Get the configuration for a single remote.
682 """
683 return self.config.GetRemote(name)
684
685 def GetBranch(self, name):
686 """Get the configuration for a single branch.
687 """
688 return self.config.GetBranch(name)
689
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700690 def GetBranches(self):
691 """Get all existing local branches.
692 """
693 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900694 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700695 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700696
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530697 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700698 if name.startswith(R_HEADS):
699 name = name[len(R_HEADS):]
700 b = self.GetBranch(name)
701 b.current = name == current
702 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900703 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700704 heads[name] = b
705
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530706 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700707 if name.startswith(R_PUB):
708 name = name[len(R_PUB):]
709 b = heads.get(name)
710 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900711 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700712
713 return heads
714
Colin Cross5acde752012-03-28 20:15:45 -0700715 def MatchesGroups(self, manifest_groups):
716 """Returns true if the manifest groups specified at init should cause
717 this project to be synced.
718 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700719 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700720
721 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700722 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700723 manifest_groups: "-group1,group2"
724 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500725
726 The special manifest group "default" will match any project that
727 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700728 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500729 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700730 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500731 if not 'notdefault' in expanded_project_groups:
732 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700733
Conley Owens971de8e2012-04-16 10:36:08 -0700734 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700735 for group in expanded_manifest_groups:
736 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700737 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700738 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700739 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700740
Conley Owens971de8e2012-04-16 10:36:08 -0700741 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742
743## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700744 def UncommitedFiles(self, get_all=True):
745 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700746
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700747 Args:
748 get_all: a boolean, if True - get information about all different
749 uncommitted files. If False - return as soon as any kind of
750 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500751 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700752 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500753 self.work_git.update_index('-q',
754 '--unmerged',
755 '--ignore-missing',
756 '--refresh')
757 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700758 details.append("rebase in progress")
759 if not get_all:
760 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500761
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700762 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
763 if changes:
764 details.extend(changes)
765 if not get_all:
766 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500767
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700768 changes = self.work_git.DiffZ('diff-files').keys()
769 if changes:
770 details.extend(changes)
771 if not get_all:
772 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500773
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700774 changes = self.work_git.LsOthers()
775 if changes:
776 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500777
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700778 return details
779
780 def HasChanges(self):
781 """Returns true if there are uncommitted changes.
782 """
783 if self.UncommitedFiles(get_all=False):
784 return True
785 else:
786 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500787
Terence Haddock4655e812011-03-31 12:33:34 +0200788 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700789 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200790
791 Args:
792 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700793 """
794 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200795 if output_redir == None:
796 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700797 print(file=output_redir)
798 print('project %s/' % self.relpath, file=output_redir)
799 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700800 return
801
802 self.work_git.update_index('-q',
803 '--unmerged',
804 '--ignore-missing',
805 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700806 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
808 df = self.work_git.DiffZ('diff-files')
809 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100810 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700811 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812
813 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200814 if not output_redir == None:
815 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700816 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700817
818 branch = self.CurrentBranch
819 if branch is None:
820 out.nobranch('(*** NO BRANCH ***)')
821 else:
822 out.branch('branch %s', branch)
823 out.nl()
824
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700825 if rb:
826 out.important('prior sync failed; rebase still in progress')
827 out.nl()
828
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829 paths = list()
830 paths.extend(di.keys())
831 paths.extend(df.keys())
832 paths.extend(do)
833
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530834 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900835 try:
836 i = di[p]
837 except KeyError:
838 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900840 try:
841 f = df[p]
842 except KeyError:
843 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200844
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900845 if i:
846 i_status = i.status.upper()
847 else:
848 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900850 if f:
851 f_status = f.status.lower()
852 else:
853 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854
855 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800856 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857 i.src_path, p, i.level)
858 else:
859 line = ' %s%s\t%s' % (i_status, f_status, p)
860
861 if i and not f:
862 out.added('%s', line)
863 elif (i and f) or (not i and f):
864 out.changed('%s', line)
865 elif not i and not f:
866 out.untracked('%s', line)
867 else:
868 out.write('%s', line)
869 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200870
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700871 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872
pelyad67872d2012-03-28 14:49:58 +0300873 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874 """Prints the status of the repository to stdout.
875 """
876 out = DiffColoring(self.config)
877 cmd = ['diff']
878 if out.is_on:
879 cmd.append('--color')
880 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300881 if absolute_paths:
882 cmd.append('--src-prefix=a/%s/' % self.relpath)
883 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700884 cmd.append('--')
885 p = GitCommand(self,
886 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100887 capture_stdout=True,
888 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889 has_diff = False
890 for line in p.process.stdout:
891 if not has_diff:
892 out.nl()
893 out.project('project %s/' % self.relpath)
894 out.nl()
895 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700896 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700897 p.Wait()
898
899
900## Publish / Upload ##
901
David Pursehouse8a68ff92012-09-24 12:15:13 +0900902 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700903 """Was the branch published (uploaded) for code review?
904 If so, returns the SHA-1 hash of the last published
905 state for the branch.
906 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700907 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900908 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700909 try:
910 return self.bare_git.rev_parse(key)
911 except GitError:
912 return None
913 else:
914 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900915 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700916 except KeyError:
917 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918
David Pursehouse8a68ff92012-09-24 12:15:13 +0900919 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920 """Prunes any stale published refs.
921 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900922 if all_refs is None:
923 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 heads = set()
925 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530926 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 if name.startswith(R_HEADS):
928 heads.add(name)
929 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900930 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530932 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933 n = name[len(R_PUB):]
934 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900935 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700937 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700938 """List any branches which can be uploaded for review.
939 """
940 heads = {}
941 pubed = {}
942
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530943 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900945 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900947 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948
949 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530950 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900951 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700953 if selected_branch and branch != selected_branch:
954 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700955
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800956 rb = self.GetUploadableBranch(branch)
957 if rb:
958 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700959 return ready
960
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800961 def GetUploadableBranch(self, branch_name):
962 """Get a single uploadable branch, or None.
963 """
964 branch = self.GetBranch(branch_name)
965 base = branch.LocalMerge
966 if branch.LocalMerge:
967 rb = ReviewableBranch(self, branch, base)
968 if rb.commits:
969 return rb
970 return None
971
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700972 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100973 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -0700974 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400975 draft=False,
976 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700977 """Uploads the named branch for code review.
978 """
979 if branch is None:
980 branch = self.CurrentBranch
981 if branch is None:
982 raise GitError('not currently on a branch')
983
984 branch = self.GetBranch(branch)
985 if not branch.LocalMerge:
986 raise GitError('branch %s does not track a remote' % branch.name)
987 if not branch.remote.review:
988 raise GitError('remote %s has no review url' % branch.remote.name)
989
Bryan Jacobsf609f912013-05-06 13:36:24 -0400990 if dest_branch is None:
991 dest_branch = self.dest_branch
992 if dest_branch is None:
993 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700994 if not dest_branch.startswith(R_HEADS):
995 dest_branch = R_HEADS + dest_branch
996
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800997 if not branch.remote.projectname:
998 branch.remote.projectname = self.name
999 branch.remote.Save()
1000
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001001 url = branch.remote.ReviewUrl(self.UserEmail)
1002 if url is None:
1003 raise UploadError('review not configured')
1004 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001005
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001006 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001007 rp = ['gerrit receive-pack']
1008 for e in people[0]:
1009 rp.append('--reviewer=%s' % sq(e))
1010 for e in people[1]:
1011 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001012 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001013
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001014 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001015
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001016 if dest_branch.startswith(R_HEADS):
1017 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001018
1019 upload_type = 'for'
1020 if draft:
1021 upload_type = 'drafts'
1022
1023 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1024 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001025 if auto_topic:
1026 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001027 if not url.startswith('ssh://'):
1028 rp = ['r=%s' % p for p in people[0]] + \
1029 ['cc=%s' % p for p in people[1]]
1030 if rp:
1031 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001032 cmd.append(ref_spec)
1033
Anthony King7bdac712014-07-16 12:56:40 +01001034 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001035 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036
1037 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1038 self.bare_git.UpdateRef(R_PUB + branch.name,
1039 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001040 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041
1042
1043## Sync ##
1044
Julien Campergue335f5ef2013-10-16 11:02:35 +02001045 def _ExtractArchive(self, tarpath, path=None):
1046 """Extract the given tar on its current location
1047
1048 Args:
1049 - tarpath: The path to the actual tar file
1050
1051 """
1052 try:
1053 with tarfile.open(tarpath, 'r') as tar:
1054 tar.extractall(path=path)
1055 return True
1056 except (IOError, tarfile.TarError) as e:
1057 print("error: Cannot extract archive %s: "
1058 "%s" % (tarpath, str(e)), file=sys.stderr)
1059 return False
1060
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001061 def Sync_NetworkHalf(self,
1062 quiet=False,
1063 is_new=None,
1064 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001065 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001066 no_tags=False,
David Pursehouseb1553542014-09-04 21:28:09 +09001067 archive=False,
1068 optimized_fetch=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069 """Perform only the network IO portion of the sync process.
1070 Local working directory/branch state is not affected.
1071 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001072 if archive and not isinstance(self, MetaProject):
1073 if self.remote.url.startswith(('http://', 'https://')):
1074 print("error: %s: Cannot fetch archives from http/https "
1075 "remotes." % self.name, file=sys.stderr)
1076 return False
1077
1078 name = self.relpath.replace('\\', '/')
1079 name = name.replace('/', '_')
1080 tarpath = '%s.tar' % name
1081 topdir = self.manifest.topdir
1082
1083 try:
1084 self._FetchArchive(tarpath, cwd=topdir)
1085 except GitError as e:
1086 print('error: %s' % str(e), file=sys.stderr)
1087 return False
1088
1089 # From now on, we only need absolute tarpath
1090 tarpath = os.path.join(topdir, tarpath)
1091
1092 if not self._ExtractArchive(tarpath, path=topdir):
1093 return False
1094 try:
1095 os.remove(tarpath)
1096 except OSError as e:
1097 print("warn: Cannot remove archive %s: "
1098 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001099 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001100 return True
1101
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001102 if is_new is None:
1103 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001104 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001105 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001106 else:
1107 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001109
1110 if is_new:
1111 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1112 try:
1113 fd = open(alt, 'rb')
1114 try:
1115 alt_dir = fd.readline().rstrip()
1116 finally:
1117 fd.close()
1118 except IOError:
1119 alt_dir = None
1120 else:
1121 alt_dir = None
1122
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001123 if clone_bundle \
1124 and alt_dir is None \
1125 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001126 is_new = False
1127
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001128 if not current_branch_only:
1129 if self.sync_c:
1130 current_branch_only = True
1131 elif not self.manifest._loaded:
1132 # Manifest cannot check defaults until it syncs.
1133 current_branch_only = False
1134 elif self.manifest.default.sync_c:
1135 current_branch_only = True
1136
David Pursehouseb1553542014-09-04 21:28:09 +09001137 need_to_fetch = not (optimized_fetch and \
1138 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1139 if (need_to_fetch
Conley Owens666d5342014-05-01 13:09:57 -07001140 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1141 current_branch_only=current_branch_only,
1142 no_tags=no_tags)):
Anthony King7bdac712014-07-16 12:56:40 +01001143 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001144
1145 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001146 self._InitMRef()
1147 else:
1148 self._InitMirrorHead()
1149 try:
1150 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1151 except OSError:
1152 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001153 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001154
1155 def PostRepoUpgrade(self):
1156 self._InitHooks()
1157
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001158 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001159 for copyfile in self.copyfiles:
1160 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001161 for linkfile in self.linkfiles:
1162 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163
Julien Camperguedd654222014-01-09 16:21:37 +01001164 def GetCommitRevisionId(self):
1165 """Get revisionId of a commit.
1166
1167 Use this method instead of GetRevisionId to get the id of the commit rather
1168 than the id of the current git object (for example, a tag)
1169
1170 """
1171 if not self.revisionExpr.startswith(R_TAGS):
1172 return self.GetRevisionId(self._allrefs)
1173
1174 try:
1175 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1176 except GitError:
1177 raise ManifestInvalidRevisionError(
1178 'revision %s in %s not found' % (self.revisionExpr,
1179 self.name))
1180
David Pursehouse8a68ff92012-09-24 12:15:13 +09001181 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001182 if self.revisionId:
1183 return self.revisionId
1184
1185 rem = self.GetRemote(self.remote.name)
1186 rev = rem.ToLocal(self.revisionExpr)
1187
David Pursehouse8a68ff92012-09-24 12:15:13 +09001188 if all_refs is not None and rev in all_refs:
1189 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001190
1191 try:
1192 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1193 except GitError:
1194 raise ManifestInvalidRevisionError(
1195 'revision %s in %s not found' % (self.revisionExpr,
1196 self.name))
1197
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001198 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001199 """Perform only the local IO portion of the sync process.
1200 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201 """
David James8d201162013-10-11 17:03:19 -07001202 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001203 all_refs = self.bare_ref.all
1204 self.CleanPublishedCache(all_refs)
1205 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001206
David Pursehouse1d947b32012-10-25 12:23:11 +09001207 def _doff():
1208 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001209 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001210
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001211 head = self.work_git.GetHead()
1212 if head.startswith(R_HEADS):
1213 branch = head[len(R_HEADS):]
1214 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001215 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001216 except KeyError:
1217 head = None
1218 else:
1219 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001220
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001221 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001222 # Currently on a detached HEAD. The user is assumed to
1223 # not have any local modifications worth worrying about.
1224 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001225 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001226 syncbuf.fail(self, _PriorSyncFailedError())
1227 return
1228
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001229 if head == revid:
1230 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001231 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001232 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001233 if not syncbuf.detach_head:
1234 return
1235 else:
1236 lost = self._revlist(not_rev(revid), HEAD)
1237 if lost:
1238 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001239
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001240 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001241 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001242 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001243 syncbuf.fail(self, e)
1244 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001245 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001246 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001248 if head == revid:
1249 # No changes; don't do anything further.
1250 #
1251 return
1252
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001253 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001254
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001255 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001256 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001257 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001259 syncbuf.info(self,
1260 "leaving %s; does not track upstream",
1261 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001262 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001263 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001264 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001265 syncbuf.fail(self, e)
1266 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001267 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001268 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001269
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001270 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001271 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001272 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001273 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001274 if not_merged:
1275 if upstream_gain:
1276 # The user has published this branch and some of those
1277 # commits are not yet merged upstream. We do not want
1278 # to rewrite the published commits so we punt.
1279 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001280 syncbuf.fail(self,
1281 "branch %s is published (but not merged) and is now %d commits behind"
1282 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001283 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001284 elif pub == head:
1285 # All published commits are merged, and thus we are a
1286 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001287 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001288 syncbuf.later1(self, _doff)
1289 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001290
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001291 # Examine the local commits not in the remote. Find the
1292 # last one attributed to this user, if any.
1293 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001294 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001295 last_mine = None
1296 cnt_mine = 0
1297 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301298 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001299 if committer_email == self.UserEmail:
1300 last_mine = commit_id
1301 cnt_mine += 1
1302
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001303 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001304 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305
1306 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001307 syncbuf.fail(self, _DirtyError())
1308 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001309
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001310 # If the upstream switched on us, warn the user.
1311 #
1312 if branch.merge != self.revisionExpr:
1313 if branch.merge and self.revisionExpr:
1314 syncbuf.info(self,
1315 'manifest switched %s...%s',
1316 branch.merge,
1317 self.revisionExpr)
1318 elif branch.merge:
1319 syncbuf.info(self,
1320 'manifest no longer tracks %s',
1321 branch.merge)
1322
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001323 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001325 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001327 syncbuf.info(self,
1328 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001329 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001331 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001332 if not ID_RE.match(self.revisionExpr):
1333 # in case of manifest sync the revisionExpr might be a SHA1
1334 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001335 if not branch.merge.startswith('refs/'):
1336 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337 branch.Save()
1338
Mike Pontillod3153822012-02-28 11:53:24 -08001339 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001340 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001341 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001342 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001343 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001344 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001345 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001346 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001347 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001348 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001349 syncbuf.fail(self, e)
1350 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001351 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001352 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001354 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001355 # dest should already be an absolute path, but src is project relative
1356 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001357 abssrc = os.path.join(self.worktree, src)
1358 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001360 def AddLinkFile(self, src, dest, absdest):
1361 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001362 # make src relative path to dest
1363 absdestdir = os.path.dirname(absdest)
1364 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
1365 self.linkfiles.append(_LinkFile(src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001366
James W. Mills24c13082012-04-12 15:04:13 -05001367 def AddAnnotation(self, name, value, keep):
1368 self.annotations.append(_Annotation(name, value, keep))
1369
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001370 def DownloadPatchSet(self, change_id, patch_id):
1371 """Download a single patch set of a single change to FETCH_HEAD.
1372 """
1373 remote = self.GetRemote(self.remote.name)
1374
1375 cmd = ['fetch', remote.name]
1376 cmd.append('refs/changes/%2.2d/%d/%d' \
1377 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001378 if GitCommand(self, cmd, bare=True).Wait() != 0:
1379 return None
1380 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001381 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001382 change_id,
1383 patch_id,
1384 self.bare_git.rev_parse('FETCH_HEAD'))
1385
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386
1387## Branch Management ##
1388
1389 def StartBranch(self, name):
1390 """Create a new branch off the manifest's revision.
1391 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001392 head = self.work_git.GetHead()
1393 if head == (R_HEADS + name):
1394 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001395
David Pursehouse8a68ff92012-09-24 12:15:13 +09001396 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001397 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001398 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001399 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001400 capture_stdout=True,
1401 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001402
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001403 branch = self.GetBranch(name)
1404 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001405 branch.merge = self.revisionExpr
Alexandre Boeglin38258272015-04-30 14:50:33 +02001406 if not branch.merge.startswith('refs/') and not ID_RE.match(self.revisionExpr):
Conley Owens04f2f0e2014-10-01 17:22:46 -07001407 branch.merge = R_HEADS + self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001408 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001409
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001410 if head.startswith(R_HEADS):
1411 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001412 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001413 except KeyError:
1414 head = None
1415
1416 if revid and head and revid == head:
1417 ref = os.path.join(self.gitdir, R_HEADS + name)
1418 try:
1419 os.makedirs(os.path.dirname(ref))
1420 except OSError:
1421 pass
1422 _lwrite(ref, '%s\n' % revid)
1423 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1424 'ref: %s%s\n' % (R_HEADS, name))
1425 branch.Save()
1426 return True
1427
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001428 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001429 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001430 capture_stdout=True,
1431 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001432 branch.Save()
1433 return True
1434 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001435
Wink Saville02d79452009-04-10 13:01:24 -07001436 def CheckoutBranch(self, name):
1437 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001438
1439 Args:
1440 name: The name of the branch to checkout.
1441
1442 Returns:
1443 True if the checkout succeeded; False if it didn't; None if the branch
1444 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001445 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001446 rev = R_HEADS + name
1447 head = self.work_git.GetHead()
1448 if head == rev:
1449 # Already on the branch
1450 #
1451 return True
Wink Saville02d79452009-04-10 13:01:24 -07001452
David Pursehouse8a68ff92012-09-24 12:15:13 +09001453 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001454 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001455 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001456 except KeyError:
1457 # Branch does not exist in this project
1458 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001459 return None
Wink Saville02d79452009-04-10 13:01:24 -07001460
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001461 if head.startswith(R_HEADS):
1462 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001463 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001464 except KeyError:
1465 head = None
1466
1467 if head == revid:
1468 # Same revision; just update HEAD to point to the new
1469 # target branch, but otherwise take no other action.
1470 #
1471 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1472 'ref: %s%s\n' % (R_HEADS, name))
1473 return True
1474
1475 return GitCommand(self,
1476 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001477 capture_stdout=True,
1478 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001479
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001480 def AbandonBranch(self, name):
1481 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001482
1483 Args:
1484 name: The name of the branch to abandon.
1485
1486 Returns:
1487 True if the abandon succeeded; False if it didn't; None if the branch
1488 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001489 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001490 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001491 all_refs = self.bare_ref.all
1492 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001493 # Doesn't exist
1494 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001495
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001496 head = self.work_git.GetHead()
1497 if head == rev:
1498 # We can't destroy the branch while we are sitting
1499 # on it. Switch to a detached HEAD.
1500 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001501 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001502
David Pursehouse8a68ff92012-09-24 12:15:13 +09001503 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001504 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001505 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1506 '%s\n' % revid)
1507 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001508 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001509
1510 return GitCommand(self,
1511 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001512 capture_stdout=True,
1513 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001514
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001515 def PruneHeads(self):
1516 """Prune any topic branches already merged into upstream.
1517 """
1518 cb = self.CurrentBranch
1519 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001520 left = self._allrefs
1521 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001522 if name.startswith(R_HEADS):
1523 name = name[len(R_HEADS):]
1524 if cb is None or name != cb:
1525 kill.append(name)
1526
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001527 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001528 if cb is not None \
1529 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001530 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001531 self.work_git.DetachHead(HEAD)
1532 kill.append(cb)
1533
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001534 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001535 old = self.bare_git.GetHead()
1536 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001537 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1538
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001539 try:
1540 self.bare_git.DetachHead(rev)
1541
1542 b = ['branch', '-d']
1543 b.extend(kill)
1544 b = GitCommand(self, b, bare=True,
1545 capture_stdout=True,
1546 capture_stderr=True)
1547 b.Wait()
1548 finally:
1549 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001550 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001551
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001552 for branch in kill:
1553 if (R_HEADS + branch) not in left:
1554 self.CleanPublishedCache()
1555 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001556
1557 if cb and cb not in kill:
1558 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001559 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001560
1561 kept = []
1562 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001563 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001564 branch = self.GetBranch(branch)
1565 base = branch.LocalMerge
1566 if not base:
1567 base = rev
1568 kept.append(ReviewableBranch(self, branch, base))
1569 return kept
1570
1571
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001572## Submodule Management ##
1573
1574 def GetRegisteredSubprojects(self):
1575 result = []
1576 def rec(subprojects):
1577 if not subprojects:
1578 return
1579 result.extend(subprojects)
1580 for p in subprojects:
1581 rec(p.subprojects)
1582 rec(self.subprojects)
1583 return result
1584
1585 def _GetSubmodules(self):
1586 # Unfortunately we cannot call `git submodule status --recursive` here
1587 # because the working tree might not exist yet, and it cannot be used
1588 # without a working tree in its current implementation.
1589
1590 def get_submodules(gitdir, rev):
1591 # Parse .gitmodules for submodule sub_paths and sub_urls
1592 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1593 if not sub_paths:
1594 return []
1595 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1596 # revision of submodule repository
1597 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1598 submodules = []
1599 for sub_path, sub_url in zip(sub_paths, sub_urls):
1600 try:
1601 sub_rev = sub_revs[sub_path]
1602 except KeyError:
1603 # Ignore non-exist submodules
1604 continue
1605 submodules.append((sub_rev, sub_path, sub_url))
1606 return submodules
1607
1608 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1609 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1610 def parse_gitmodules(gitdir, rev):
1611 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1612 try:
Anthony King7bdac712014-07-16 12:56:40 +01001613 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1614 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001615 except GitError:
1616 return [], []
1617 if p.Wait() != 0:
1618 return [], []
1619
1620 gitmodules_lines = []
1621 fd, temp_gitmodules_path = tempfile.mkstemp()
1622 try:
1623 os.write(fd, p.stdout)
1624 os.close(fd)
1625 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001626 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1627 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001628 if p.Wait() != 0:
1629 return [], []
1630 gitmodules_lines = p.stdout.split('\n')
1631 except GitError:
1632 return [], []
1633 finally:
1634 os.remove(temp_gitmodules_path)
1635
1636 names = set()
1637 paths = {}
1638 urls = {}
1639 for line in gitmodules_lines:
1640 if not line:
1641 continue
1642 m = re_path.match(line)
1643 if m:
1644 names.add(m.group(1))
1645 paths[m.group(1)] = m.group(2)
1646 continue
1647 m = re_url.match(line)
1648 if m:
1649 names.add(m.group(1))
1650 urls[m.group(1)] = m.group(2)
1651 continue
1652 names = sorted(names)
1653 return ([paths.get(name, '') for name in names],
1654 [urls.get(name, '') for name in names])
1655
1656 def git_ls_tree(gitdir, rev, paths):
1657 cmd = ['ls-tree', rev, '--']
1658 cmd.extend(paths)
1659 try:
Anthony King7bdac712014-07-16 12:56:40 +01001660 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1661 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001662 except GitError:
1663 return []
1664 if p.Wait() != 0:
1665 return []
1666 objects = {}
1667 for line in p.stdout.split('\n'):
1668 if not line.strip():
1669 continue
1670 object_rev, object_path = line.split()[2:4]
1671 objects[object_path] = object_rev
1672 return objects
1673
1674 try:
1675 rev = self.GetRevisionId()
1676 except GitError:
1677 return []
1678 return get_submodules(self.gitdir, rev)
1679
1680 def GetDerivedSubprojects(self):
1681 result = []
1682 if not self.Exists:
1683 # If git repo does not exist yet, querying its submodules will
1684 # mess up its states; so return here.
1685 return result
1686 for rev, path, url in self._GetSubmodules():
1687 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001688 relpath, worktree, gitdir, objdir = \
1689 self.manifest.GetSubprojectPaths(self, name, path)
1690 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001691 if project:
1692 result.extend(project.GetDerivedSubprojects())
1693 continue
David James8d201162013-10-11 17:03:19 -07001694
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001695 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001696 url=url,
1697 review=self.remote.review,
1698 revision=self.remote.revision)
1699 subproject = Project(manifest=self.manifest,
1700 name=name,
1701 remote=remote,
1702 gitdir=gitdir,
1703 objdir=objdir,
1704 worktree=worktree,
1705 relpath=relpath,
1706 revisionExpr=self.revisionExpr,
1707 revisionId=rev,
1708 rebase=self.rebase,
1709 groups=self.groups,
1710 sync_c=self.sync_c,
1711 sync_s=self.sync_s,
1712 parent=self,
1713 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001714 result.append(subproject)
1715 result.extend(subproject.GetDerivedSubprojects())
1716 return result
1717
1718
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001719## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001720 def _CheckForSha1(self):
1721 try:
1722 # if revision (sha or tag) is not present then following function
1723 # throws an error.
1724 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1725 return True
1726 except GitError:
1727 # There is no such persistent revision. We have to fetch it.
1728 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001729
Julien Campergue335f5ef2013-10-16 11:02:35 +02001730 def _FetchArchive(self, tarpath, cwd=None):
1731 cmd = ['archive', '-v', '-o', tarpath]
1732 cmd.append('--remote=%s' % self.remote.url)
1733 cmd.append('--prefix=%s/' % self.relpath)
1734 cmd.append(self.revisionExpr)
1735
1736 command = GitCommand(self, cmd, cwd=cwd,
1737 capture_stdout=True,
1738 capture_stderr=True)
1739
1740 if command.Wait() != 0:
1741 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1742
Conley Owens80b87fe2014-05-09 17:13:44 -07001743
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001744 def _RemoteFetch(self, name=None,
1745 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001746 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001747 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001748 alt_dir=None,
1749 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001750
1751 is_sha1 = False
1752 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001753 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001754
David Pursehouse9bc422f2014-04-15 10:28:56 +09001755 # The depth should not be used when fetching to a mirror because
1756 # it will result in a shallow repository that cannot be cloned or
1757 # fetched from.
1758 if not self.manifest.IsMirror:
1759 if self.clone_depth:
1760 depth = self.clone_depth
1761 else:
1762 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001763 # The repo project should never be synced with partial depth
1764 if self.relpath == '.repo/repo':
1765 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001766
Shawn Pearce69e04d82014-01-29 12:48:54 -08001767 if depth:
1768 current_branch_only = True
1769
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001770 if ID_RE.match(self.revisionExpr) is not None:
1771 is_sha1 = True
1772
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001773 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001774 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001775 # this is a tag and its sha1 value should never change
1776 tag_name = self.revisionExpr[len(R_TAGS):]
1777
1778 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001779 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001780 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001781 if is_sha1 and not depth:
1782 # When syncing a specific commit and --depth is not set:
1783 # * if upstream is explicitly specified and is not a sha1, fetch only
1784 # upstream as users expect only upstream to be fetch.
1785 # Note: The commit might not be in upstream in which case the sync
1786 # will fail.
1787 # * otherwise, fetch all branches to make sure we end up with the
1788 # specific commit.
1789 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001790
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001791 if not name:
1792 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001793
1794 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001795 remote = self.GetRemote(name)
1796 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001797 ssh_proxy = True
1798
Shawn O. Pearce88443382010-10-08 10:02:09 +02001799 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001800 if alt_dir and 'objects' == os.path.basename(alt_dir):
1801 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001802 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1803 remote = self.GetRemote(name)
1804
David Pursehouse8a68ff92012-09-24 12:15:13 +09001805 all_refs = self.bare_ref.all
1806 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001807 tmp = set()
1808
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301809 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001810 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001811 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001812 all_refs[r] = ref_id
1813 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001814 continue
1815
David Pursehouse8a68ff92012-09-24 12:15:13 +09001816 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001817 continue
1818
David Pursehouse8a68ff92012-09-24 12:15:13 +09001819 r = 'refs/_alt/%s' % ref_id
1820 all_refs[r] = ref_id
1821 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001822 tmp.add(r)
1823
Shawn O. Pearce88443382010-10-08 10:02:09 +02001824 tmp_packed = ''
1825 old_packed = ''
1826
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301827 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001828 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001829 tmp_packed += line
1830 if r not in tmp:
1831 old_packed += line
1832
1833 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001834 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001835 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001836
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001837 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001838
Conley Owensf97e8382015-01-21 11:12:46 -08001839 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001840 cmd.append('--depth=%s' % depth)
1841
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001842 if quiet:
1843 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001844 if not self.worktree:
1845 cmd.append('--update-head-ok')
David Pursehouseb4d43b92015-04-28 18:28:12 +09001846 if self.manifest.IsMirror:
1847 cmd.append('--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001848 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001849
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001850 # If using depth then we should not get all the tags since they may
1851 # be outside of the depth.
1852 if no_tags or depth:
1853 cmd.append('--no-tags')
1854 else:
1855 cmd.append('--tags')
1856
Conley Owens80b87fe2014-05-09 17:13:44 -07001857 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001858 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001859 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001860 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001861 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001862 spec.append('tag')
1863 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001864
David Pursehouse403b64e2015-04-27 10:41:33 +09001865 if not self.manifest.IsMirror:
1866 branch = self.revisionExpr
1867 if is_sha1 and depth:
1868 # Shallow checkout of a specific commit, fetch from that commit and not
1869 # the heads only as the commit might be deeper in the history.
1870 spec.append(branch)
1871 else:
1872 if is_sha1:
1873 branch = self.upstream
1874 if branch is not None and branch.strip():
1875 if not branch.startswith('refs/'):
1876 branch = R_HEADS + branch
1877 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001878 cmd.extend(spec)
1879
1880 shallowfetch = self.config.GetString('repo.shallowfetch')
1881 if shallowfetch and shallowfetch != ' '.join(spec):
Anthony King23ff7df2015-03-28 19:42:39 +00001882 GitCommand(self, ['fetch', '--depth=2147483647', name]
1883 + shallowfetch.split(),
Conley Owens80b87fe2014-05-09 17:13:44 -07001884 bare=True, ssh_proxy=ssh_proxy).Wait()
1885 if depth:
Anthony King7bdac712014-07-16 12:56:40 +01001886 self.config.SetString('repo.shallowfetch', ' '.join(spec))
Conley Owens80b87fe2014-05-09 17:13:44 -07001887 else:
Anthony King7bdac712014-07-16 12:56:40 +01001888 self.config.SetString('repo.shallowfetch', None)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001889
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001890 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001891 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001892 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001893 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001894 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001895 ok = True
1896 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001897 # If needed, run the 'git remote prune' the first time through the loop
1898 elif (not _i and
1899 "error:" in gitcmd.stderr and
1900 "git remote prune" in gitcmd.stderr):
1901 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001902 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001903 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001904 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001905 break
1906 continue
Brian Harring14a66742012-09-28 20:21:57 -07001907 elif current_branch_only and is_sha1 and ret == 128:
1908 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1909 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1910 # abort the optimization attempt and do a full sync.
1911 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001912 elif ret < 0:
1913 # Git died with a signal, exit immediately
1914 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001915 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001916
1917 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001918 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001919 if old_packed != '':
1920 _lwrite(packed_refs, old_packed)
1921 else:
1922 os.remove(packed_refs)
1923 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001924
1925 if is_sha1 and current_branch_only and self.upstream:
1926 # We just synced the upstream given branch; verify we
1927 # got what we wanted, else trigger a second run of all
1928 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001929 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001930 return self._RemoteFetch(name=name, current_branch_only=False,
1931 initial=False, quiet=quiet, alt_dir=alt_dir)
1932
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001933 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001934
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001935 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001936 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001937 return False
1938
1939 remote = self.GetRemote(self.remote.name)
1940 bundle_url = remote.url + '/clone.bundle'
1941 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001942 if GetSchemeFromUrl(bundle_url) not in (
1943 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001944 return False
1945
1946 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1947 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1948
1949 exist_dst = os.path.exists(bundle_dst)
1950 exist_tmp = os.path.exists(bundle_tmp)
1951
1952 if not initial and not exist_dst and not exist_tmp:
1953 return False
1954
1955 if not exist_dst:
1956 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1957 if not exist_dst:
1958 return False
1959
1960 cmd = ['fetch']
1961 if quiet:
1962 cmd.append('--quiet')
1963 if not self.worktree:
1964 cmd.append('--update-head-ok')
1965 cmd.append(bundle_dst)
1966 for f in remote.fetch:
1967 cmd.append(str(f))
1968 cmd.append('refs/tags/*:refs/tags/*')
1969
1970 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001971 if os.path.exists(bundle_dst):
1972 os.remove(bundle_dst)
1973 if os.path.exists(bundle_tmp):
1974 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001975 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001976
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001977 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001978 if os.path.exists(dstPath):
1979 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001980
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001981 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001982 if quiet:
1983 cmd += ['--silent']
1984 if os.path.exists(tmpPath):
1985 size = os.stat(tmpPath).st_size
1986 if size >= 1024:
1987 cmd += ['--continue-at', '%d' % (size,)]
1988 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001989 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001990 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1991 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz497bde42015-01-02 13:58:05 -08001992 with self._GetBundleCookieFile(srcUrl, quiet) as cookiefile:
Dave Borowitz137d0132015-01-02 11:12:54 -08001993 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08001994 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08001995 if srcUrl.startswith('persistent-'):
1996 srcUrl = srcUrl[len('persistent-'):]
1997 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001998
Dave Borowitz137d0132015-01-02 11:12:54 -08001999 if IsTrace():
2000 Trace('%s', ' '.join(cmd))
2001 try:
2002 proc = subprocess.Popen(cmd)
2003 except OSError:
2004 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002005
Dave Borowitz137d0132015-01-02 11:12:54 -08002006 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002007
Dave Borowitz137d0132015-01-02 11:12:54 -08002008 if curlret == 22:
2009 # From curl man page:
2010 # 22: HTTP page not retrieved. The requested url was not found or
2011 # returned another error with the HTTP error code being 400 or above.
2012 # This return code only appears if -f, --fail is used.
2013 if not quiet:
2014 print("Server does not provide clone.bundle; ignoring.",
2015 file=sys.stderr)
2016 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002017
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002018 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002019 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002020 os.rename(tmpPath, dstPath)
2021 return True
2022 else:
2023 os.remove(tmpPath)
2024 return False
2025 else:
2026 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002027
Kris Giesingc8d882a2014-12-23 13:02:32 -08002028 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002029 try:
2030 with open(path) as f:
2031 if f.read(16) == '# v2 git bundle\n':
2032 return True
2033 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002034 if not quiet:
2035 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002036 return False
2037 except OSError:
2038 return False
2039
Dave Borowitz137d0132015-01-02 11:12:54 -08002040 @contextlib.contextmanager
Dave Borowitz497bde42015-01-02 13:58:05 -08002041 def _GetBundleCookieFile(self, url, quiet):
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002042 if url.startswith('persistent-'):
2043 try:
2044 p = subprocess.Popen(
2045 ['git-remote-persistent-https', '-print_config', url],
2046 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2047 stderr=subprocess.PIPE)
Dave Borowitz137d0132015-01-02 11:12:54 -08002048 try:
2049 prefix = 'http.cookiefile='
2050 cookiefile = None
2051 for line in p.stdout:
2052 line = line.strip()
2053 if line.startswith(prefix):
2054 cookiefile = line[len(prefix):]
2055 break
2056 # Leave subprocess open, as cookie file may be transient.
2057 if cookiefile:
2058 yield cookiefile
2059 return
2060 finally:
2061 p.stdin.close()
2062 if p.wait():
2063 err_msg = p.stderr.read()
2064 if ' -print_config' in err_msg:
2065 pass # Persistent proxy doesn't support -print_config.
Dave Borowitz497bde42015-01-02 13:58:05 -08002066 elif not quiet:
Dave Borowitz137d0132015-01-02 11:12:54 -08002067 print(err_msg, file=sys.stderr)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002068 except OSError as e:
2069 if e.errno == errno.ENOENT:
2070 pass # No persistent proxy.
2071 raise
Dave Borowitz137d0132015-01-02 11:12:54 -08002072 yield GitConfig.ForUser().GetString('http.cookiefile')
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002073
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002074 def _Checkout(self, rev, quiet=False):
2075 cmd = ['checkout']
2076 if quiet:
2077 cmd.append('-q')
2078 cmd.append(rev)
2079 cmd.append('--')
2080 if GitCommand(self, cmd).Wait() != 0:
2081 if self._allrefs:
2082 raise GitError('%s checkout %s ' % (self.name, rev))
2083
Anthony King7bdac712014-07-16 12:56:40 +01002084 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002085 cmd = ['cherry-pick']
2086 cmd.append(rev)
2087 cmd.append('--')
2088 if GitCommand(self, cmd).Wait() != 0:
2089 if self._allrefs:
2090 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2091
Anthony King7bdac712014-07-16 12:56:40 +01002092 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002093 cmd = ['revert']
2094 cmd.append('--no-edit')
2095 cmd.append(rev)
2096 cmd.append('--')
2097 if GitCommand(self, cmd).Wait() != 0:
2098 if self._allrefs:
2099 raise GitError('%s revert %s ' % (self.name, rev))
2100
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002101 def _ResetHard(self, rev, quiet=True):
2102 cmd = ['reset', '--hard']
2103 if quiet:
2104 cmd.append('-q')
2105 cmd.append(rev)
2106 if GitCommand(self, cmd).Wait() != 0:
2107 raise GitError('%s reset --hard %s ' % (self.name, rev))
2108
Anthony King7bdac712014-07-16 12:56:40 +01002109 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002110 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002111 if onto is not None:
2112 cmd.extend(['--onto', onto])
2113 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002114 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002115 raise GitError('%s rebase %s ' % (self.name, upstream))
2116
Pierre Tardy3d125942012-05-04 12:18:12 +02002117 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002118 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002119 if ffonly:
2120 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002121 if GitCommand(self, cmd).Wait() != 0:
2122 raise GitError('%s merge %s ' % (self.name, head))
2123
Jonathan Nieder93719792015-03-17 11:29:58 -07002124 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002125 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002126
2127 # Initialize the bare repository, which contains all of the objects.
2128 if not os.path.exists(self.objdir):
2129 os.makedirs(self.objdir)
2130 self.bare_objdir.init()
2131
2132 # If we have a separate directory to hold refs, initialize it as well.
2133 if self.objdir != self.gitdir:
2134 os.makedirs(self.gitdir)
2135 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2136 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002137
Shawn O. Pearce88443382010-10-08 10:02:09 +02002138 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002139 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002140
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002141 if ref_dir or mirror_git:
2142 if not mirror_git:
2143 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002144 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2145 self.relpath + '.git')
2146
2147 if os.path.exists(mirror_git):
2148 ref_dir = mirror_git
2149
2150 elif os.path.exists(repo_git):
2151 ref_dir = repo_git
2152
2153 else:
2154 ref_dir = None
2155
2156 if ref_dir:
2157 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2158 os.path.join(ref_dir, 'objects') + '\n')
2159
Jimmie Westera0444582012-10-24 13:44:42 +02002160 self._UpdateHooks()
2161
2162 m = self.manifest.manifestProject.config
2163 for key in ['user.name', 'user.email']:
Anthony King7bdac712014-07-16 12:56:40 +01002164 if m.Has(key, include_defaults=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002165 self.config.SetString(key, m.GetString(key))
Jonathan Nieder93719792015-03-17 11:29:58 -07002166 if self.manifest.IsMirror:
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002167 self.config.SetString('core.bare', 'true')
2168 else:
2169 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002170
Jimmie Westera0444582012-10-24 13:44:42 +02002171 def _UpdateHooks(self):
2172 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002173 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002174
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002175 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002176 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002177 if not os.path.exists(hooks):
2178 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002179 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002180 name = os.path.basename(stock_hook)
2181
Victor Boivie65e0f352011-04-18 11:23:29 +02002182 if name in ('commit-msg',) and not self.remote.review \
2183 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002184 # Don't install a Gerrit Code Review hook if this
2185 # project does not appear to use it for reviews.
2186 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002187 # Since the manifest project is one of those, but also
2188 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002189 continue
2190
2191 dst = os.path.join(hooks, name)
2192 if os.path.islink(dst):
2193 continue
2194 if os.path.exists(dst):
2195 if filecmp.cmp(stock_hook, dst, shallow=False):
2196 os.remove(dst)
2197 else:
2198 _error("%s: Not replacing %s hook", self.relpath, name)
2199 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002200 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002201 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002202 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002203 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002204 raise GitError('filesystem must support symlinks')
2205 else:
2206 raise
2207
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002208 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002209 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002210 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002211 remote.url = self.remote.url
2212 remote.review = self.remote.review
2213 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002214
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002215 if self.worktree:
2216 remote.ResetFetch(mirror=False)
2217 else:
2218 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002219 remote.Save()
2220
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002221 def _InitMRef(self):
2222 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002223 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002224
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002225 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002226 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002227
2228 def _InitAnyMRef(self, ref):
2229 cur = self.bare_ref.symref(ref)
2230
2231 if self.revisionId:
2232 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2233 msg = 'manifest set to %s' % self.revisionId
2234 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002235 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002236 else:
2237 remote = self.GetRemote(self.remote.name)
2238 dst = remote.ToLocal(self.revisionExpr)
2239 if cur != dst:
2240 msg = 'manifest set to %s' % self.revisionExpr
2241 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002242
David James8d201162013-10-11 17:03:19 -07002243 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2244 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2245
2246 Args:
2247 gitdir: The bare git repository. Must already be initialized.
2248 dotgit: The repository you would like to initialize.
2249 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2250 Only one work tree can store refs under a given |gitdir|.
2251 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2252 This saves you the effort of initializing |dotgit| yourself.
2253 """
2254 # These objects can be shared between several working trees.
2255 symlink_files = ['description', 'info']
2256 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2257 if share_refs:
2258 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002259 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002260 symlink_dirs += ['logs', 'refs']
2261 to_symlink = symlink_files + symlink_dirs
2262
2263 to_copy = []
2264 if copy_all:
2265 to_copy = os.listdir(gitdir)
2266
2267 for name in set(to_copy).union(to_symlink):
2268 try:
2269 src = os.path.realpath(os.path.join(gitdir, name))
2270 dst = os.path.realpath(os.path.join(dotgit, name))
2271
2272 if os.path.lexists(dst) and not os.path.islink(dst):
2273 raise GitError('cannot overwrite a local work tree')
2274
2275 # If the source dir doesn't exist, create an empty dir.
2276 if name in symlink_dirs and not os.path.lexists(src):
2277 os.makedirs(src)
2278
Conley Owens80b87fe2014-05-09 17:13:44 -07002279 # If the source file doesn't exist, ensure the destination
2280 # file doesn't either.
2281 if name in symlink_files and not os.path.lexists(src):
2282 try:
2283 os.remove(dst)
2284 except OSError:
2285 pass
2286
David James8d201162013-10-11 17:03:19 -07002287 if name in to_symlink:
2288 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2289 elif copy_all and not os.path.islink(dst):
2290 if os.path.isdir(src):
2291 shutil.copytree(src, dst)
2292 elif os.path.isfile(src):
2293 shutil.copy(src, dst)
2294 except OSError as e:
2295 if e.errno == errno.EPERM:
2296 raise GitError('filesystem must support symlinks')
2297 else:
2298 raise
2299
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002300 def _InitWorkTree(self):
2301 dotgit = os.path.join(self.worktree, '.git')
2302 if not os.path.exists(dotgit):
2303 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002304 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2305 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002306
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002307 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002308
2309 cmd = ['read-tree', '--reset', '-u']
2310 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002311 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002312 if GitCommand(self, cmd).Wait() != 0:
2313 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002314
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002315 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002316
2317 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002318 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002319
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002320 def _revlist(self, *args, **kw):
2321 a = []
2322 a.extend(args)
2323 a.append('--')
2324 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002325
2326 @property
2327 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002328 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002329
Julien Camperguedd654222014-01-09 16:21:37 +01002330 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2331 """Get logs between two revisions of this project."""
2332 comp = '..'
2333 if rev1:
2334 revs = [rev1]
2335 if rev2:
2336 revs.extend([comp, rev2])
2337 cmd = ['log', ''.join(revs)]
2338 out = DiffColoring(self.config)
2339 if out.is_on and color:
2340 cmd.append('--color')
2341 if oneline:
2342 cmd.append('--oneline')
2343
2344 try:
2345 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2346 if log.Wait() == 0:
2347 return log.stdout
2348 except GitError:
2349 # worktree may not exist if groups changed for example. In that case,
2350 # try in gitdir instead.
2351 if not os.path.exists(self.worktree):
2352 return self.bare_git.log(*cmd[1:])
2353 else:
2354 raise
2355 return None
2356
2357 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2358 """Get the list of logs from this revision to given revisionId"""
2359 logs = {}
2360 selfId = self.GetRevisionId(self._allrefs)
2361 toId = toProject.GetRevisionId(toProject._allrefs)
2362
2363 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2364 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2365 return logs
2366
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002367 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002368 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002369 self._project = project
2370 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002371 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002372
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002373 def LsOthers(self):
2374 p = GitCommand(self._project,
2375 ['ls-files',
2376 '-z',
2377 '--others',
2378 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002379 bare=False,
David James8d201162013-10-11 17:03:19 -07002380 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002381 capture_stdout=True,
2382 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002383 if p.Wait() == 0:
2384 out = p.stdout
2385 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002386 return out[:-1].split('\0') # pylint: disable=W1401
2387 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002388 return []
2389
2390 def DiffZ(self, name, *args):
2391 cmd = [name]
2392 cmd.append('-z')
2393 cmd.extend(args)
2394 p = GitCommand(self._project,
2395 cmd,
David James8d201162013-10-11 17:03:19 -07002396 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002397 bare=False,
2398 capture_stdout=True,
2399 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002400 try:
2401 out = p.process.stdout.read()
2402 r = {}
2403 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002404 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002405 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002406 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002407 info = next(out)
2408 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002409 except StopIteration:
2410 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002411
2412 class _Info(object):
2413 def __init__(self, path, omode, nmode, oid, nid, state):
2414 self.path = path
2415 self.src_path = None
2416 self.old_mode = omode
2417 self.new_mode = nmode
2418 self.old_id = oid
2419 self.new_id = nid
2420
2421 if len(state) == 1:
2422 self.status = state
2423 self.level = None
2424 else:
2425 self.status = state[:1]
2426 self.level = state[1:]
2427 while self.level.startswith('0'):
2428 self.level = self.level[1:]
2429
2430 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002431 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002432 if info.status in ('R', 'C'):
2433 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002434 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002435 r[info.path] = info
2436 return r
2437 finally:
2438 p.Wait()
2439
2440 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002441 if self._bare:
2442 path = os.path.join(self._project.gitdir, HEAD)
2443 else:
2444 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002445 try:
2446 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002447 except IOError as e:
2448 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002449 try:
2450 line = fd.read()
2451 finally:
2452 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302453 try:
2454 line = line.decode()
2455 except AttributeError:
2456 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002457 if line.startswith('ref: '):
2458 return line[5:-1]
2459 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002460
2461 def SetHead(self, ref, message=None):
2462 cmdv = []
2463 if message is not None:
2464 cmdv.extend(['-m', message])
2465 cmdv.append(HEAD)
2466 cmdv.append(ref)
2467 self.symbolic_ref(*cmdv)
2468
2469 def DetachHead(self, new, message=None):
2470 cmdv = ['--no-deref']
2471 if message is not None:
2472 cmdv.extend(['-m', message])
2473 cmdv.append(HEAD)
2474 cmdv.append(new)
2475 self.update_ref(*cmdv)
2476
2477 def UpdateRef(self, name, new, old=None,
2478 message=None,
2479 detach=False):
2480 cmdv = []
2481 if message is not None:
2482 cmdv.extend(['-m', message])
2483 if detach:
2484 cmdv.append('--no-deref')
2485 cmdv.append(name)
2486 cmdv.append(new)
2487 if old is not None:
2488 cmdv.append(old)
2489 self.update_ref(*cmdv)
2490
2491 def DeleteRef(self, name, old=None):
2492 if not old:
2493 old = self.rev_parse(name)
2494 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002495 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002496
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002497 def rev_list(self, *args, **kw):
2498 if 'format' in kw:
2499 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2500 else:
2501 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002502 cmdv.extend(args)
2503 p = GitCommand(self._project,
2504 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002505 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002506 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002507 capture_stdout=True,
2508 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002509 r = []
2510 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002511 if line[-1] == '\n':
2512 line = line[:-1]
2513 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002514 if p.Wait() != 0:
2515 raise GitError('%s rev-list %s: %s' % (
2516 self._project.name,
2517 str(args),
2518 p.stderr))
2519 return r
2520
2521 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002522 """Allow arbitrary git commands using pythonic syntax.
2523
2524 This allows you to do things like:
2525 git_obj.rev_parse('HEAD')
2526
2527 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2528 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002529 Any other positional arguments will be passed to the git command, and the
2530 following keyword arguments are supported:
2531 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002532
2533 Args:
2534 name: The name of the git command to call. Any '_' characters will
2535 be replaced with '-'.
2536
2537 Returns:
2538 A callable object that will try to call git with the named command.
2539 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002540 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002541 def runner(*args, **kwargs):
2542 cmdv = []
2543 config = kwargs.pop('config', None)
2544 for k in kwargs:
2545 raise TypeError('%s() got an unexpected keyword argument %r'
2546 % (name, k))
2547 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002548 if not git_require((1, 7, 2)):
2549 raise ValueError('cannot set config on command line for %s()'
2550 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302551 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002552 cmdv.append('-c')
2553 cmdv.append('%s=%s' % (k, v))
2554 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002555 cmdv.extend(args)
2556 p = GitCommand(self._project,
2557 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002558 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002559 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002560 capture_stdout=True,
2561 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002562 if p.Wait() != 0:
2563 raise GitError('%s %s: %s' % (
2564 self._project.name,
2565 name,
2566 p.stderr))
2567 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302568 try:
Conley Owensedd01512013-09-26 12:59:58 -07002569 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302570 except AttributeError:
2571 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002572 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2573 return r[:-1]
2574 return r
2575 return runner
2576
2577
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002578class _PriorSyncFailedError(Exception):
2579 def __str__(self):
2580 return 'prior sync failed; rebase still in progress'
2581
2582class _DirtyError(Exception):
2583 def __str__(self):
2584 return 'contains uncommitted changes'
2585
2586class _InfoMessage(object):
2587 def __init__(self, project, text):
2588 self.project = project
2589 self.text = text
2590
2591 def Print(self, syncbuf):
2592 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2593 syncbuf.out.nl()
2594
2595class _Failure(object):
2596 def __init__(self, project, why):
2597 self.project = project
2598 self.why = why
2599
2600 def Print(self, syncbuf):
2601 syncbuf.out.fail('error: %s/: %s',
2602 self.project.relpath,
2603 str(self.why))
2604 syncbuf.out.nl()
2605
2606class _Later(object):
2607 def __init__(self, project, action):
2608 self.project = project
2609 self.action = action
2610
2611 def Run(self, syncbuf):
2612 out = syncbuf.out
2613 out.project('project %s/', self.project.relpath)
2614 out.nl()
2615 try:
2616 self.action()
2617 out.nl()
2618 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002619 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002620 out.nl()
2621 return False
2622
2623class _SyncColoring(Coloring):
2624 def __init__(self, config):
2625 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002626 self.project = self.printer('header', attr='bold')
2627 self.info = self.printer('info')
2628 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002629
2630class SyncBuffer(object):
2631 def __init__(self, config, detach_head=False):
2632 self._messages = []
2633 self._failures = []
2634 self._later_queue1 = []
2635 self._later_queue2 = []
2636
2637 self.out = _SyncColoring(config)
2638 self.out.redirect(sys.stderr)
2639
2640 self.detach_head = detach_head
2641 self.clean = True
2642
2643 def info(self, project, fmt, *args):
2644 self._messages.append(_InfoMessage(project, fmt % args))
2645
2646 def fail(self, project, err=None):
2647 self._failures.append(_Failure(project, err))
2648 self.clean = False
2649
2650 def later1(self, project, what):
2651 self._later_queue1.append(_Later(project, what))
2652
2653 def later2(self, project, what):
2654 self._later_queue2.append(_Later(project, what))
2655
2656 def Finish(self):
2657 self._PrintMessages()
2658 self._RunLater()
2659 self._PrintMessages()
2660 return self.clean
2661
2662 def _RunLater(self):
2663 for q in ['_later_queue1', '_later_queue2']:
2664 if not self._RunQueue(q):
2665 return
2666
2667 def _RunQueue(self, queue):
2668 for m in getattr(self, queue):
2669 if not m.Run(self):
2670 self.clean = False
2671 return False
2672 setattr(self, queue, [])
2673 return True
2674
2675 def _PrintMessages(self):
2676 for m in self._messages:
2677 m.Print(self)
2678 for m in self._failures:
2679 m.Print(self)
2680
2681 self._messages = []
2682 self._failures = []
2683
2684
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002685class MetaProject(Project):
2686 """A special project housed under .repo.
2687 """
2688 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002689 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002690 manifest=manifest,
2691 name=name,
2692 gitdir=gitdir,
2693 objdir=gitdir,
2694 worktree=worktree,
2695 remote=RemoteSpec('origin'),
2696 relpath='.repo/%s' % name,
2697 revisionExpr='refs/heads/master',
2698 revisionId=None,
2699 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002700
2701 def PreSync(self):
2702 if self.Exists:
2703 cb = self.CurrentBranch
2704 if cb:
2705 base = self.GetBranch(cb).merge
2706 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002707 self.revisionExpr = base
2708 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002709
Anthony King7bdac712014-07-16 12:56:40 +01002710 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002711 """ Prepare MetaProject for manifest branch switch
2712 """
2713
2714 # detach and delete manifest branch, allowing a new
2715 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002716 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002717 self.Sync_LocalHalf(syncbuf)
2718 syncbuf.Finish()
2719
2720 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002721 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002722 capture_stdout=True,
2723 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002724
2725
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002726 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002727 def LastFetch(self):
2728 try:
2729 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2730 return os.path.getmtime(fh)
2731 except OSError:
2732 return 0
2733
2734 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002735 def HasChanges(self):
2736 """Has the remote received new commits not yet checked out?
2737 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002738 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002739 return False
2740
David Pursehouse8a68ff92012-09-24 12:15:13 +09002741 all_refs = self.bare_ref.all
2742 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002743 head = self.work_git.GetHead()
2744 if head.startswith(R_HEADS):
2745 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002746 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002747 except KeyError:
2748 head = None
2749
2750 if revid == head:
2751 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002752 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002753 return True
2754 return False