blob: 24cac8bd0e772afaaabe1cbf374e97cd75766ee3 [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
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070018import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import 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
Dan Willemsen0745bb22015-08-17 13:41:45 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070034from error import GitError, HookError, UploadError, DownloadError
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):
Wink Saville4c426ef2015-06-03 08:05:17 -0700236 def __init__(self, git_worktree, src, dest, relsrc, absdest):
237 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500238 self.src = src
239 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700240 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500241 self.abs_dest = absdest
242
Wink Saville4c426ef2015-06-03 08:05:17 -0700243 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500244 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700245 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500246 try:
247 # remove existing file first, since it might be read-only
Wink Saville4c426ef2015-06-03 08:05:17 -0700248 if os.path.exists(absDest):
249 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500250 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700251 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500252 if not os.path.isdir(dest_dir):
253 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700254 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500255 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700256 _error('Cannot link file %s to %s', relSrc, absDest)
257
258 def _Link(self):
259 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
260 on the src linking all of the files in the source in to the destination
261 directory.
262 """
263 # We use the absSrc to handle the situation where the current directory
264 # is not the root of the repo
265 absSrc = os.path.join(self.git_worktree, self.src)
266 if os.path.exists(absSrc):
267 # Entity exists so just a simple one to one link operation
268 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
269 else:
270 # Entity doesn't exist assume there is a wild card
271 absDestDir = self.abs_dest
272 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
273 _error('Link error: src with wildcard, %s must be a directory',
274 absDestDir)
275 else:
276 absSrcFiles = glob.glob(absSrc)
277 for absSrcFile in absSrcFiles:
278 # Create a releative path from source dir to destination dir
279 absSrcDir = os.path.dirname(absSrcFile)
280 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
281
282 # Get the source file name
283 srcFile = os.path.basename(absSrcFile)
284
285 # Now form the final full paths to srcFile. They will be
286 # absolute for the desintaiton and relative for the srouce.
287 absDest = os.path.join(absDestDir, srcFile)
288 relSrc = os.path.join(relSrcDir, srcFile)
289 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500290
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700291class RemoteSpec(object):
292 def __init__(self,
293 name,
Anthony King7bdac712014-07-16 12:56:40 +0100294 url=None,
295 review=None,
296 revision=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700297 self.name = name
298 self.url = url
299 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100300 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700301
Doug Anderson37282b42011-03-04 11:54:18 -0800302class RepoHook(object):
303 """A RepoHook contains information about a script to run as a hook.
304
305 Hooks are used to run a python script before running an upload (for instance,
306 to run presubmit checks). Eventually, we may have hooks for other actions.
307
308 This shouldn't be confused with files in the 'repo/hooks' directory. Those
309 files are copied into each '.git/hooks' folder for each project. Repo-level
310 hooks are associated instead with repo actions.
311
312 Hooks are always python. When a hook is run, we will load the hook into the
313 interpreter and execute its main() function.
314 """
315 def __init__(self,
316 hook_type,
317 hooks_project,
318 topdir,
319 abort_if_user_denies=False):
320 """RepoHook constructor.
321
322 Params:
323 hook_type: A string representing the type of hook. This is also used
324 to figure out the name of the file containing the hook. For
325 example: 'pre-upload'.
326 hooks_project: The project containing the repo hooks. If you have a
327 manifest, this is manifest.repo_hooks_project. OK if this is None,
328 which will make the hook a no-op.
329 topdir: Repo's top directory (the one containing the .repo directory).
330 Scripts will run with CWD as this directory. If you have a manifest,
331 this is manifest.topdir
332 abort_if_user_denies: If True, we'll throw a HookError() if the user
333 doesn't allow us to run the hook.
334 """
335 self._hook_type = hook_type
336 self._hooks_project = hooks_project
337 self._topdir = topdir
338 self._abort_if_user_denies = abort_if_user_denies
339
340 # Store the full path to the script for convenience.
341 if self._hooks_project:
342 self._script_fullpath = os.path.join(self._hooks_project.worktree,
343 self._hook_type + '.py')
344 else:
345 self._script_fullpath = None
346
347 def _GetHash(self):
348 """Return a hash of the contents of the hooks directory.
349
350 We'll just use git to do this. This hash has the property that if anything
351 changes in the directory we will return a different has.
352
353 SECURITY CONSIDERATION:
354 This hash only represents the contents of files in the hook directory, not
355 any other files imported or called by hooks. Changes to imported files
356 can change the script behavior without affecting the hash.
357
358 Returns:
359 A string representing the hash. This will always be ASCII so that it can
360 be printed to the user easily.
361 """
362 assert self._hooks_project, "Must have hooks to calculate their hash."
363
364 # We will use the work_git object rather than just calling GetRevisionId().
365 # That gives us a hash of the latest checked in version of the files that
366 # the user will actually be executing. Specifically, GetRevisionId()
367 # doesn't appear to change even if a user checks out a different version
368 # of the hooks repo (via git checkout) nor if a user commits their own revs.
369 #
370 # NOTE: Local (non-committed) changes will not be factored into this hash.
371 # I think this is OK, since we're really only worried about warning the user
372 # about upstream changes.
373 return self._hooks_project.work_git.rev_parse('HEAD')
374
375 def _GetMustVerb(self):
376 """Return 'must' if the hook is required; 'should' if not."""
377 if self._abort_if_user_denies:
378 return 'must'
379 else:
380 return 'should'
381
382 def _CheckForHookApproval(self):
383 """Check to see whether this hook has been approved.
384
385 We'll look at the hash of all of the hooks. If this matches the hash that
386 the user last approved, we're done. If it doesn't, we'll ask the user
387 about approval.
388
389 Note that we ask permission for each individual hook even though we use
390 the hash of all hooks when detecting changes. We'd like the user to be
391 able to approve / deny each hook individually. We only use the hash of all
392 hooks because there is no other easy way to detect changes to local imports.
393
394 Returns:
395 True if this hook is approved to run; False otherwise.
396
397 Raises:
398 HookError: Raised if the user doesn't approve and abort_if_user_denies
399 was passed to the consturctor.
400 """
Doug Anderson37282b42011-03-04 11:54:18 -0800401 hooks_config = self._hooks_project.config
402 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
403
404 # Get the last hash that the user approved for this hook; may be None.
405 old_hash = hooks_config.GetString(git_approval_key)
406
407 # Get the current hash so we can tell if scripts changed since approval.
408 new_hash = self._GetHash()
409
410 if old_hash is not None:
411 # User previously approved hook and asked not to be prompted again.
412 if new_hash == old_hash:
413 # Approval matched. We're done.
414 return True
415 else:
416 # Give the user a reason why we're prompting, since they last told
417 # us to "never ask again".
418 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
419 self._hook_type)
420 else:
421 prompt = ''
422
423 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
424 if sys.stdout.isatty():
425 prompt += ('Repo %s run the script:\n'
426 ' %s\n'
427 '\n'
428 'Do you want to allow this script to run '
429 '(yes/yes-never-ask-again/NO)? ') % (
430 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530431 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900432 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800433
434 # User is doing a one-time approval.
435 if response in ('y', 'yes'):
436 return True
437 elif response == 'yes-never-ask-again':
438 hooks_config.SetString(git_approval_key, new_hash)
439 return True
440
441 # For anything else, we'll assume no approval.
442 if self._abort_if_user_denies:
443 raise HookError('You must allow the %s hook or use --no-verify.' %
444 self._hook_type)
445
446 return False
447
448 def _ExecuteHook(self, **kwargs):
449 """Actually execute the given hook.
450
451 This will run the hook's 'main' function in our python interpreter.
452
453 Args:
454 kwargs: Keyword arguments to pass to the hook. These are often specific
455 to the hook type. For instance, pre-upload hooks will contain
456 a project_list.
457 """
458 # Keep sys.path and CWD stashed away so that we can always restore them
459 # upon function exit.
460 orig_path = os.getcwd()
461 orig_syspath = sys.path
462
463 try:
464 # Always run hooks with CWD as topdir.
465 os.chdir(self._topdir)
466
467 # Put the hook dir as the first item of sys.path so hooks can do
468 # relative imports. We want to replace the repo dir as [0] so
469 # hooks can't import repo files.
470 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
471
472 # Exec, storing global context in the context dict. We catch exceptions
473 # and convert to a HookError w/ just the failing traceback.
474 context = {}
475 try:
Anthony King70f68902014-05-05 21:15:34 +0100476 exec(compile(open(self._script_fullpath).read(),
477 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800478 except Exception:
479 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
480 traceback.format_exc(), self._hook_type))
481
482 # Running the script should have defined a main() function.
483 if 'main' not in context:
484 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
485
486
487 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
488 # We don't actually want hooks to define their main with this argument--
489 # it's there to remind them that their hook should always take **kwargs.
490 # For instance, a pre-upload hook should be defined like:
491 # def main(project_list, **kwargs):
492 #
493 # This allows us to later expand the API without breaking old hooks.
494 kwargs = kwargs.copy()
495 kwargs['hook_should_take_kwargs'] = True
496
497 # Call the main function in the hook. If the hook should cause the
498 # build to fail, it will raise an Exception. We'll catch that convert
499 # to a HookError w/ just the failing traceback.
500 try:
501 context['main'](**kwargs)
502 except Exception:
503 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
504 'above.' % (
505 traceback.format_exc(), self._hook_type))
506 finally:
507 # Restore sys.path and CWD.
508 sys.path = orig_syspath
509 os.chdir(orig_path)
510
511 def Run(self, user_allows_all_hooks, **kwargs):
512 """Run the hook.
513
514 If the hook doesn't exist (because there is no hooks project or because
515 this particular hook is not enabled), this is a no-op.
516
517 Args:
518 user_allows_all_hooks: If True, we will never prompt about running the
519 hook--we'll just assume it's OK to run it.
520 kwargs: Keyword arguments to pass to the hook. These are often specific
521 to the hook type. For instance, pre-upload hooks will contain
522 a project_list.
523
524 Raises:
525 HookError: If there was a problem finding the hook or the user declined
526 to run a required hook (from _CheckForHookApproval).
527 """
528 # No-op if there is no hooks project or if hook is disabled.
529 if ((not self._hooks_project) or
530 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
531 return
532
533 # Bail with a nice error if we can't find the hook.
534 if not os.path.isfile(self._script_fullpath):
535 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
536
537 # Make sure the user is OK with running the hook.
538 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
539 return
540
541 # Run the hook with the same version of python we're using.
542 self._ExecuteHook(**kwargs)
543
544
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700545class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600546 # These objects can be shared between several working trees.
547 shareable_files = ['description', 'info']
548 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
549 # These objects can only be used by a single working tree.
550 working_tree_files = ['config', 'packed-refs', 'shallow']
551 working_tree_dirs = ['logs', 'refs']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552 def __init__(self,
553 manifest,
554 name,
555 remote,
556 gitdir,
David James8d201162013-10-11 17:03:19 -0700557 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700558 worktree,
559 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700560 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800561 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100562 rebase=True,
563 groups=None,
564 sync_c=False,
565 sync_s=False,
566 clone_depth=None,
567 upstream=None,
568 parent=None,
569 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900570 dest_branch=None,
571 optimized_fetch=False):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800572 """Init a Project object.
573
574 Args:
575 manifest: The XmlManifest object.
576 name: The `name` attribute of manifest.xml's project element.
577 remote: RemoteSpec object specifying its remote's properties.
578 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700579 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800580 worktree: Absolute path of git working tree.
581 relpath: Relative path of git working tree to repo's top directory.
582 revisionExpr: The `revision` attribute of manifest.xml's project element.
583 revisionId: git commit id for checking out.
584 rebase: The `rebase` attribute of manifest.xml's project element.
585 groups: The `groups` attribute of manifest.xml's project element.
586 sync_c: The `sync-c` attribute of manifest.xml's project element.
587 sync_s: The `sync-s` attribute of manifest.xml's project element.
588 upstream: The `upstream` attribute of manifest.xml's project element.
589 parent: The parent Project object.
590 is_derived: False if the project was explicitly defined in the manifest;
591 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400592 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900593 optimized_fetch: If True, when a project is set to a sha1 revision, only
594 fetch from the remote if the sha1 is not present locally.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800595 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700596 self.manifest = manifest
597 self.name = name
598 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800599 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700600 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800601 if worktree:
602 self.worktree = worktree.replace('\\', '/')
603 else:
604 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700605 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700606 self.revisionExpr = revisionExpr
607
608 if revisionId is None \
609 and revisionExpr \
610 and IsId(revisionExpr):
611 self.revisionId = revisionExpr
612 else:
613 self.revisionId = revisionId
614
Mike Pontillod3153822012-02-28 11:53:24 -0800615 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700616 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700617 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800618 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900619 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700620 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800621 self.parent = parent
622 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900623 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800624 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800625
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700626 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700627 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500628 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500629 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700630 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100631 gitdir=self.gitdir,
632 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700633
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800634 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700635 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800636 else:
637 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700638 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700639 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700640 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400641 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700642
Doug Anderson37282b42011-03-04 11:54:18 -0800643 # This will be filled in if a project is later identified to be the
644 # project containing repo hooks.
645 self.enabled_repo_hooks = []
646
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700647 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800648 def Derived(self):
649 return self.is_derived
650
651 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700652 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600653 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654
655 @property
656 def CurrentBranch(self):
657 """Obtain the name of the currently checked out branch.
658 The branch name omits the 'refs/heads/' prefix.
659 None is returned if the project is on a detached HEAD.
660 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700661 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700662 if b.startswith(R_HEADS):
663 return b[len(R_HEADS):]
664 return None
665
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700666 def IsRebaseInProgress(self):
667 w = self.worktree
668 g = os.path.join(w, '.git')
669 return os.path.exists(os.path.join(g, 'rebase-apply')) \
670 or os.path.exists(os.path.join(g, 'rebase-merge')) \
671 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200672
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700673 def IsDirty(self, consider_untracked=True):
674 """Is the working directory modified in some way?
675 """
676 self.work_git.update_index('-q',
677 '--unmerged',
678 '--ignore-missing',
679 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900680 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700681 return True
682 if self.work_git.DiffZ('diff-files'):
683 return True
684 if consider_untracked and self.work_git.LsOthers():
685 return True
686 return False
687
688 _userident_name = None
689 _userident_email = None
690
691 @property
692 def UserName(self):
693 """Obtain the user's personal name.
694 """
695 if self._userident_name is None:
696 self._LoadUserIdentity()
697 return self._userident_name
698
699 @property
700 def UserEmail(self):
701 """Obtain the user's email address. This is very likely
702 to be their Gerrit login.
703 """
704 if self._userident_email is None:
705 self._LoadUserIdentity()
706 return self._userident_email
707
708 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900709 u = self.bare_git.var('GIT_COMMITTER_IDENT')
710 m = re.compile("^(.*) <([^>]*)> ").match(u)
711 if m:
712 self._userident_name = m.group(1)
713 self._userident_email = m.group(2)
714 else:
715 self._userident_name = ''
716 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717
718 def GetRemote(self, name):
719 """Get the configuration for a single remote.
720 """
721 return self.config.GetRemote(name)
722
723 def GetBranch(self, name):
724 """Get the configuration for a single branch.
725 """
726 return self.config.GetBranch(name)
727
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700728 def GetBranches(self):
729 """Get all existing local branches.
730 """
731 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900732 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700733 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700734
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530735 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700736 if name.startswith(R_HEADS):
737 name = name[len(R_HEADS):]
738 b = self.GetBranch(name)
739 b.current = name == current
740 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900741 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700742 heads[name] = b
743
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530744 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700745 if name.startswith(R_PUB):
746 name = name[len(R_PUB):]
747 b = heads.get(name)
748 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900749 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700750
751 return heads
752
Colin Cross5acde752012-03-28 20:15:45 -0700753 def MatchesGroups(self, manifest_groups):
754 """Returns true if the manifest groups specified at init should cause
755 this project to be synced.
756 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700757 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700758
759 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700760 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700761 manifest_groups: "-group1,group2"
762 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500763
764 The special manifest group "default" will match any project that
765 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700766 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500767 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700768 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500769 if not 'notdefault' in expanded_project_groups:
770 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700771
Conley Owens971de8e2012-04-16 10:36:08 -0700772 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700773 for group in expanded_manifest_groups:
774 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700775 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700776 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700777 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700778
Conley Owens971de8e2012-04-16 10:36:08 -0700779 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780
781## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700782 def UncommitedFiles(self, get_all=True):
783 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700785 Args:
786 get_all: a boolean, if True - get information about all different
787 uncommitted files. If False - return as soon as any kind of
788 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500789 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700790 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500791 self.work_git.update_index('-q',
792 '--unmerged',
793 '--ignore-missing',
794 '--refresh')
795 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700796 details.append("rebase in progress")
797 if not get_all:
798 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500799
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700800 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
801 if changes:
802 details.extend(changes)
803 if not get_all:
804 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500805
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700806 changes = self.work_git.DiffZ('diff-files').keys()
807 if changes:
808 details.extend(changes)
809 if not get_all:
810 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500811
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700812 changes = self.work_git.LsOthers()
813 if changes:
814 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500815
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700816 return details
817
818 def HasChanges(self):
819 """Returns true if there are uncommitted changes.
820 """
821 if self.UncommitedFiles(get_all=False):
822 return True
823 else:
824 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500825
Terence Haddock4655e812011-03-31 12:33:34 +0200826 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700827 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200828
829 Args:
830 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700831 """
832 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200833 if output_redir == None:
834 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700835 print(file=output_redir)
836 print('project %s/' % self.relpath, file=output_redir)
837 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 return
839
840 self.work_git.update_index('-q',
841 '--unmerged',
842 '--ignore-missing',
843 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700844 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
846 df = self.work_git.DiffZ('diff-files')
847 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100848 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700849 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700850
851 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200852 if not output_redir == None:
853 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700854 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700855
856 branch = self.CurrentBranch
857 if branch is None:
858 out.nobranch('(*** NO BRANCH ***)')
859 else:
860 out.branch('branch %s', branch)
861 out.nl()
862
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700863 if rb:
864 out.important('prior sync failed; rebase still in progress')
865 out.nl()
866
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867 paths = list()
868 paths.extend(di.keys())
869 paths.extend(df.keys())
870 paths.extend(do)
871
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530872 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900873 try:
874 i = di[p]
875 except KeyError:
876 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900878 try:
879 f = df[p]
880 except KeyError:
881 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200882
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900883 if i:
884 i_status = i.status.upper()
885 else:
886 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700887
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900888 if f:
889 f_status = f.status.lower()
890 else:
891 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892
893 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800894 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700895 i.src_path, p, i.level)
896 else:
897 line = ' %s%s\t%s' % (i_status, f_status, p)
898
899 if i and not f:
900 out.added('%s', line)
901 elif (i and f) or (not i and f):
902 out.changed('%s', line)
903 elif not i and not f:
904 out.untracked('%s', line)
905 else:
906 out.write('%s', line)
907 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200908
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700909 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700910
pelyad67872d2012-03-28 14:49:58 +0300911 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700912 """Prints the status of the repository to stdout.
913 """
914 out = DiffColoring(self.config)
915 cmd = ['diff']
916 if out.is_on:
917 cmd.append('--color')
918 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300919 if absolute_paths:
920 cmd.append('--src-prefix=a/%s/' % self.relpath)
921 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 cmd.append('--')
923 p = GitCommand(self,
924 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100925 capture_stdout=True,
926 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 has_diff = False
928 for line in p.process.stdout:
929 if not has_diff:
930 out.nl()
931 out.project('project %s/' % self.relpath)
932 out.nl()
933 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700934 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700935 p.Wait()
936
937
938## Publish / Upload ##
939
David Pursehouse8a68ff92012-09-24 12:15:13 +0900940 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941 """Was the branch published (uploaded) for code review?
942 If so, returns the SHA-1 hash of the last published
943 state for the branch.
944 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700945 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900946 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700947 try:
948 return self.bare_git.rev_parse(key)
949 except GitError:
950 return None
951 else:
952 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900953 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700954 except KeyError:
955 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700956
David Pursehouse8a68ff92012-09-24 12:15:13 +0900957 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958 """Prunes any stale published refs.
959 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900960 if all_refs is None:
961 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 heads = set()
963 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530964 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700965 if name.startswith(R_HEADS):
966 heads.add(name)
967 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900968 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530970 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700971 n = name[len(R_PUB):]
972 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900973 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700974
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700975 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700976 """List any branches which can be uploaded for review.
977 """
978 heads = {}
979 pubed = {}
980
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530981 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900983 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900985 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986
987 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530988 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900989 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700991 if selected_branch and branch != selected_branch:
992 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700993
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800994 rb = self.GetUploadableBranch(branch)
995 if rb:
996 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997 return ready
998
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800999 def GetUploadableBranch(self, branch_name):
1000 """Get a single uploadable branch, or None.
1001 """
1002 branch = self.GetBranch(branch_name)
1003 base = branch.LocalMerge
1004 if branch.LocalMerge:
1005 rb = ReviewableBranch(self, branch, base)
1006 if rb.commits:
1007 return rb
1008 return None
1009
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001010 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001011 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001012 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001013 draft=False,
1014 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015 """Uploads the named branch for code review.
1016 """
1017 if branch is None:
1018 branch = self.CurrentBranch
1019 if branch is None:
1020 raise GitError('not currently on a branch')
1021
1022 branch = self.GetBranch(branch)
1023 if not branch.LocalMerge:
1024 raise GitError('branch %s does not track a remote' % branch.name)
1025 if not branch.remote.review:
1026 raise GitError('remote %s has no review url' % branch.remote.name)
1027
Bryan Jacobsf609f912013-05-06 13:36:24 -04001028 if dest_branch is None:
1029 dest_branch = self.dest_branch
1030 if dest_branch is None:
1031 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001032 if not dest_branch.startswith(R_HEADS):
1033 dest_branch = R_HEADS + dest_branch
1034
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001035 if not branch.remote.projectname:
1036 branch.remote.projectname = self.name
1037 branch.remote.Save()
1038
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001039 url = branch.remote.ReviewUrl(self.UserEmail)
1040 if url is None:
1041 raise UploadError('review not configured')
1042 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001043
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001044 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001045 rp = ['gerrit receive-pack']
1046 for e in people[0]:
1047 rp.append('--reviewer=%s' % sq(e))
1048 for e in people[1]:
1049 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001050 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001051
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001052 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001053
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001054 if dest_branch.startswith(R_HEADS):
1055 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001056
1057 upload_type = 'for'
1058 if draft:
1059 upload_type = 'drafts'
1060
1061 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1062 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001063 if auto_topic:
1064 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001065 if not url.startswith('ssh://'):
1066 rp = ['r=%s' % p for p in people[0]] + \
1067 ['cc=%s' % p for p in people[1]]
1068 if rp:
1069 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001070 cmd.append(ref_spec)
1071
Anthony King7bdac712014-07-16 12:56:40 +01001072 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001073 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074
1075 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1076 self.bare_git.UpdateRef(R_PUB + branch.name,
1077 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001078 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079
1080
1081## Sync ##
1082
Julien Campergue335f5ef2013-10-16 11:02:35 +02001083 def _ExtractArchive(self, tarpath, path=None):
1084 """Extract the given tar on its current location
1085
1086 Args:
1087 - tarpath: The path to the actual tar file
1088
1089 """
1090 try:
1091 with tarfile.open(tarpath, 'r') as tar:
1092 tar.extractall(path=path)
1093 return True
1094 except (IOError, tarfile.TarError) as e:
1095 print("error: Cannot extract archive %s: "
1096 "%s" % (tarpath, str(e)), file=sys.stderr)
1097 return False
1098
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001099 def Sync_NetworkHalf(self,
1100 quiet=False,
1101 is_new=None,
1102 current_branch_only=False,
Kevin Degiabaa7f32014-11-12 11:27:45 -07001103 force_sync=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001104 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001105 no_tags=False,
David Pursehouseb1553542014-09-04 21:28:09 +09001106 archive=False,
1107 optimized_fetch=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108 """Perform only the network IO portion of the sync process.
1109 Local working directory/branch state is not affected.
1110 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001111 if archive and not isinstance(self, MetaProject):
1112 if self.remote.url.startswith(('http://', 'https://')):
1113 print("error: %s: Cannot fetch archives from http/https "
1114 "remotes." % self.name, file=sys.stderr)
1115 return False
1116
1117 name = self.relpath.replace('\\', '/')
1118 name = name.replace('/', '_')
1119 tarpath = '%s.tar' % name
1120 topdir = self.manifest.topdir
1121
1122 try:
1123 self._FetchArchive(tarpath, cwd=topdir)
1124 except GitError as e:
1125 print('error: %s' % str(e), file=sys.stderr)
1126 return False
1127
1128 # From now on, we only need absolute tarpath
1129 tarpath = os.path.join(topdir, tarpath)
1130
1131 if not self._ExtractArchive(tarpath, path=topdir):
1132 return False
1133 try:
1134 os.remove(tarpath)
1135 except OSError as e:
1136 print("warn: Cannot remove archive %s: "
1137 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001138 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001139 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001140 if is_new is None:
1141 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001142 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001143 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001144 else:
1145 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001147
1148 if is_new:
1149 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1150 try:
1151 fd = open(alt, 'rb')
1152 try:
1153 alt_dir = fd.readline().rstrip()
1154 finally:
1155 fd.close()
1156 except IOError:
1157 alt_dir = None
1158 else:
1159 alt_dir = None
1160
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001161 if clone_bundle \
1162 and alt_dir is None \
1163 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001164 is_new = False
1165
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001166 if not current_branch_only:
1167 if self.sync_c:
1168 current_branch_only = True
1169 elif not self.manifest._loaded:
1170 # Manifest cannot check defaults until it syncs.
1171 current_branch_only = False
1172 elif self.manifest.default.sync_c:
1173 current_branch_only = True
1174
David Pursehouseb1553542014-09-04 21:28:09 +09001175 need_to_fetch = not (optimized_fetch and \
1176 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1177 if (need_to_fetch
Conley Owens666d5342014-05-01 13:09:57 -07001178 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1179 current_branch_only=current_branch_only,
1180 no_tags=no_tags)):
Anthony King7bdac712014-07-16 12:56:40 +01001181 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001182
1183 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001184 self._InitMRef()
1185 else:
1186 self._InitMirrorHead()
1187 try:
1188 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1189 except OSError:
1190 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001191 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001192
1193 def PostRepoUpgrade(self):
1194 self._InitHooks()
1195
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001196 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001197 for copyfile in self.copyfiles:
1198 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001199 for linkfile in self.linkfiles:
1200 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201
Julien Camperguedd654222014-01-09 16:21:37 +01001202 def GetCommitRevisionId(self):
1203 """Get revisionId of a commit.
1204
1205 Use this method instead of GetRevisionId to get the id of the commit rather
1206 than the id of the current git object (for example, a tag)
1207
1208 """
1209 if not self.revisionExpr.startswith(R_TAGS):
1210 return self.GetRevisionId(self._allrefs)
1211
1212 try:
1213 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1214 except GitError:
1215 raise ManifestInvalidRevisionError(
1216 'revision %s in %s not found' % (self.revisionExpr,
1217 self.name))
1218
David Pursehouse8a68ff92012-09-24 12:15:13 +09001219 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001220 if self.revisionId:
1221 return self.revisionId
1222
1223 rem = self.GetRemote(self.remote.name)
1224 rev = rem.ToLocal(self.revisionExpr)
1225
David Pursehouse8a68ff92012-09-24 12:15:13 +09001226 if all_refs is not None and rev in all_refs:
1227 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001228
1229 try:
1230 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1231 except GitError:
1232 raise ManifestInvalidRevisionError(
1233 'revision %s in %s not found' % (self.revisionExpr,
1234 self.name))
1235
Kevin Degiabaa7f32014-11-12 11:27:45 -07001236 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001237 """Perform only the local IO portion of the sync process.
1238 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001239 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001240 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001241 all_refs = self.bare_ref.all
1242 self.CleanPublishedCache(all_refs)
1243 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001244
David Pursehouse1d947b32012-10-25 12:23:11 +09001245 def _doff():
1246 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001247 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001248
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001249 head = self.work_git.GetHead()
1250 if head.startswith(R_HEADS):
1251 branch = head[len(R_HEADS):]
1252 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001253 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001254 except KeyError:
1255 head = None
1256 else:
1257 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001259 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001260 # Currently on a detached HEAD. The user is assumed to
1261 # not have any local modifications worth worrying about.
1262 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001263 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001264 syncbuf.fail(self, _PriorSyncFailedError())
1265 return
1266
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001267 if head == revid:
1268 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001269 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001270 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001271 if not syncbuf.detach_head:
1272 return
1273 else:
1274 lost = self._revlist(not_rev(revid), HEAD)
1275 if lost:
1276 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001277
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001278 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001279 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001280 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001281 syncbuf.fail(self, e)
1282 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001283 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001284 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001285
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001286 if head == revid:
1287 # No changes; don't do anything further.
1288 #
1289 return
1290
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001291 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001293 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001295 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001297 syncbuf.info(self,
1298 "leaving %s; does not track upstream",
1299 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001301 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001302 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001303 syncbuf.fail(self, e)
1304 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001305 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001306 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001308 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001309 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001311 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001312 if not_merged:
1313 if upstream_gain:
1314 # The user has published this branch and some of those
1315 # commits are not yet merged upstream. We do not want
1316 # to rewrite the published commits so we punt.
1317 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001318 syncbuf.fail(self,
1319 "branch %s is published (but not merged) and is now %d commits behind"
1320 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001321 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001322 elif pub == head:
1323 # All published commits are merged, and thus we are a
1324 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001325 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001326 syncbuf.later1(self, _doff)
1327 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001329 # Examine the local commits not in the remote. Find the
1330 # last one attributed to this user, if any.
1331 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001332 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001333 last_mine = None
1334 cnt_mine = 0
1335 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301336 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001337 if committer_email == self.UserEmail:
1338 last_mine = commit_id
1339 cnt_mine += 1
1340
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001341 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001342 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001343
1344 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001345 syncbuf.fail(self, _DirtyError())
1346 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001347
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001348 # If the upstream switched on us, warn the user.
1349 #
1350 if branch.merge != self.revisionExpr:
1351 if branch.merge and self.revisionExpr:
1352 syncbuf.info(self,
1353 'manifest switched %s...%s',
1354 branch.merge,
1355 self.revisionExpr)
1356 elif branch.merge:
1357 syncbuf.info(self,
1358 'manifest no longer tracks %s',
1359 branch.merge)
1360
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001361 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001362 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001363 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001365 syncbuf.info(self,
1366 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001367 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001369 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001370 if not ID_RE.match(self.revisionExpr):
1371 # in case of manifest sync the revisionExpr might be a SHA1
1372 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001373 if not branch.merge.startswith('refs/'):
1374 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001375 branch.Save()
1376
Mike Pontillod3153822012-02-28 11:53:24 -08001377 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001378 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001379 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001380 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001381 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001382 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001383 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001384 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001385 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001386 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001387 syncbuf.fail(self, e)
1388 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001390 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001392 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393 # dest should already be an absolute path, but src is project relative
1394 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001395 abssrc = os.path.join(self.worktree, src)
1396 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001397
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001398 def AddLinkFile(self, src, dest, absdest):
1399 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001400 # make src relative path to dest
1401 absdestdir = os.path.dirname(absdest)
1402 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001403 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001404
James W. Mills24c13082012-04-12 15:04:13 -05001405 def AddAnnotation(self, name, value, keep):
1406 self.annotations.append(_Annotation(name, value, keep))
1407
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001408 def DownloadPatchSet(self, change_id, patch_id):
1409 """Download a single patch set of a single change to FETCH_HEAD.
1410 """
1411 remote = self.GetRemote(self.remote.name)
1412
1413 cmd = ['fetch', remote.name]
1414 cmd.append('refs/changes/%2.2d/%d/%d' \
1415 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001416 if GitCommand(self, cmd, bare=True).Wait() != 0:
1417 return None
1418 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001419 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001420 change_id,
1421 patch_id,
1422 self.bare_git.rev_parse('FETCH_HEAD'))
1423
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001424
1425## Branch Management ##
1426
1427 def StartBranch(self, name):
1428 """Create a new branch off the manifest's revision.
1429 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001430 head = self.work_git.GetHead()
1431 if head == (R_HEADS + name):
1432 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001433
David Pursehouse8a68ff92012-09-24 12:15:13 +09001434 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001435 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001436 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001437 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001438 capture_stdout=True,
1439 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001440
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001441 branch = self.GetBranch(name)
1442 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001443 branch.merge = self.revisionExpr
Alexandre Boeglin38258272015-04-30 14:50:33 +02001444 if not branch.merge.startswith('refs/') and not ID_RE.match(self.revisionExpr):
Conley Owens04f2f0e2014-10-01 17:22:46 -07001445 branch.merge = R_HEADS + self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001446 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001447
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001448 if head.startswith(R_HEADS):
1449 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001450 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001451 except KeyError:
1452 head = None
1453
1454 if revid and head and revid == head:
1455 ref = os.path.join(self.gitdir, R_HEADS + name)
1456 try:
1457 os.makedirs(os.path.dirname(ref))
1458 except OSError:
1459 pass
1460 _lwrite(ref, '%s\n' % revid)
1461 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1462 'ref: %s%s\n' % (R_HEADS, name))
1463 branch.Save()
1464 return True
1465
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001466 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001467 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001468 capture_stdout=True,
1469 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001470 branch.Save()
1471 return True
1472 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001473
Wink Saville02d79452009-04-10 13:01:24 -07001474 def CheckoutBranch(self, name):
1475 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001476
1477 Args:
1478 name: The name of the branch to checkout.
1479
1480 Returns:
1481 True if the checkout succeeded; False if it didn't; None if the branch
1482 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001483 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001484 rev = R_HEADS + name
1485 head = self.work_git.GetHead()
1486 if head == rev:
1487 # Already on the branch
1488 #
1489 return True
Wink Saville02d79452009-04-10 13:01:24 -07001490
David Pursehouse8a68ff92012-09-24 12:15:13 +09001491 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001492 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001493 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001494 except KeyError:
1495 # Branch does not exist in this project
1496 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001497 return None
Wink Saville02d79452009-04-10 13:01:24 -07001498
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001499 if head.startswith(R_HEADS):
1500 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001501 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001502 except KeyError:
1503 head = None
1504
1505 if head == revid:
1506 # Same revision; just update HEAD to point to the new
1507 # target branch, but otherwise take no other action.
1508 #
1509 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1510 'ref: %s%s\n' % (R_HEADS, name))
1511 return True
1512
1513 return GitCommand(self,
1514 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001515 capture_stdout=True,
1516 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001517
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001518 def AbandonBranch(self, name):
1519 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001520
1521 Args:
1522 name: The name of the branch to abandon.
1523
1524 Returns:
1525 True if the abandon succeeded; False if it didn't; None if the branch
1526 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001527 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001528 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001529 all_refs = self.bare_ref.all
1530 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001531 # Doesn't exist
1532 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001533
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001534 head = self.work_git.GetHead()
1535 if head == rev:
1536 # We can't destroy the branch while we are sitting
1537 # on it. Switch to a detached HEAD.
1538 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001539 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001540
David Pursehouse8a68ff92012-09-24 12:15:13 +09001541 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001542 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001543 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1544 '%s\n' % revid)
1545 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001546 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001547
1548 return GitCommand(self,
1549 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001550 capture_stdout=True,
1551 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001552
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001553 def PruneHeads(self):
1554 """Prune any topic branches already merged into upstream.
1555 """
1556 cb = self.CurrentBranch
1557 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001558 left = self._allrefs
1559 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001560 if name.startswith(R_HEADS):
1561 name = name[len(R_HEADS):]
1562 if cb is None or name != cb:
1563 kill.append(name)
1564
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001565 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001566 if cb is not None \
1567 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001568 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001569 self.work_git.DetachHead(HEAD)
1570 kill.append(cb)
1571
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001572 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001573 old = self.bare_git.GetHead()
1574 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001575 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1576
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001577 try:
1578 self.bare_git.DetachHead(rev)
1579
1580 b = ['branch', '-d']
1581 b.extend(kill)
1582 b = GitCommand(self, b, bare=True,
1583 capture_stdout=True,
1584 capture_stderr=True)
1585 b.Wait()
1586 finally:
1587 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001588 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001589
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001590 for branch in kill:
1591 if (R_HEADS + branch) not in left:
1592 self.CleanPublishedCache()
1593 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001594
1595 if cb and cb not in kill:
1596 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001597 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001598
1599 kept = []
1600 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001601 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001602 branch = self.GetBranch(branch)
1603 base = branch.LocalMerge
1604 if not base:
1605 base = rev
1606 kept.append(ReviewableBranch(self, branch, base))
1607 return kept
1608
1609
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001610## Submodule Management ##
1611
1612 def GetRegisteredSubprojects(self):
1613 result = []
1614 def rec(subprojects):
1615 if not subprojects:
1616 return
1617 result.extend(subprojects)
1618 for p in subprojects:
1619 rec(p.subprojects)
1620 rec(self.subprojects)
1621 return result
1622
1623 def _GetSubmodules(self):
1624 # Unfortunately we cannot call `git submodule status --recursive` here
1625 # because the working tree might not exist yet, and it cannot be used
1626 # without a working tree in its current implementation.
1627
1628 def get_submodules(gitdir, rev):
1629 # Parse .gitmodules for submodule sub_paths and sub_urls
1630 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1631 if not sub_paths:
1632 return []
1633 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1634 # revision of submodule repository
1635 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1636 submodules = []
1637 for sub_path, sub_url in zip(sub_paths, sub_urls):
1638 try:
1639 sub_rev = sub_revs[sub_path]
1640 except KeyError:
1641 # Ignore non-exist submodules
1642 continue
1643 submodules.append((sub_rev, sub_path, sub_url))
1644 return submodules
1645
1646 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1647 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1648 def parse_gitmodules(gitdir, rev):
1649 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1650 try:
Anthony King7bdac712014-07-16 12:56:40 +01001651 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1652 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001653 except GitError:
1654 return [], []
1655 if p.Wait() != 0:
1656 return [], []
1657
1658 gitmodules_lines = []
1659 fd, temp_gitmodules_path = tempfile.mkstemp()
1660 try:
1661 os.write(fd, p.stdout)
1662 os.close(fd)
1663 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001664 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1665 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001666 if p.Wait() != 0:
1667 return [], []
1668 gitmodules_lines = p.stdout.split('\n')
1669 except GitError:
1670 return [], []
1671 finally:
1672 os.remove(temp_gitmodules_path)
1673
1674 names = set()
1675 paths = {}
1676 urls = {}
1677 for line in gitmodules_lines:
1678 if not line:
1679 continue
1680 m = re_path.match(line)
1681 if m:
1682 names.add(m.group(1))
1683 paths[m.group(1)] = m.group(2)
1684 continue
1685 m = re_url.match(line)
1686 if m:
1687 names.add(m.group(1))
1688 urls[m.group(1)] = m.group(2)
1689 continue
1690 names = sorted(names)
1691 return ([paths.get(name, '') for name in names],
1692 [urls.get(name, '') for name in names])
1693
1694 def git_ls_tree(gitdir, rev, paths):
1695 cmd = ['ls-tree', rev, '--']
1696 cmd.extend(paths)
1697 try:
Anthony King7bdac712014-07-16 12:56:40 +01001698 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1699 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001700 except GitError:
1701 return []
1702 if p.Wait() != 0:
1703 return []
1704 objects = {}
1705 for line in p.stdout.split('\n'):
1706 if not line.strip():
1707 continue
1708 object_rev, object_path = line.split()[2:4]
1709 objects[object_path] = object_rev
1710 return objects
1711
1712 try:
1713 rev = self.GetRevisionId()
1714 except GitError:
1715 return []
1716 return get_submodules(self.gitdir, rev)
1717
1718 def GetDerivedSubprojects(self):
1719 result = []
1720 if not self.Exists:
1721 # If git repo does not exist yet, querying its submodules will
1722 # mess up its states; so return here.
1723 return result
1724 for rev, path, url in self._GetSubmodules():
1725 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001726 relpath, worktree, gitdir, objdir = \
1727 self.manifest.GetSubprojectPaths(self, name, path)
1728 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001729 if project:
1730 result.extend(project.GetDerivedSubprojects())
1731 continue
David James8d201162013-10-11 17:03:19 -07001732
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001733 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001734 url=url,
1735 review=self.remote.review,
1736 revision=self.remote.revision)
1737 subproject = Project(manifest=self.manifest,
1738 name=name,
1739 remote=remote,
1740 gitdir=gitdir,
1741 objdir=objdir,
1742 worktree=worktree,
1743 relpath=relpath,
1744 revisionExpr=self.revisionExpr,
1745 revisionId=rev,
1746 rebase=self.rebase,
1747 groups=self.groups,
1748 sync_c=self.sync_c,
1749 sync_s=self.sync_s,
1750 parent=self,
1751 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001752 result.append(subproject)
1753 result.extend(subproject.GetDerivedSubprojects())
1754 return result
1755
1756
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001757## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001758 def _CheckForSha1(self):
1759 try:
1760 # if revision (sha or tag) is not present then following function
1761 # throws an error.
1762 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1763 return True
1764 except GitError:
1765 # There is no such persistent revision. We have to fetch it.
1766 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001767
Julien Campergue335f5ef2013-10-16 11:02:35 +02001768 def _FetchArchive(self, tarpath, cwd=None):
1769 cmd = ['archive', '-v', '-o', tarpath]
1770 cmd.append('--remote=%s' % self.remote.url)
1771 cmd.append('--prefix=%s/' % self.relpath)
1772 cmd.append(self.revisionExpr)
1773
1774 command = GitCommand(self, cmd, cwd=cwd,
1775 capture_stdout=True,
1776 capture_stderr=True)
1777
1778 if command.Wait() != 0:
1779 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1780
Conley Owens80b87fe2014-05-09 17:13:44 -07001781
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001782 def _RemoteFetch(self, name=None,
1783 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001784 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001785 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001786 alt_dir=None,
1787 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001788
1789 is_sha1 = False
1790 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001791 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001792
David Pursehouse9bc422f2014-04-15 10:28:56 +09001793 # The depth should not be used when fetching to a mirror because
1794 # it will result in a shallow repository that cannot be cloned or
1795 # fetched from.
1796 if not self.manifest.IsMirror:
1797 if self.clone_depth:
1798 depth = self.clone_depth
1799 else:
1800 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001801 # The repo project should never be synced with partial depth
1802 if self.relpath == '.repo/repo':
1803 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001804
Shawn Pearce69e04d82014-01-29 12:48:54 -08001805 if depth:
1806 current_branch_only = True
1807
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001808 if ID_RE.match(self.revisionExpr) is not None:
1809 is_sha1 = True
1810
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001811 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001812 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001813 # this is a tag and its sha1 value should never change
1814 tag_name = self.revisionExpr[len(R_TAGS):]
1815
1816 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001817 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001818 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001819 if is_sha1 and not depth:
1820 # When syncing a specific commit and --depth is not set:
1821 # * if upstream is explicitly specified and is not a sha1, fetch only
1822 # upstream as users expect only upstream to be fetch.
1823 # Note: The commit might not be in upstream in which case the sync
1824 # will fail.
1825 # * otherwise, fetch all branches to make sure we end up with the
1826 # specific commit.
1827 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001828
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001829 if not name:
1830 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001831
1832 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001833 remote = self.GetRemote(name)
1834 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001835 ssh_proxy = True
1836
Shawn O. Pearce88443382010-10-08 10:02:09 +02001837 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001838 if alt_dir and 'objects' == os.path.basename(alt_dir):
1839 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001840 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1841 remote = self.GetRemote(name)
1842
David Pursehouse8a68ff92012-09-24 12:15:13 +09001843 all_refs = self.bare_ref.all
1844 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001845 tmp = set()
1846
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301847 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001848 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001849 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001850 all_refs[r] = ref_id
1851 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001852 continue
1853
David Pursehouse8a68ff92012-09-24 12:15:13 +09001854 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001855 continue
1856
David Pursehouse8a68ff92012-09-24 12:15:13 +09001857 r = 'refs/_alt/%s' % ref_id
1858 all_refs[r] = ref_id
1859 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001860 tmp.add(r)
1861
Shawn O. Pearce88443382010-10-08 10:02:09 +02001862 tmp_packed = ''
1863 old_packed = ''
1864
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301865 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001866 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001867 tmp_packed += line
1868 if r not in tmp:
1869 old_packed += line
1870
1871 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001872 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001873 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001874
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001875 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001876
Conley Owensf97e8382015-01-21 11:12:46 -08001877 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001878 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07001879 else:
1880 # If this repo has shallow objects, then we don't know which refs have
1881 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
1882 # do this with projects that don't have shallow objects, since it is less
1883 # efficient.
1884 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
1885 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07001886
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001887 if quiet:
1888 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001889 if not self.worktree:
1890 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001891 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001892
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001893 # If using depth then we should not get all the tags since they may
1894 # be outside of the depth.
1895 if no_tags or depth:
1896 cmd.append('--no-tags')
1897 else:
1898 cmd.append('--tags')
1899
Conley Owens80b87fe2014-05-09 17:13:44 -07001900 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001901 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001902 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001903 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001904 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001905 spec.append('tag')
1906 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001907
David Pursehouse403b64e2015-04-27 10:41:33 +09001908 if not self.manifest.IsMirror:
1909 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06001910 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001911 # Shallow checkout of a specific commit, fetch from that commit and not
1912 # the heads only as the commit might be deeper in the history.
1913 spec.append(branch)
1914 else:
1915 if is_sha1:
1916 branch = self.upstream
1917 if branch is not None and branch.strip():
1918 if not branch.startswith('refs/'):
1919 branch = R_HEADS + branch
1920 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001921 cmd.extend(spec)
1922
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001923 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001924 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001925 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001926 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001927 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001928 ok = True
1929 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001930 # If needed, run the 'git remote prune' the first time through the loop
1931 elif (not _i and
1932 "error:" in gitcmd.stderr and
1933 "git remote prune" in gitcmd.stderr):
1934 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001935 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001936 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001937 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001938 break
1939 continue
Brian Harring14a66742012-09-28 20:21:57 -07001940 elif current_branch_only and is_sha1 and ret == 128:
1941 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1942 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1943 # abort the optimization attempt and do a full sync.
1944 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001945 elif ret < 0:
1946 # Git died with a signal, exit immediately
1947 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001948 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001949
1950 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001951 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001952 if old_packed != '':
1953 _lwrite(packed_refs, old_packed)
1954 else:
1955 os.remove(packed_refs)
1956 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001957
1958 if is_sha1 and current_branch_only and self.upstream:
1959 # We just synced the upstream given branch; verify we
1960 # got what we wanted, else trigger a second run of all
1961 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001962 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06001963 if not depth:
1964 # Avoid infinite recursion when depth is True (since depth implies
1965 # current_branch_only)
1966 return self._RemoteFetch(name=name, current_branch_only=False,
1967 initial=False, quiet=quiet, alt_dir=alt_dir)
1968 if self.clone_depth:
1969 self.clone_depth = None
1970 return self._RemoteFetch(name=name, current_branch_only=current_branch_only,
1971 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07001972
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001973 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001974
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001975 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001976 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001977 return False
1978
1979 remote = self.GetRemote(self.remote.name)
1980 bundle_url = remote.url + '/clone.bundle'
1981 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001982 if GetSchemeFromUrl(bundle_url) not in (
1983 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001984 return False
1985
1986 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1987 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1988
1989 exist_dst = os.path.exists(bundle_dst)
1990 exist_tmp = os.path.exists(bundle_tmp)
1991
1992 if not initial and not exist_dst and not exist_tmp:
1993 return False
1994
1995 if not exist_dst:
1996 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1997 if not exist_dst:
1998 return False
1999
2000 cmd = ['fetch']
2001 if quiet:
2002 cmd.append('--quiet')
2003 if not self.worktree:
2004 cmd.append('--update-head-ok')
2005 cmd.append(bundle_dst)
2006 for f in remote.fetch:
2007 cmd.append(str(f))
2008 cmd.append('refs/tags/*:refs/tags/*')
2009
2010 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002011 if os.path.exists(bundle_dst):
2012 os.remove(bundle_dst)
2013 if os.path.exists(bundle_tmp):
2014 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002015 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002016
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002017 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002018 if os.path.exists(dstPath):
2019 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002020
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002021 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002022 if quiet:
2023 cmd += ['--silent']
2024 if os.path.exists(tmpPath):
2025 size = os.stat(tmpPath).st_size
2026 if size >= 1024:
2027 cmd += ['--continue-at', '%d' % (size,)]
2028 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002029 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002030 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2031 cmd += ['--proxy', os.environ['http_proxy']]
Dan Willemsen0745bb22015-08-17 13:41:45 -07002032 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002033 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002034 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002035 if srcUrl.startswith('persistent-'):
2036 srcUrl = srcUrl[len('persistent-'):]
2037 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002038
Dave Borowitz137d0132015-01-02 11:12:54 -08002039 if IsTrace():
2040 Trace('%s', ' '.join(cmd))
2041 try:
2042 proc = subprocess.Popen(cmd)
2043 except OSError:
2044 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002045
Dave Borowitz137d0132015-01-02 11:12:54 -08002046 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002047
Dave Borowitz137d0132015-01-02 11:12:54 -08002048 if curlret == 22:
2049 # From curl man page:
2050 # 22: HTTP page not retrieved. The requested url was not found or
2051 # returned another error with the HTTP error code being 400 or above.
2052 # This return code only appears if -f, --fail is used.
2053 if not quiet:
2054 print("Server does not provide clone.bundle; ignoring.",
2055 file=sys.stderr)
2056 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002057
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002058 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002059 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002060 os.rename(tmpPath, dstPath)
2061 return True
2062 else:
2063 os.remove(tmpPath)
2064 return False
2065 else:
2066 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002067
Kris Giesingc8d882a2014-12-23 13:02:32 -08002068 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002069 try:
2070 with open(path) as f:
2071 if f.read(16) == '# v2 git bundle\n':
2072 return True
2073 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002074 if not quiet:
2075 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002076 return False
2077 except OSError:
2078 return False
2079
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002080 def _Checkout(self, rev, quiet=False):
2081 cmd = ['checkout']
2082 if quiet:
2083 cmd.append('-q')
2084 cmd.append(rev)
2085 cmd.append('--')
2086 if GitCommand(self, cmd).Wait() != 0:
2087 if self._allrefs:
2088 raise GitError('%s checkout %s ' % (self.name, rev))
2089
Anthony King7bdac712014-07-16 12:56:40 +01002090 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002091 cmd = ['cherry-pick']
2092 cmd.append(rev)
2093 cmd.append('--')
2094 if GitCommand(self, cmd).Wait() != 0:
2095 if self._allrefs:
2096 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2097
Anthony King7bdac712014-07-16 12:56:40 +01002098 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002099 cmd = ['revert']
2100 cmd.append('--no-edit')
2101 cmd.append(rev)
2102 cmd.append('--')
2103 if GitCommand(self, cmd).Wait() != 0:
2104 if self._allrefs:
2105 raise GitError('%s revert %s ' % (self.name, rev))
2106
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002107 def _ResetHard(self, rev, quiet=True):
2108 cmd = ['reset', '--hard']
2109 if quiet:
2110 cmd.append('-q')
2111 cmd.append(rev)
2112 if GitCommand(self, cmd).Wait() != 0:
2113 raise GitError('%s reset --hard %s ' % (self.name, rev))
2114
Anthony King7bdac712014-07-16 12:56:40 +01002115 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002116 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002117 if onto is not None:
2118 cmd.extend(['--onto', onto])
2119 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002120 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002121 raise GitError('%s rebase %s ' % (self.name, upstream))
2122
Pierre Tardy3d125942012-05-04 12:18:12 +02002123 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002124 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002125 if ffonly:
2126 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002127 if GitCommand(self, cmd).Wait() != 0:
2128 raise GitError('%s merge %s ' % (self.name, head))
2129
Kevin Degiabaa7f32014-11-12 11:27:45 -07002130 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002131 init_git_dir = not os.path.exists(self.gitdir)
2132 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002133 try:
2134 # Initialize the bare repository, which contains all of the objects.
2135 if init_obj_dir:
2136 os.makedirs(self.objdir)
2137 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002138
Kevin Degib1a07b82015-07-27 13:33:43 -06002139 # If we have a separate directory to hold refs, initialize it as well.
2140 if self.objdir != self.gitdir:
2141 if init_git_dir:
2142 os.makedirs(self.gitdir)
2143
2144 if init_obj_dir or init_git_dir:
2145 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2146 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002147 try:
2148 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2149 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002150 if force_sync:
David Pursehouse25857b82015-08-19 18:06:22 +09002151 print("Retrying clone after deleting %s" % self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002152 try:
2153 shutil.rmtree(os.path.realpath(self.gitdir))
2154 if self.worktree and os.path.exists(
2155 os.path.realpath(self.worktree)):
2156 shutil.rmtree(os.path.realpath(self.worktree))
2157 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2158 except:
2159 raise e
2160 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002161
Kevin Degi384b3c52014-10-16 16:02:58 -06002162 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002163 mp = self.manifest.manifestProject
2164 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002165
Kevin Degib1a07b82015-07-27 13:33:43 -06002166 if ref_dir or mirror_git:
2167 if not mirror_git:
2168 mirror_git = os.path.join(ref_dir, self.name + '.git')
2169 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2170 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002171
Kevin Degib1a07b82015-07-27 13:33:43 -06002172 if os.path.exists(mirror_git):
2173 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002174
Kevin Degib1a07b82015-07-27 13:33:43 -06002175 elif os.path.exists(repo_git):
2176 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002177
Kevin Degib1a07b82015-07-27 13:33:43 -06002178 else:
2179 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002180
Kevin Degib1a07b82015-07-27 13:33:43 -06002181 if ref_dir:
2182 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2183 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002184
Kevin Degib1a07b82015-07-27 13:33:43 -06002185 self._UpdateHooks()
2186
2187 m = self.manifest.manifestProject.config
2188 for key in ['user.name', 'user.email']:
2189 if m.Has(key, include_defaults=False):
2190 self.config.SetString(key, m.GetString(key))
2191 if self.manifest.IsMirror:
2192 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002193 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002194 self.config.SetString('core.bare', None)
2195 except Exception:
2196 if init_obj_dir and os.path.exists(self.objdir):
2197 shutil.rmtree(self.objdir)
2198 if init_git_dir and os.path.exists(self.gitdir):
2199 shutil.rmtree(self.gitdir)
2200 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002201
Jimmie Westera0444582012-10-24 13:44:42 +02002202 def _UpdateHooks(self):
2203 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002204 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002205
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002206 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002207 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002208 if not os.path.exists(hooks):
2209 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002210 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002211 name = os.path.basename(stock_hook)
2212
Victor Boivie65e0f352011-04-18 11:23:29 +02002213 if name in ('commit-msg',) and not self.remote.review \
2214 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002215 # Don't install a Gerrit Code Review hook if this
2216 # project does not appear to use it for reviews.
2217 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002218 # Since the manifest project is one of those, but also
2219 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002220 continue
2221
2222 dst = os.path.join(hooks, name)
2223 if os.path.islink(dst):
2224 continue
2225 if os.path.exists(dst):
2226 if filecmp.cmp(stock_hook, dst, shallow=False):
2227 os.remove(dst)
2228 else:
2229 _error("%s: Not replacing %s hook", self.relpath, name)
2230 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002231 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002232 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002233 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002234 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002235 raise GitError('filesystem must support symlinks')
2236 else:
2237 raise
2238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002239 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002240 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002241 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002242 remote.url = self.remote.url
2243 remote.review = self.remote.review
2244 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002245
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002246 if self.worktree:
2247 remote.ResetFetch(mirror=False)
2248 else:
2249 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002250 remote.Save()
2251
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002252 def _InitMRef(self):
2253 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002254 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002255
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002256 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002257 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002258
2259 def _InitAnyMRef(self, ref):
2260 cur = self.bare_ref.symref(ref)
2261
2262 if self.revisionId:
2263 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2264 msg = 'manifest set to %s' % self.revisionId
2265 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002266 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002267 else:
2268 remote = self.GetRemote(self.remote.name)
2269 dst = remote.ToLocal(self.revisionExpr)
2270 if cur != dst:
2271 msg = 'manifest set to %s' % self.revisionExpr
2272 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002273
Kevin Degi384b3c52014-10-16 16:02:58 -06002274 def _CheckDirReference(self, srcdir, destdir, share_refs):
2275 symlink_files = self.shareable_files
2276 symlink_dirs = self.shareable_dirs
2277 if share_refs:
2278 symlink_files += self.working_tree_files
2279 symlink_dirs += self.working_tree_dirs
2280 to_symlink = symlink_files + symlink_dirs
2281 for name in set(to_symlink):
2282 dst = os.path.realpath(os.path.join(destdir, name))
2283 if os.path.lexists(dst):
2284 src = os.path.realpath(os.path.join(srcdir, name))
2285 # Fail if the links are pointing to the wrong place
2286 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002287 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002288 'work tree. If you\'re comfortable with the '
2289 'possibility of losing the work tree\'s git metadata,'
2290 ' use `repo sync --force-sync {0}` to '
2291 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002292
David James8d201162013-10-11 17:03:19 -07002293 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2294 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2295
2296 Args:
2297 gitdir: The bare git repository. Must already be initialized.
2298 dotgit: The repository you would like to initialize.
2299 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2300 Only one work tree can store refs under a given |gitdir|.
2301 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2302 This saves you the effort of initializing |dotgit| yourself.
2303 """
Kevin Degi384b3c52014-10-16 16:02:58 -06002304 symlink_files = self.shareable_files
2305 symlink_dirs = self.shareable_dirs
David James8d201162013-10-11 17:03:19 -07002306 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002307 symlink_files += self.working_tree_files
2308 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002309 to_symlink = symlink_files + symlink_dirs
2310
2311 to_copy = []
2312 if copy_all:
2313 to_copy = os.listdir(gitdir)
2314
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002315 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002316 for name in set(to_copy).union(to_symlink):
2317 try:
2318 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002319 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002320
Kevin Degi384b3c52014-10-16 16:02:58 -06002321 if os.path.lexists(dst):
2322 continue
David James8d201162013-10-11 17:03:19 -07002323
2324 # If the source dir doesn't exist, create an empty dir.
2325 if name in symlink_dirs and not os.path.lexists(src):
2326 os.makedirs(src)
2327
Conley Owens80b87fe2014-05-09 17:13:44 -07002328 # If the source file doesn't exist, ensure the destination
2329 # file doesn't either.
2330 if name in symlink_files and not os.path.lexists(src):
2331 try:
2332 os.remove(dst)
2333 except OSError:
2334 pass
2335
David James8d201162013-10-11 17:03:19 -07002336 if name in to_symlink:
2337 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2338 elif copy_all and not os.path.islink(dst):
2339 if os.path.isdir(src):
2340 shutil.copytree(src, dst)
2341 elif os.path.isfile(src):
2342 shutil.copy(src, dst)
2343 except OSError as e:
2344 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002345 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002346 else:
2347 raise
2348
Kevin Degiabaa7f32014-11-12 11:27:45 -07002349 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002350 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002351 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002352 try:
2353 if init_dotgit:
2354 os.makedirs(dotgit)
2355 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2356 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002357
Kevin Degiabaa7f32014-11-12 11:27:45 -07002358 try:
2359 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2360 except GitError as e:
2361 if force_sync:
2362 try:
2363 shutil.rmtree(dotgit)
2364 return self._InitWorkTree(force_sync=False)
2365 except:
2366 raise e
2367 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002368
Kevin Degib1a07b82015-07-27 13:33:43 -06002369 if init_dotgit:
2370 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002371
Kevin Degib1a07b82015-07-27 13:33:43 -06002372 cmd = ['read-tree', '--reset', '-u']
2373 cmd.append('-v')
2374 cmd.append(HEAD)
2375 if GitCommand(self, cmd).Wait() != 0:
2376 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002377
Kevin Degib1a07b82015-07-27 13:33:43 -06002378 self._CopyAndLinkFiles()
2379 except Exception:
2380 if init_dotgit:
2381 shutil.rmtree(dotgit)
2382 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002383
2384 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002385 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002386
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002387 def _revlist(self, *args, **kw):
2388 a = []
2389 a.extend(args)
2390 a.append('--')
2391 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002392
2393 @property
2394 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002395 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002396
Julien Camperguedd654222014-01-09 16:21:37 +01002397 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2398 """Get logs between two revisions of this project."""
2399 comp = '..'
2400 if rev1:
2401 revs = [rev1]
2402 if rev2:
2403 revs.extend([comp, rev2])
2404 cmd = ['log', ''.join(revs)]
2405 out = DiffColoring(self.config)
2406 if out.is_on and color:
2407 cmd.append('--color')
2408 if oneline:
2409 cmd.append('--oneline')
2410
2411 try:
2412 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2413 if log.Wait() == 0:
2414 return log.stdout
2415 except GitError:
2416 # worktree may not exist if groups changed for example. In that case,
2417 # try in gitdir instead.
2418 if not os.path.exists(self.worktree):
2419 return self.bare_git.log(*cmd[1:])
2420 else:
2421 raise
2422 return None
2423
2424 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2425 """Get the list of logs from this revision to given revisionId"""
2426 logs = {}
2427 selfId = self.GetRevisionId(self._allrefs)
2428 toId = toProject.GetRevisionId(toProject._allrefs)
2429
2430 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2431 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2432 return logs
2433
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002434 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002435 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002436 self._project = project
2437 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002438 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002439
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002440 def LsOthers(self):
2441 p = GitCommand(self._project,
2442 ['ls-files',
2443 '-z',
2444 '--others',
2445 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002446 bare=False,
David James8d201162013-10-11 17:03:19 -07002447 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002448 capture_stdout=True,
2449 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002450 if p.Wait() == 0:
2451 out = p.stdout
2452 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002453 return out[:-1].split('\0') # pylint: disable=W1401
2454 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002455 return []
2456
2457 def DiffZ(self, name, *args):
2458 cmd = [name]
2459 cmd.append('-z')
2460 cmd.extend(args)
2461 p = GitCommand(self._project,
2462 cmd,
David James8d201162013-10-11 17:03:19 -07002463 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002464 bare=False,
2465 capture_stdout=True,
2466 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002467 try:
2468 out = p.process.stdout.read()
2469 r = {}
2470 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002471 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002472 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002473 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002474 info = next(out)
2475 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002476 except StopIteration:
2477 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002478
2479 class _Info(object):
2480 def __init__(self, path, omode, nmode, oid, nid, state):
2481 self.path = path
2482 self.src_path = None
2483 self.old_mode = omode
2484 self.new_mode = nmode
2485 self.old_id = oid
2486 self.new_id = nid
2487
2488 if len(state) == 1:
2489 self.status = state
2490 self.level = None
2491 else:
2492 self.status = state[:1]
2493 self.level = state[1:]
2494 while self.level.startswith('0'):
2495 self.level = self.level[1:]
2496
2497 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002498 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002499 if info.status in ('R', 'C'):
2500 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002501 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002502 r[info.path] = info
2503 return r
2504 finally:
2505 p.Wait()
2506
2507 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002508 if self._bare:
2509 path = os.path.join(self._project.gitdir, HEAD)
2510 else:
2511 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002512 try:
2513 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002514 except IOError as e:
2515 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002516 try:
2517 line = fd.read()
2518 finally:
2519 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302520 try:
2521 line = line.decode()
2522 except AttributeError:
2523 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002524 if line.startswith('ref: '):
2525 return line[5:-1]
2526 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002527
2528 def SetHead(self, ref, message=None):
2529 cmdv = []
2530 if message is not None:
2531 cmdv.extend(['-m', message])
2532 cmdv.append(HEAD)
2533 cmdv.append(ref)
2534 self.symbolic_ref(*cmdv)
2535
2536 def DetachHead(self, new, message=None):
2537 cmdv = ['--no-deref']
2538 if message is not None:
2539 cmdv.extend(['-m', message])
2540 cmdv.append(HEAD)
2541 cmdv.append(new)
2542 self.update_ref(*cmdv)
2543
2544 def UpdateRef(self, name, new, old=None,
2545 message=None,
2546 detach=False):
2547 cmdv = []
2548 if message is not None:
2549 cmdv.extend(['-m', message])
2550 if detach:
2551 cmdv.append('--no-deref')
2552 cmdv.append(name)
2553 cmdv.append(new)
2554 if old is not None:
2555 cmdv.append(old)
2556 self.update_ref(*cmdv)
2557
2558 def DeleteRef(self, name, old=None):
2559 if not old:
2560 old = self.rev_parse(name)
2561 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002562 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002563
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002564 def rev_list(self, *args, **kw):
2565 if 'format' in kw:
2566 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2567 else:
2568 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002569 cmdv.extend(args)
2570 p = GitCommand(self._project,
2571 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002572 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002573 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002574 capture_stdout=True,
2575 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002576 r = []
2577 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002578 if line[-1] == '\n':
2579 line = line[:-1]
2580 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002581 if p.Wait() != 0:
2582 raise GitError('%s rev-list %s: %s' % (
2583 self._project.name,
2584 str(args),
2585 p.stderr))
2586 return r
2587
2588 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002589 """Allow arbitrary git commands using pythonic syntax.
2590
2591 This allows you to do things like:
2592 git_obj.rev_parse('HEAD')
2593
2594 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2595 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002596 Any other positional arguments will be passed to the git command, and the
2597 following keyword arguments are supported:
2598 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002599
2600 Args:
2601 name: The name of the git command to call. Any '_' characters will
2602 be replaced with '-'.
2603
2604 Returns:
2605 A callable object that will try to call git with the named command.
2606 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002607 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002608 def runner(*args, **kwargs):
2609 cmdv = []
2610 config = kwargs.pop('config', None)
2611 for k in kwargs:
2612 raise TypeError('%s() got an unexpected keyword argument %r'
2613 % (name, k))
2614 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002615 if not git_require((1, 7, 2)):
2616 raise ValueError('cannot set config on command line for %s()'
2617 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302618 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002619 cmdv.append('-c')
2620 cmdv.append('%s=%s' % (k, v))
2621 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002622 cmdv.extend(args)
2623 p = GitCommand(self._project,
2624 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002625 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002626 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002627 capture_stdout=True,
2628 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002629 if p.Wait() != 0:
2630 raise GitError('%s %s: %s' % (
2631 self._project.name,
2632 name,
2633 p.stderr))
2634 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302635 try:
Conley Owensedd01512013-09-26 12:59:58 -07002636 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302637 except AttributeError:
2638 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002639 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2640 return r[:-1]
2641 return r
2642 return runner
2643
2644
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002645class _PriorSyncFailedError(Exception):
2646 def __str__(self):
2647 return 'prior sync failed; rebase still in progress'
2648
2649class _DirtyError(Exception):
2650 def __str__(self):
2651 return 'contains uncommitted changes'
2652
2653class _InfoMessage(object):
2654 def __init__(self, project, text):
2655 self.project = project
2656 self.text = text
2657
2658 def Print(self, syncbuf):
2659 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2660 syncbuf.out.nl()
2661
2662class _Failure(object):
2663 def __init__(self, project, why):
2664 self.project = project
2665 self.why = why
2666
2667 def Print(self, syncbuf):
2668 syncbuf.out.fail('error: %s/: %s',
2669 self.project.relpath,
2670 str(self.why))
2671 syncbuf.out.nl()
2672
2673class _Later(object):
2674 def __init__(self, project, action):
2675 self.project = project
2676 self.action = action
2677
2678 def Run(self, syncbuf):
2679 out = syncbuf.out
2680 out.project('project %s/', self.project.relpath)
2681 out.nl()
2682 try:
2683 self.action()
2684 out.nl()
2685 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002686 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002687 out.nl()
2688 return False
2689
2690class _SyncColoring(Coloring):
2691 def __init__(self, config):
2692 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002693 self.project = self.printer('header', attr='bold')
2694 self.info = self.printer('info')
2695 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002696
2697class SyncBuffer(object):
2698 def __init__(self, config, detach_head=False):
2699 self._messages = []
2700 self._failures = []
2701 self._later_queue1 = []
2702 self._later_queue2 = []
2703
2704 self.out = _SyncColoring(config)
2705 self.out.redirect(sys.stderr)
2706
2707 self.detach_head = detach_head
2708 self.clean = True
2709
2710 def info(self, project, fmt, *args):
2711 self._messages.append(_InfoMessage(project, fmt % args))
2712
2713 def fail(self, project, err=None):
2714 self._failures.append(_Failure(project, err))
2715 self.clean = False
2716
2717 def later1(self, project, what):
2718 self._later_queue1.append(_Later(project, what))
2719
2720 def later2(self, project, what):
2721 self._later_queue2.append(_Later(project, what))
2722
2723 def Finish(self):
2724 self._PrintMessages()
2725 self._RunLater()
2726 self._PrintMessages()
2727 return self.clean
2728
2729 def _RunLater(self):
2730 for q in ['_later_queue1', '_later_queue2']:
2731 if not self._RunQueue(q):
2732 return
2733
2734 def _RunQueue(self, queue):
2735 for m in getattr(self, queue):
2736 if not m.Run(self):
2737 self.clean = False
2738 return False
2739 setattr(self, queue, [])
2740 return True
2741
2742 def _PrintMessages(self):
2743 for m in self._messages:
2744 m.Print(self)
2745 for m in self._failures:
2746 m.Print(self)
2747
2748 self._messages = []
2749 self._failures = []
2750
2751
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002752class MetaProject(Project):
2753 """A special project housed under .repo.
2754 """
2755 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002756 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002757 manifest=manifest,
2758 name=name,
2759 gitdir=gitdir,
2760 objdir=gitdir,
2761 worktree=worktree,
2762 remote=RemoteSpec('origin'),
2763 relpath='.repo/%s' % name,
2764 revisionExpr='refs/heads/master',
2765 revisionId=None,
2766 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002767
2768 def PreSync(self):
2769 if self.Exists:
2770 cb = self.CurrentBranch
2771 if cb:
2772 base = self.GetBranch(cb).merge
2773 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002774 self.revisionExpr = base
2775 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002776
Anthony King7bdac712014-07-16 12:56:40 +01002777 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002778 """ Prepare MetaProject for manifest branch switch
2779 """
2780
2781 # detach and delete manifest branch, allowing a new
2782 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002783 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002784 self.Sync_LocalHalf(syncbuf)
2785 syncbuf.Finish()
2786
2787 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002788 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002789 capture_stdout=True,
2790 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002791
2792
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002793 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002794 def LastFetch(self):
2795 try:
2796 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2797 return os.path.getmtime(fh)
2798 except OSError:
2799 return 0
2800
2801 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002802 def HasChanges(self):
2803 """Has the remote received new commits not yet checked out?
2804 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002805 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002806 return False
2807
David Pursehouse8a68ff92012-09-24 12:15:13 +09002808 all_refs = self.bare_ref.all
2809 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002810 head = self.work_git.GetHead()
2811 if head.startswith(R_HEADS):
2812 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002813 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002814 except KeyError:
2815 head = None
2816
2817 if revid == head:
2818 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002819 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002820 return True
2821 return False