blob: d54e336c9dcecc3c0bab2c2cca74541a0b6f1e25 [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
David Pursehousef33929d2015-08-24 14:39:14 +090066def _warn(fmt, *args):
67 msg = fmt % args
68 print('warn: %s' % msg, file=sys.stderr)
69
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070070def not_rev(r):
71 return '^' + r
72
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080073def sq(r):
74 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080075
Jonathan Nieder93719792015-03-17 11:29:58 -070076_project_hook_list = None
77def _ProjectHooks():
78 """List the hooks present in the 'hooks' directory.
79
80 These hooks are project hooks and are copied to the '.git/hooks' directory
81 of all subprojects.
82
83 This function caches the list of hooks (based on the contents of the
84 'repo/hooks' directory) on the first call.
85
86 Returns:
87 A list of absolute paths to all of the files in the hooks directory.
88 """
89 global _project_hook_list
90 if _project_hook_list is None:
91 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
92 d = os.path.join(d, 'hooks')
93 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
94 return _project_hook_list
95
96
Shawn O. Pearce632768b2008-10-23 11:58:52 -070097class DownloadedChange(object):
98 _commit_cache = None
99
100 def __init__(self, project, base, change_id, ps_id, commit):
101 self.project = project
102 self.base = base
103 self.change_id = change_id
104 self.ps_id = ps_id
105 self.commit = commit
106
107 @property
108 def commits(self):
109 if self._commit_cache is None:
110 self._commit_cache = self.project.bare_git.rev_list(
111 '--abbrev=8',
112 '--abbrev-commit',
113 '--pretty=oneline',
114 '--reverse',
115 '--date-order',
116 not_rev(self.base),
117 self.commit,
118 '--')
119 return self._commit_cache
120
121
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700122class ReviewableBranch(object):
123 _commit_cache = None
124
125 def __init__(self, project, branch, base):
126 self.project = project
127 self.branch = branch
128 self.base = base
129
130 @property
131 def name(self):
132 return self.branch.name
133
134 @property
135 def commits(self):
136 if self._commit_cache is None:
137 self._commit_cache = self.project.bare_git.rev_list(
138 '--abbrev=8',
139 '--abbrev-commit',
140 '--pretty=oneline',
141 '--reverse',
142 '--date-order',
143 not_rev(self.base),
144 R_HEADS + self.name,
145 '--')
146 return self._commit_cache
147
148 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800149 def unabbrev_commits(self):
150 r = dict()
151 for commit in self.project.bare_git.rev_list(
152 not_rev(self.base),
153 R_HEADS + self.name,
154 '--'):
155 r[commit[0:8]] = commit
156 return r
157
158 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159 def date(self):
160 return self.project.bare_git.log(
161 '--pretty=format:%cd',
162 '-n', '1',
163 R_HEADS + self.name,
164 '--')
165
Bryan Jacobsf609f912013-05-06 13:36:24 -0400166 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800167 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700168 people,
Brian Harring435370c2012-07-28 15:37:04 -0700169 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400170 draft=draft,
171 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700173 def GetPublishedRefs(self):
174 refs = {}
175 output = self.project.bare_git.ls_remote(
176 self.branch.remote.SshReviewUrl(self.project.UserEmail),
177 'refs/changes/*')
178 for line in output.split('\n'):
179 try:
180 (sha, ref) = line.split()
181 refs[sha] = ref
182 except ValueError:
183 pass
184
185 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186
187class StatusColoring(Coloring):
188 def __init__(self, config):
189 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100190 self.project = self.printer('header', attr='bold')
191 self.branch = self.printer('header', attr='bold')
192 self.nobranch = self.printer('nobranch', fg='red')
193 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700194
Anthony King7bdac712014-07-16 12:56:40 +0100195 self.added = self.printer('added', fg='green')
196 self.changed = self.printer('changed', fg='red')
197 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700198
199
200class DiffColoring(Coloring):
201 def __init__(self, config):
202 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100203 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204
Anthony King7bdac712014-07-16 12:56:40 +0100205class _Annotation(object):
James W. Mills24c13082012-04-12 15:04:13 -0500206 def __init__(self, name, value, keep):
207 self.name = name
208 self.value = value
209 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210
Anthony King7bdac712014-07-16 12:56:40 +0100211class _CopyFile(object):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800212 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213 self.src = src
214 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800215 self.abs_src = abssrc
216 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217
218 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800219 src = self.abs_src
220 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221 # copy file if it does not exist or is out of date
222 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
223 try:
224 # remove existing file first, since it might be read-only
225 if os.path.exists(dest):
226 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400227 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200228 dest_dir = os.path.dirname(dest)
229 if not os.path.isdir(dest_dir):
230 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231 shutil.copy(src, dest)
232 # make the file read-only
233 mode = os.stat(dest)[stat.ST_MODE]
234 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
235 os.chmod(dest, mode)
236 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700237 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238
Anthony King7bdac712014-07-16 12:56:40 +0100239class _LinkFile(object):
Wink Saville4c426ef2015-06-03 08:05:17 -0700240 def __init__(self, git_worktree, src, dest, relsrc, absdest):
241 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500242 self.src = src
243 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700244 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500245 self.abs_dest = absdest
246
Wink Saville4c426ef2015-06-03 08:05:17 -0700247 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500248 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700249 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500250 try:
251 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800252 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700253 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500254 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700255 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500256 if not os.path.isdir(dest_dir):
257 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700258 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500259 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700260 _error('Cannot link file %s to %s', relSrc, absDest)
261
262 def _Link(self):
263 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
264 on the src linking all of the files in the source in to the destination
265 directory.
266 """
267 # We use the absSrc to handle the situation where the current directory
268 # is not the root of the repo
269 absSrc = os.path.join(self.git_worktree, self.src)
270 if os.path.exists(absSrc):
271 # Entity exists so just a simple one to one link operation
272 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
273 else:
274 # Entity doesn't exist assume there is a wild card
275 absDestDir = self.abs_dest
276 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
277 _error('Link error: src with wildcard, %s must be a directory',
278 absDestDir)
279 else:
280 absSrcFiles = glob.glob(absSrc)
281 for absSrcFile in absSrcFiles:
282 # Create a releative path from source dir to destination dir
283 absSrcDir = os.path.dirname(absSrcFile)
284 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
285
286 # Get the source file name
287 srcFile = os.path.basename(absSrcFile)
288
289 # Now form the final full paths to srcFile. They will be
290 # absolute for the desintaiton and relative for the srouce.
291 absDest = os.path.join(absDestDir, srcFile)
292 relSrc = os.path.join(relSrcDir, srcFile)
293 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500294
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700295class RemoteSpec(object):
296 def __init__(self,
297 name,
Anthony King7bdac712014-07-16 12:56:40 +0100298 url=None,
299 review=None,
300 revision=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700301 self.name = name
302 self.url = url
303 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100304 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700305
Doug Anderson37282b42011-03-04 11:54:18 -0800306class RepoHook(object):
307 """A RepoHook contains information about a script to run as a hook.
308
309 Hooks are used to run a python script before running an upload (for instance,
310 to run presubmit checks). Eventually, we may have hooks for other actions.
311
312 This shouldn't be confused with files in the 'repo/hooks' directory. Those
313 files are copied into each '.git/hooks' folder for each project. Repo-level
314 hooks are associated instead with repo actions.
315
316 Hooks are always python. When a hook is run, we will load the hook into the
317 interpreter and execute its main() function.
318 """
319 def __init__(self,
320 hook_type,
321 hooks_project,
322 topdir,
323 abort_if_user_denies=False):
324 """RepoHook constructor.
325
326 Params:
327 hook_type: A string representing the type of hook. This is also used
328 to figure out the name of the file containing the hook. For
329 example: 'pre-upload'.
330 hooks_project: The project containing the repo hooks. If you have a
331 manifest, this is manifest.repo_hooks_project. OK if this is None,
332 which will make the hook a no-op.
333 topdir: Repo's top directory (the one containing the .repo directory).
334 Scripts will run with CWD as this directory. If you have a manifest,
335 this is manifest.topdir
336 abort_if_user_denies: If True, we'll throw a HookError() if the user
337 doesn't allow us to run the hook.
338 """
339 self._hook_type = hook_type
340 self._hooks_project = hooks_project
341 self._topdir = topdir
342 self._abort_if_user_denies = abort_if_user_denies
343
344 # Store the full path to the script for convenience.
345 if self._hooks_project:
346 self._script_fullpath = os.path.join(self._hooks_project.worktree,
347 self._hook_type + '.py')
348 else:
349 self._script_fullpath = None
350
351 def _GetHash(self):
352 """Return a hash of the contents of the hooks directory.
353
354 We'll just use git to do this. This hash has the property that if anything
355 changes in the directory we will return a different has.
356
357 SECURITY CONSIDERATION:
358 This hash only represents the contents of files in the hook directory, not
359 any other files imported or called by hooks. Changes to imported files
360 can change the script behavior without affecting the hash.
361
362 Returns:
363 A string representing the hash. This will always be ASCII so that it can
364 be printed to the user easily.
365 """
366 assert self._hooks_project, "Must have hooks to calculate their hash."
367
368 # We will use the work_git object rather than just calling GetRevisionId().
369 # That gives us a hash of the latest checked in version of the files that
370 # the user will actually be executing. Specifically, GetRevisionId()
371 # doesn't appear to change even if a user checks out a different version
372 # of the hooks repo (via git checkout) nor if a user commits their own revs.
373 #
374 # NOTE: Local (non-committed) changes will not be factored into this hash.
375 # I think this is OK, since we're really only worried about warning the user
376 # about upstream changes.
377 return self._hooks_project.work_git.rev_parse('HEAD')
378
379 def _GetMustVerb(self):
380 """Return 'must' if the hook is required; 'should' if not."""
381 if self._abort_if_user_denies:
382 return 'must'
383 else:
384 return 'should'
385
386 def _CheckForHookApproval(self):
387 """Check to see whether this hook has been approved.
388
389 We'll look at the hash of all of the hooks. If this matches the hash that
390 the user last approved, we're done. If it doesn't, we'll ask the user
391 about approval.
392
393 Note that we ask permission for each individual hook even though we use
394 the hash of all hooks when detecting changes. We'd like the user to be
395 able to approve / deny each hook individually. We only use the hash of all
396 hooks because there is no other easy way to detect changes to local imports.
397
398 Returns:
399 True if this hook is approved to run; False otherwise.
400
401 Raises:
402 HookError: Raised if the user doesn't approve and abort_if_user_denies
403 was passed to the consturctor.
404 """
Doug Anderson37282b42011-03-04 11:54:18 -0800405 hooks_config = self._hooks_project.config
406 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
407
408 # Get the last hash that the user approved for this hook; may be None.
409 old_hash = hooks_config.GetString(git_approval_key)
410
411 # Get the current hash so we can tell if scripts changed since approval.
412 new_hash = self._GetHash()
413
414 if old_hash is not None:
415 # User previously approved hook and asked not to be prompted again.
416 if new_hash == old_hash:
417 # Approval matched. We're done.
418 return True
419 else:
420 # Give the user a reason why we're prompting, since they last told
421 # us to "never ask again".
422 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
423 self._hook_type)
424 else:
425 prompt = ''
426
427 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
428 if sys.stdout.isatty():
429 prompt += ('Repo %s run the script:\n'
430 ' %s\n'
431 '\n'
432 'Do you want to allow this script to run '
433 '(yes/yes-never-ask-again/NO)? ') % (
434 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530435 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900436 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800437
438 # User is doing a one-time approval.
439 if response in ('y', 'yes'):
440 return True
441 elif response == 'yes-never-ask-again':
442 hooks_config.SetString(git_approval_key, new_hash)
443 return True
444
445 # For anything else, we'll assume no approval.
446 if self._abort_if_user_denies:
447 raise HookError('You must allow the %s hook or use --no-verify.' %
448 self._hook_type)
449
450 return False
451
452 def _ExecuteHook(self, **kwargs):
453 """Actually execute the given hook.
454
455 This will run the hook's 'main' function in our python interpreter.
456
457 Args:
458 kwargs: Keyword arguments to pass to the hook. These are often specific
459 to the hook type. For instance, pre-upload hooks will contain
460 a project_list.
461 """
462 # Keep sys.path and CWD stashed away so that we can always restore them
463 # upon function exit.
464 orig_path = os.getcwd()
465 orig_syspath = sys.path
466
467 try:
468 # Always run hooks with CWD as topdir.
469 os.chdir(self._topdir)
470
471 # Put the hook dir as the first item of sys.path so hooks can do
472 # relative imports. We want to replace the repo dir as [0] so
473 # hooks can't import repo files.
474 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
475
476 # Exec, storing global context in the context dict. We catch exceptions
477 # and convert to a HookError w/ just the failing traceback.
478 context = {}
479 try:
Anthony King70f68902014-05-05 21:15:34 +0100480 exec(compile(open(self._script_fullpath).read(),
481 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800482 except Exception:
483 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
484 traceback.format_exc(), self._hook_type))
485
486 # Running the script should have defined a main() function.
487 if 'main' not in context:
488 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
489
490
491 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
492 # We don't actually want hooks to define their main with this argument--
493 # it's there to remind them that their hook should always take **kwargs.
494 # For instance, a pre-upload hook should be defined like:
495 # def main(project_list, **kwargs):
496 #
497 # This allows us to later expand the API without breaking old hooks.
498 kwargs = kwargs.copy()
499 kwargs['hook_should_take_kwargs'] = True
500
501 # Call the main function in the hook. If the hook should cause the
502 # build to fail, it will raise an Exception. We'll catch that convert
503 # to a HookError w/ just the failing traceback.
504 try:
505 context['main'](**kwargs)
506 except Exception:
507 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
508 'above.' % (
509 traceback.format_exc(), self._hook_type))
510 finally:
511 # Restore sys.path and CWD.
512 sys.path = orig_syspath
513 os.chdir(orig_path)
514
515 def Run(self, user_allows_all_hooks, **kwargs):
516 """Run the hook.
517
518 If the hook doesn't exist (because there is no hooks project or because
519 this particular hook is not enabled), this is a no-op.
520
521 Args:
522 user_allows_all_hooks: If True, we will never prompt about running the
523 hook--we'll just assume it's OK to run it.
524 kwargs: Keyword arguments to pass to the hook. These are often specific
525 to the hook type. For instance, pre-upload hooks will contain
526 a project_list.
527
528 Raises:
529 HookError: If there was a problem finding the hook or the user declined
530 to run a required hook (from _CheckForHookApproval).
531 """
532 # No-op if there is no hooks project or if hook is disabled.
533 if ((not self._hooks_project) or
534 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
535 return
536
537 # Bail with a nice error if we can't find the hook.
538 if not os.path.isfile(self._script_fullpath):
539 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
540
541 # Make sure the user is OK with running the hook.
542 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
543 return
544
545 # Run the hook with the same version of python we're using.
546 self._ExecuteHook(**kwargs)
547
548
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700549class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600550 # These objects can be shared between several working trees.
551 shareable_files = ['description', 'info']
552 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
553 # These objects can only be used by a single working tree.
554 working_tree_files = ['config', 'packed-refs', 'shallow']
555 working_tree_dirs = ['logs', 'refs']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700556 def __init__(self,
557 manifest,
558 name,
559 remote,
560 gitdir,
David James8d201162013-10-11 17:03:19 -0700561 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700562 worktree,
563 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700564 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800565 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100566 rebase=True,
567 groups=None,
568 sync_c=False,
569 sync_s=False,
570 clone_depth=None,
571 upstream=None,
572 parent=None,
573 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900574 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700575 optimized_fetch=False,
576 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800577 """Init a Project object.
578
579 Args:
580 manifest: The XmlManifest object.
581 name: The `name` attribute of manifest.xml's project element.
582 remote: RemoteSpec object specifying its remote's properties.
583 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700584 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800585 worktree: Absolute path of git working tree.
586 relpath: Relative path of git working tree to repo's top directory.
587 revisionExpr: The `revision` attribute of manifest.xml's project element.
588 revisionId: git commit id for checking out.
589 rebase: The `rebase` attribute of manifest.xml's project element.
590 groups: The `groups` attribute of manifest.xml's project element.
591 sync_c: The `sync-c` attribute of manifest.xml's project element.
592 sync_s: The `sync-s` attribute of manifest.xml's project element.
593 upstream: The `upstream` attribute of manifest.xml's project element.
594 parent: The parent Project object.
595 is_derived: False if the project was explicitly defined in the manifest;
596 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400597 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900598 optimized_fetch: If True, when a project is set to a sha1 revision, only
599 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700600 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800601 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700602 self.manifest = manifest
603 self.name = name
604 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800605 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700606 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800607 if worktree:
608 self.worktree = worktree.replace('\\', '/')
609 else:
610 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700611 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700612 self.revisionExpr = revisionExpr
613
614 if revisionId is None \
615 and revisionExpr \
616 and IsId(revisionExpr):
617 self.revisionId = revisionExpr
618 else:
619 self.revisionId = revisionId
620
Mike Pontillod3153822012-02-28 11:53:24 -0800621 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700622 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700623 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800624 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900625 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700626 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800627 self.parent = parent
628 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900629 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800630 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800631
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700632 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700633 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500634 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500635 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700636 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100637 gitdir=self.gitdir,
638 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700639
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800640 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700641 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800642 else:
643 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700644 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700645 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700646 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400647 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700648 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700649
Doug Anderson37282b42011-03-04 11:54:18 -0800650 # This will be filled in if a project is later identified to be the
651 # project containing repo hooks.
652 self.enabled_repo_hooks = []
653
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800655 def Derived(self):
656 return self.is_derived
657
658 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700659 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600660 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700661
662 @property
663 def CurrentBranch(self):
664 """Obtain the name of the currently checked out branch.
665 The branch name omits the 'refs/heads/' prefix.
666 None is returned if the project is on a detached HEAD.
667 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700668 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669 if b.startswith(R_HEADS):
670 return b[len(R_HEADS):]
671 return None
672
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700673 def IsRebaseInProgress(self):
674 w = self.worktree
675 g = os.path.join(w, '.git')
676 return os.path.exists(os.path.join(g, 'rebase-apply')) \
677 or os.path.exists(os.path.join(g, 'rebase-merge')) \
678 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200679
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700680 def IsDirty(self, consider_untracked=True):
681 """Is the working directory modified in some way?
682 """
683 self.work_git.update_index('-q',
684 '--unmerged',
685 '--ignore-missing',
686 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900687 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700688 return True
689 if self.work_git.DiffZ('diff-files'):
690 return True
691 if consider_untracked and self.work_git.LsOthers():
692 return True
693 return False
694
695 _userident_name = None
696 _userident_email = None
697
698 @property
699 def UserName(self):
700 """Obtain the user's personal name.
701 """
702 if self._userident_name is None:
703 self._LoadUserIdentity()
704 return self._userident_name
705
706 @property
707 def UserEmail(self):
708 """Obtain the user's email address. This is very likely
709 to be their Gerrit login.
710 """
711 if self._userident_email is None:
712 self._LoadUserIdentity()
713 return self._userident_email
714
715 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900716 u = self.bare_git.var('GIT_COMMITTER_IDENT')
717 m = re.compile("^(.*) <([^>]*)> ").match(u)
718 if m:
719 self._userident_name = m.group(1)
720 self._userident_email = m.group(2)
721 else:
722 self._userident_name = ''
723 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700724
725 def GetRemote(self, name):
726 """Get the configuration for a single remote.
727 """
728 return self.config.GetRemote(name)
729
730 def GetBranch(self, name):
731 """Get the configuration for a single branch.
732 """
733 return self.config.GetBranch(name)
734
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700735 def GetBranches(self):
736 """Get all existing local branches.
737 """
738 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900739 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700740 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700741
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530742 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700743 if name.startswith(R_HEADS):
744 name = name[len(R_HEADS):]
745 b = self.GetBranch(name)
746 b.current = name == current
747 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900748 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700749 heads[name] = b
750
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530751 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700752 if name.startswith(R_PUB):
753 name = name[len(R_PUB):]
754 b = heads.get(name)
755 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900756 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700757
758 return heads
759
Colin Cross5acde752012-03-28 20:15:45 -0700760 def MatchesGroups(self, manifest_groups):
761 """Returns true if the manifest groups specified at init should cause
762 this project to be synced.
763 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700764 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700765
Conley Owens971de8e2012-04-16 10:36:08 -0700766 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700767 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700768 manifest_groups: "-group1,group2"
769 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500770
771 The special manifest group "default" will match any project that
772 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700773 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500774 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700775 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500776 if not 'notdefault' in expanded_project_groups:
777 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700778
Conley Owens971de8e2012-04-16 10:36:08 -0700779 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700780 for group in expanded_manifest_groups:
781 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700782 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700783 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700784 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700785
Conley Owens971de8e2012-04-16 10:36:08 -0700786 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700787
788## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700789 def UncommitedFiles(self, get_all=True):
790 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700792 Args:
793 get_all: a boolean, if True - get information about all different
794 uncommitted files. If False - return as soon as any kind of
795 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500796 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700797 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500798 self.work_git.update_index('-q',
799 '--unmerged',
800 '--ignore-missing',
801 '--refresh')
802 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700803 details.append("rebase in progress")
804 if not get_all:
805 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500806
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700807 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
808 if changes:
809 details.extend(changes)
810 if not get_all:
811 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500812
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700813 changes = self.work_git.DiffZ('diff-files').keys()
814 if changes:
815 details.extend(changes)
816 if not get_all:
817 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500818
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700819 changes = self.work_git.LsOthers()
820 if changes:
821 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500822
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700823 return details
824
825 def HasChanges(self):
826 """Returns true if there are uncommitted changes.
827 """
828 if self.UncommitedFiles(get_all=False):
829 return True
830 else:
831 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500832
Terence Haddock4655e812011-03-31 12:33:34 +0200833 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200835
836 Args:
837 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 """
839 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200840 if output_redir == None:
841 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700842 print(file=output_redir)
843 print('project %s/' % self.relpath, file=output_redir)
844 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845 return
846
847 self.work_git.update_index('-q',
848 '--unmerged',
849 '--ignore-missing',
850 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700851 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700852 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
853 df = self.work_git.DiffZ('diff-files')
854 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100855 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700856 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857
858 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200859 if not output_redir == None:
860 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700861 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862
863 branch = self.CurrentBranch
864 if branch is None:
865 out.nobranch('(*** NO BRANCH ***)')
866 else:
867 out.branch('branch %s', branch)
868 out.nl()
869
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700870 if rb:
871 out.important('prior sync failed; rebase still in progress')
872 out.nl()
873
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874 paths = list()
875 paths.extend(di.keys())
876 paths.extend(df.keys())
877 paths.extend(do)
878
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530879 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900880 try:
881 i = di[p]
882 except KeyError:
883 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700884
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900885 try:
886 f = df[p]
887 except KeyError:
888 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200889
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900890 if i:
891 i_status = i.status.upper()
892 else:
893 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900895 if f:
896 f_status = f.status.lower()
897 else:
898 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899
900 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800901 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902 i.src_path, p, i.level)
903 else:
904 line = ' %s%s\t%s' % (i_status, f_status, p)
905
906 if i and not f:
907 out.added('%s', line)
908 elif (i and f) or (not i and f):
909 out.changed('%s', line)
910 elif not i and not f:
911 out.untracked('%s', line)
912 else:
913 out.write('%s', line)
914 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200915
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700916 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917
pelyad67872d2012-03-28 14:49:58 +0300918 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919 """Prints the status of the repository to stdout.
920 """
921 out = DiffColoring(self.config)
922 cmd = ['diff']
923 if out.is_on:
924 cmd.append('--color')
925 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300926 if absolute_paths:
927 cmd.append('--src-prefix=a/%s/' % self.relpath)
928 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929 cmd.append('--')
930 p = GitCommand(self,
931 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100932 capture_stdout=True,
933 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934 has_diff = False
935 for line in p.process.stdout:
936 if not has_diff:
937 out.nl()
938 out.project('project %s/' % self.relpath)
939 out.nl()
940 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700941 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 p.Wait()
943
944
945## Publish / Upload ##
946
David Pursehouse8a68ff92012-09-24 12:15:13 +0900947 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 """Was the branch published (uploaded) for code review?
949 If so, returns the SHA-1 hash of the last published
950 state for the branch.
951 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700952 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900953 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700954 try:
955 return self.bare_git.rev_parse(key)
956 except GitError:
957 return None
958 else:
959 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900960 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700961 except KeyError:
962 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963
David Pursehouse8a68ff92012-09-24 12:15:13 +0900964 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700965 """Prunes any stale published refs.
966 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900967 if all_refs is None:
968 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 heads = set()
970 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530971 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 if name.startswith(R_HEADS):
973 heads.add(name)
974 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900975 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700976
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530977 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978 n = name[len(R_PUB):]
979 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900980 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700982 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 """List any branches which can be uploaded for review.
984 """
985 heads = {}
986 pubed = {}
987
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530988 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900990 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900992 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700993
994 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530995 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900996 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700998 if selected_branch and branch != selected_branch:
999 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001001 rb = self.GetUploadableBranch(branch)
1002 if rb:
1003 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001004 return ready
1005
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001006 def GetUploadableBranch(self, branch_name):
1007 """Get a single uploadable branch, or None.
1008 """
1009 branch = self.GetBranch(branch_name)
1010 base = branch.LocalMerge
1011 if branch.LocalMerge:
1012 rb = ReviewableBranch(self, branch, base)
1013 if rb.commits:
1014 return rb
1015 return None
1016
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001017 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001018 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001019 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001020 draft=False,
1021 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022 """Uploads the named branch for code review.
1023 """
1024 if branch is None:
1025 branch = self.CurrentBranch
1026 if branch is None:
1027 raise GitError('not currently on a branch')
1028
1029 branch = self.GetBranch(branch)
1030 if not branch.LocalMerge:
1031 raise GitError('branch %s does not track a remote' % branch.name)
1032 if not branch.remote.review:
1033 raise GitError('remote %s has no review url' % branch.remote.name)
1034
Bryan Jacobsf609f912013-05-06 13:36:24 -04001035 if dest_branch is None:
1036 dest_branch = self.dest_branch
1037 if dest_branch is None:
1038 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039 if not dest_branch.startswith(R_HEADS):
1040 dest_branch = R_HEADS + dest_branch
1041
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001042 if not branch.remote.projectname:
1043 branch.remote.projectname = self.name
1044 branch.remote.Save()
1045
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001046 url = branch.remote.ReviewUrl(self.UserEmail)
1047 if url is None:
1048 raise UploadError('review not configured')
1049 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001050
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001051 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001052 rp = ['gerrit receive-pack']
1053 for e in people[0]:
1054 rp.append('--reviewer=%s' % sq(e))
1055 for e in people[1]:
1056 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001057 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001058
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001059 cmd.append(url)
1060
1061 if dest_branch.startswith(R_HEADS):
1062 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001063
1064 upload_type = 'for'
1065 if draft:
1066 upload_type = 'drafts'
1067
1068 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1069 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001070 if auto_topic:
1071 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001072 if not url.startswith('ssh://'):
1073 rp = ['r=%s' % p for p in people[0]] + \
1074 ['cc=%s' % p for p in people[1]]
1075 if rp:
1076 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001077 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001078
Anthony King7bdac712014-07-16 12:56:40 +01001079 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001080 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081
1082 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1083 self.bare_git.UpdateRef(R_PUB + branch.name,
1084 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001085 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086
1087
1088## Sync ##
1089
Julien Campergue335f5ef2013-10-16 11:02:35 +02001090 def _ExtractArchive(self, tarpath, path=None):
1091 """Extract the given tar on its current location
1092
1093 Args:
1094 - tarpath: The path to the actual tar file
1095
1096 """
1097 try:
1098 with tarfile.open(tarpath, 'r') as tar:
1099 tar.extractall(path=path)
1100 return True
1101 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001102 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001103 return False
1104
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001105 def Sync_NetworkHalf(self,
1106 quiet=False,
1107 is_new=None,
1108 current_branch_only=False,
Kevin Degiabaa7f32014-11-12 11:27:45 -07001109 force_sync=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001110 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001111 no_tags=False,
David Pursehouseb1553542014-09-04 21:28:09 +09001112 archive=False,
David Pursehouse74cfd272015-10-14 10:50:15 +09001113 optimized_fetch=False,
1114 prune=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001115 """Perform only the network IO portion of the sync process.
1116 Local working directory/branch state is not affected.
1117 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001118 if archive and not isinstance(self, MetaProject):
1119 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001120 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001121 return False
1122
1123 name = self.relpath.replace('\\', '/')
1124 name = name.replace('/', '_')
1125 tarpath = '%s.tar' % name
1126 topdir = self.manifest.topdir
1127
1128 try:
1129 self._FetchArchive(tarpath, cwd=topdir)
1130 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001131 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001132 return False
1133
1134 # From now on, we only need absolute tarpath
1135 tarpath = os.path.join(topdir, tarpath)
1136
1137 if not self._ExtractArchive(tarpath, path=topdir):
1138 return False
1139 try:
1140 os.remove(tarpath)
1141 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001142 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001143 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001144 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001145 if is_new is None:
1146 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001147 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001148 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001149 else:
1150 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001151 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001152
1153 if is_new:
1154 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1155 try:
1156 fd = open(alt, 'rb')
1157 try:
1158 alt_dir = fd.readline().rstrip()
1159 finally:
1160 fd.close()
1161 except IOError:
1162 alt_dir = None
1163 else:
1164 alt_dir = None
1165
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001166 if clone_bundle \
1167 and alt_dir is None \
1168 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001169 is_new = False
1170
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001171 if not current_branch_only:
1172 if self.sync_c:
1173 current_branch_only = True
1174 elif not self.manifest._loaded:
1175 # Manifest cannot check defaults until it syncs.
1176 current_branch_only = False
1177 elif self.manifest.default.sync_c:
1178 current_branch_only = True
1179
David Pursehouseb1553542014-09-04 21:28:09 +09001180 need_to_fetch = not (optimized_fetch and \
1181 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1182 if (need_to_fetch
Conley Owens666d5342014-05-01 13:09:57 -07001183 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1184 current_branch_only=current_branch_only,
David Pursehouse74cfd272015-10-14 10:50:15 +09001185 no_tags=no_tags, prune=prune)):
Anthony King7bdac712014-07-16 12:56:40 +01001186 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001187
1188 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001189 self._InitMRef()
1190 else:
1191 self._InitMirrorHead()
1192 try:
1193 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1194 except OSError:
1195 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001197
1198 def PostRepoUpgrade(self):
1199 self._InitHooks()
1200
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001201 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001202 if self.manifest.isGitcClient:
1203 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001204 for copyfile in self.copyfiles:
1205 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001206 for linkfile in self.linkfiles:
1207 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001208
Julien Camperguedd654222014-01-09 16:21:37 +01001209 def GetCommitRevisionId(self):
1210 """Get revisionId of a commit.
1211
1212 Use this method instead of GetRevisionId to get the id of the commit rather
1213 than the id of the current git object (for example, a tag)
1214
1215 """
1216 if not self.revisionExpr.startswith(R_TAGS):
1217 return self.GetRevisionId(self._allrefs)
1218
1219 try:
1220 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1221 except GitError:
1222 raise ManifestInvalidRevisionError(
1223 'revision %s in %s not found' % (self.revisionExpr,
1224 self.name))
1225
David Pursehouse8a68ff92012-09-24 12:15:13 +09001226 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001227 if self.revisionId:
1228 return self.revisionId
1229
1230 rem = self.GetRemote(self.remote.name)
1231 rev = rem.ToLocal(self.revisionExpr)
1232
David Pursehouse8a68ff92012-09-24 12:15:13 +09001233 if all_refs is not None and rev in all_refs:
1234 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001235
1236 try:
1237 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1238 except GitError:
1239 raise ManifestInvalidRevisionError(
1240 'revision %s in %s not found' % (self.revisionExpr,
1241 self.name))
1242
Kevin Degiabaa7f32014-11-12 11:27:45 -07001243 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001244 """Perform only the local IO portion of the sync process.
1245 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001246 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001247 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001248 all_refs = self.bare_ref.all
1249 self.CleanPublishedCache(all_refs)
1250 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001251
David Pursehouse1d947b32012-10-25 12:23:11 +09001252 def _doff():
1253 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001254 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001255
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001256 head = self.work_git.GetHead()
1257 if head.startswith(R_HEADS):
1258 branch = head[len(R_HEADS):]
1259 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001260 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001261 except KeyError:
1262 head = None
1263 else:
1264 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001265
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001266 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001267 # Currently on a detached HEAD. The user is assumed to
1268 # not have any local modifications worth worrying about.
1269 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001270 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001271 syncbuf.fail(self, _PriorSyncFailedError())
1272 return
1273
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001274 if head == revid:
1275 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001276 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001277 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001278 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001279 # The copy/linkfile config may have changed.
1280 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001281 return
1282 else:
1283 lost = self._revlist(not_rev(revid), HEAD)
1284 if lost:
1285 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001286
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001287 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001288 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001289 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001290 syncbuf.fail(self, e)
1291 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001292 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001293 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001295 if head == revid:
1296 # No changes; don't do anything further.
1297 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001298 # The copy/linkfile config may have changed.
1299 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001300 return
1301
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001304 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001306 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001308 syncbuf.info(self,
1309 "leaving %s; does not track upstream",
1310 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001312 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001313 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001314 syncbuf.fail(self, e)
1315 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001316 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001317 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001319 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001320 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001322 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323 if not_merged:
1324 if upstream_gain:
1325 # The user has published this branch and some of those
1326 # commits are not yet merged upstream. We do not want
1327 # to rewrite the published commits so we punt.
1328 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001329 syncbuf.fail(self,
1330 "branch %s is published (but not merged) and is now %d commits behind"
1331 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001332 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001333 elif pub == head:
1334 # All published commits are merged, and thus we are a
1335 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001336 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001337 syncbuf.later1(self, _doff)
1338 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001339
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001340 # Examine the local commits not in the remote. Find the
1341 # last one attributed to this user, if any.
1342 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001343 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001344 last_mine = None
1345 cnt_mine = 0
1346 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301347 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001348 if committer_email == self.UserEmail:
1349 last_mine = commit_id
1350 cnt_mine += 1
1351
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001352 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001353 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001354
1355 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001356 syncbuf.fail(self, _DirtyError())
1357 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001359 # If the upstream switched on us, warn the user.
1360 #
1361 if branch.merge != self.revisionExpr:
1362 if branch.merge and self.revisionExpr:
1363 syncbuf.info(self,
1364 'manifest switched %s...%s',
1365 branch.merge,
1366 self.revisionExpr)
1367 elif branch.merge:
1368 syncbuf.info(self,
1369 'manifest no longer tracks %s',
1370 branch.merge)
1371
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001372 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001374 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001375 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001376 syncbuf.info(self,
1377 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001378 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001379
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001380 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001381 if not ID_RE.match(self.revisionExpr):
1382 # in case of manifest sync the revisionExpr might be a SHA1
1383 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001384 if not branch.merge.startswith('refs/'):
1385 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386 branch.Save()
1387
Mike Pontillod3153822012-02-28 11:53:24 -08001388 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001389 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001390 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001391 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001392 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001393 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001395 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001396 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001397 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001398 syncbuf.fail(self, e)
1399 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001400 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001401 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001402
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001403 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001404 # dest should already be an absolute path, but src is project relative
1405 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001406 abssrc = os.path.join(self.worktree, src)
1407 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001408
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001409 def AddLinkFile(self, src, dest, absdest):
1410 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001411 # make src relative path to dest
1412 absdestdir = os.path.dirname(absdest)
1413 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001414 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001415
James W. Mills24c13082012-04-12 15:04:13 -05001416 def AddAnnotation(self, name, value, keep):
1417 self.annotations.append(_Annotation(name, value, keep))
1418
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001419 def DownloadPatchSet(self, change_id, patch_id):
1420 """Download a single patch set of a single change to FETCH_HEAD.
1421 """
1422 remote = self.GetRemote(self.remote.name)
1423
1424 cmd = ['fetch', remote.name]
1425 cmd.append('refs/changes/%2.2d/%d/%d' \
1426 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001427 if GitCommand(self, cmd, bare=True).Wait() != 0:
1428 return None
1429 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001430 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001431 change_id,
1432 patch_id,
1433 self.bare_git.rev_parse('FETCH_HEAD'))
1434
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001435
1436## Branch Management ##
1437
Simran Basib9a1b732015-08-20 12:19:28 -07001438 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001439 """Create a new branch off the manifest's revision.
1440 """
Simran Basib9a1b732015-08-20 12:19:28 -07001441 if not branch_merge:
1442 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001443 head = self.work_git.GetHead()
1444 if head == (R_HEADS + name):
1445 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001446
David Pursehouse8a68ff92012-09-24 12:15:13 +09001447 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001448 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001449 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001450 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001451 capture_stdout=True,
1452 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001453
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001454 branch = self.GetBranch(name)
1455 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001456 branch.merge = branch_merge
1457 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1458 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001459 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001460
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001461 if head.startswith(R_HEADS):
1462 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001463 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001464 except KeyError:
1465 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001466 if revid and head and revid == head:
1467 ref = os.path.join(self.gitdir, R_HEADS + name)
1468 try:
1469 os.makedirs(os.path.dirname(ref))
1470 except OSError:
1471 pass
1472 _lwrite(ref, '%s\n' % revid)
1473 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1474 'ref: %s%s\n' % (R_HEADS, name))
1475 branch.Save()
1476 return True
1477
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001478 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001479 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001480 capture_stdout=True,
1481 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001482 branch.Save()
1483 return True
1484 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001485
Wink Saville02d79452009-04-10 13:01:24 -07001486 def CheckoutBranch(self, name):
1487 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001488
1489 Args:
1490 name: The name of the branch to checkout.
1491
1492 Returns:
1493 True if the checkout succeeded; False if it didn't; None if the branch
1494 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001495 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001496 rev = R_HEADS + name
1497 head = self.work_git.GetHead()
1498 if head == rev:
1499 # Already on the branch
1500 #
1501 return True
Wink Saville02d79452009-04-10 13:01:24 -07001502
David Pursehouse8a68ff92012-09-24 12:15:13 +09001503 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001504 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001505 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001506 except KeyError:
1507 # Branch does not exist in this project
1508 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001509 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001510
1511 if head.startswith(R_HEADS):
1512 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001513 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001514 except KeyError:
1515 head = None
1516
1517 if head == revid:
1518 # Same revision; just update HEAD to point to the new
1519 # target branch, but otherwise take no other action.
1520 #
1521 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1522 'ref: %s%s\n' % (R_HEADS, name))
1523 return True
Wink Saville02d79452009-04-10 13:01:24 -07001524
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001525 return GitCommand(self,
1526 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001527 capture_stdout=True,
1528 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001529
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001530 def AbandonBranch(self, name):
1531 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001532
1533 Args:
1534 name: The name of the branch to abandon.
1535
1536 Returns:
1537 True if the abandon succeeded; False if it didn't; None if the branch
1538 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001539 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001540 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001541 all_refs = self.bare_ref.all
1542 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001543 # Doesn't exist
1544 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001545
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001546 head = self.work_git.GetHead()
1547 if head == rev:
1548 # We can't destroy the branch while we are sitting
1549 # on it. Switch to a detached HEAD.
1550 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001551 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001552
David Pursehouse8a68ff92012-09-24 12:15:13 +09001553 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001554 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001555 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1556 '%s\n' % revid)
1557 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001558 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001559
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001560 return GitCommand(self,
1561 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001562 capture_stdout=True,
1563 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001564
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001565 def PruneHeads(self):
1566 """Prune any topic branches already merged into upstream.
1567 """
1568 cb = self.CurrentBranch
1569 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001570 left = self._allrefs
1571 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001572 if name.startswith(R_HEADS):
1573 name = name[len(R_HEADS):]
1574 if cb is None or name != cb:
1575 kill.append(name)
1576
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001577 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001578 if cb is not None \
1579 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001580 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001581 self.work_git.DetachHead(HEAD)
1582 kill.append(cb)
1583
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001584 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001585 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001586
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001587 try:
1588 self.bare_git.DetachHead(rev)
1589
1590 b = ['branch', '-d']
1591 b.extend(kill)
1592 b = GitCommand(self, b, bare=True,
1593 capture_stdout=True,
1594 capture_stderr=True)
1595 b.Wait()
1596 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001597 if ID_RE.match(old):
1598 self.bare_git.DetachHead(old)
1599 else:
1600 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001601 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001602
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001603 for branch in kill:
1604 if (R_HEADS + branch) not in left:
1605 self.CleanPublishedCache()
1606 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001607
1608 if cb and cb not in kill:
1609 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001610 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001611
1612 kept = []
1613 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001614 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001615 branch = self.GetBranch(branch)
1616 base = branch.LocalMerge
1617 if not base:
1618 base = rev
1619 kept.append(ReviewableBranch(self, branch, base))
1620 return kept
1621
1622
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001623## Submodule Management ##
1624
1625 def GetRegisteredSubprojects(self):
1626 result = []
1627 def rec(subprojects):
1628 if not subprojects:
1629 return
1630 result.extend(subprojects)
1631 for p in subprojects:
1632 rec(p.subprojects)
1633 rec(self.subprojects)
1634 return result
1635
1636 def _GetSubmodules(self):
1637 # Unfortunately we cannot call `git submodule status --recursive` here
1638 # because the working tree might not exist yet, and it cannot be used
1639 # without a working tree in its current implementation.
1640
1641 def get_submodules(gitdir, rev):
1642 # Parse .gitmodules for submodule sub_paths and sub_urls
1643 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1644 if not sub_paths:
1645 return []
1646 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1647 # revision of submodule repository
1648 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1649 submodules = []
1650 for sub_path, sub_url in zip(sub_paths, sub_urls):
1651 try:
1652 sub_rev = sub_revs[sub_path]
1653 except KeyError:
1654 # Ignore non-exist submodules
1655 continue
1656 submodules.append((sub_rev, sub_path, sub_url))
1657 return submodules
1658
1659 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1660 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1661 def parse_gitmodules(gitdir, rev):
1662 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1663 try:
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 except GitError:
1667 return [], []
1668 if p.Wait() != 0:
1669 return [], []
1670
1671 gitmodules_lines = []
1672 fd, temp_gitmodules_path = tempfile.mkstemp()
1673 try:
1674 os.write(fd, p.stdout)
1675 os.close(fd)
1676 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001677 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1678 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001679 if p.Wait() != 0:
1680 return [], []
1681 gitmodules_lines = p.stdout.split('\n')
1682 except GitError:
1683 return [], []
1684 finally:
1685 os.remove(temp_gitmodules_path)
1686
1687 names = set()
1688 paths = {}
1689 urls = {}
1690 for line in gitmodules_lines:
1691 if not line:
1692 continue
1693 m = re_path.match(line)
1694 if m:
1695 names.add(m.group(1))
1696 paths[m.group(1)] = m.group(2)
1697 continue
1698 m = re_url.match(line)
1699 if m:
1700 names.add(m.group(1))
1701 urls[m.group(1)] = m.group(2)
1702 continue
1703 names = sorted(names)
1704 return ([paths.get(name, '') for name in names],
1705 [urls.get(name, '') for name in names])
1706
1707 def git_ls_tree(gitdir, rev, paths):
1708 cmd = ['ls-tree', rev, '--']
1709 cmd.extend(paths)
1710 try:
Anthony King7bdac712014-07-16 12:56:40 +01001711 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1712 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001713 except GitError:
1714 return []
1715 if p.Wait() != 0:
1716 return []
1717 objects = {}
1718 for line in p.stdout.split('\n'):
1719 if not line.strip():
1720 continue
1721 object_rev, object_path = line.split()[2:4]
1722 objects[object_path] = object_rev
1723 return objects
1724
1725 try:
1726 rev = self.GetRevisionId()
1727 except GitError:
1728 return []
1729 return get_submodules(self.gitdir, rev)
1730
1731 def GetDerivedSubprojects(self):
1732 result = []
1733 if not self.Exists:
1734 # If git repo does not exist yet, querying its submodules will
1735 # mess up its states; so return here.
1736 return result
1737 for rev, path, url in self._GetSubmodules():
1738 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001739 relpath, worktree, gitdir, objdir = \
1740 self.manifest.GetSubprojectPaths(self, name, path)
1741 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001742 if project:
1743 result.extend(project.GetDerivedSubprojects())
1744 continue
David James8d201162013-10-11 17:03:19 -07001745
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001746 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001747 url=url,
1748 review=self.remote.review,
1749 revision=self.remote.revision)
1750 subproject = Project(manifest=self.manifest,
1751 name=name,
1752 remote=remote,
1753 gitdir=gitdir,
1754 objdir=objdir,
1755 worktree=worktree,
1756 relpath=relpath,
1757 revisionExpr=self.revisionExpr,
1758 revisionId=rev,
1759 rebase=self.rebase,
1760 groups=self.groups,
1761 sync_c=self.sync_c,
1762 sync_s=self.sync_s,
1763 parent=self,
1764 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001765 result.append(subproject)
1766 result.extend(subproject.GetDerivedSubprojects())
1767 return result
1768
1769
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001770## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001771 def _CheckForSha1(self):
1772 try:
1773 # if revision (sha or tag) is not present then following function
1774 # throws an error.
1775 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1776 return True
1777 except GitError:
1778 # There is no such persistent revision. We have to fetch it.
1779 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780
Julien Campergue335f5ef2013-10-16 11:02:35 +02001781 def _FetchArchive(self, tarpath, cwd=None):
1782 cmd = ['archive', '-v', '-o', tarpath]
1783 cmd.append('--remote=%s' % self.remote.url)
1784 cmd.append('--prefix=%s/' % self.relpath)
1785 cmd.append(self.revisionExpr)
1786
1787 command = GitCommand(self, cmd, cwd=cwd,
1788 capture_stdout=True,
1789 capture_stderr=True)
1790
1791 if command.Wait() != 0:
1792 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1793
Conley Owens80b87fe2014-05-09 17:13:44 -07001794
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001795 def _RemoteFetch(self, name=None,
1796 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001797 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001798 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001799 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001800 no_tags=False,
1801 prune=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001802
1803 is_sha1 = False
1804 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001805 depth = None
1806
1807 # The depth should not be used when fetching to a mirror because
1808 # it will result in a shallow repository that cannot be cloned or
1809 # fetched from.
1810 if not self.manifest.IsMirror:
1811 if self.clone_depth:
1812 depth = self.clone_depth
1813 else:
1814 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001815 # The repo project should never be synced with partial depth
1816 if self.relpath == '.repo/repo':
1817 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001818
Shawn Pearce69e04d82014-01-29 12:48:54 -08001819 if depth:
1820 current_branch_only = True
1821
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001822 if ID_RE.match(self.revisionExpr) is not None:
1823 is_sha1 = True
1824
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001825 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001826 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001827 # this is a tag and its sha1 value should never change
1828 tag_name = self.revisionExpr[len(R_TAGS):]
1829
1830 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001831 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001832 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001833 if is_sha1 and not depth:
1834 # When syncing a specific commit and --depth is not set:
1835 # * if upstream is explicitly specified and is not a sha1, fetch only
1836 # upstream as users expect only upstream to be fetch.
1837 # Note: The commit might not be in upstream in which case the sync
1838 # will fail.
1839 # * otherwise, fetch all branches to make sure we end up with the
1840 # specific commit.
1841 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001842
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001843 if not name:
1844 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001845
1846 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001847 remote = self.GetRemote(name)
1848 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001849 ssh_proxy = True
1850
Shawn O. Pearce88443382010-10-08 10:02:09 +02001851 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001852 if alt_dir and 'objects' == os.path.basename(alt_dir):
1853 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001854 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1855 remote = self.GetRemote(name)
1856
David Pursehouse8a68ff92012-09-24 12:15:13 +09001857 all_refs = self.bare_ref.all
1858 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001859 tmp = set()
1860
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301861 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001862 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001863 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001864 all_refs[r] = ref_id
1865 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001866 continue
1867
David Pursehouse8a68ff92012-09-24 12:15:13 +09001868 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001869 continue
1870
David Pursehouse8a68ff92012-09-24 12:15:13 +09001871 r = 'refs/_alt/%s' % ref_id
1872 all_refs[r] = ref_id
1873 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001874 tmp.add(r)
1875
Shawn O. Pearce88443382010-10-08 10:02:09 +02001876 tmp_packed = ''
1877 old_packed = ''
1878
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301879 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001880 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001881 tmp_packed += line
1882 if r not in tmp:
1883 old_packed += line
1884
1885 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001886 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001887 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001888
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001889 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001890
Conley Owensf97e8382015-01-21 11:12:46 -08001891 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001892 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07001893 else:
1894 # If this repo has shallow objects, then we don't know which refs have
1895 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
1896 # do this with projects that don't have shallow objects, since it is less
1897 # efficient.
1898 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
1899 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07001900
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001901 if quiet:
1902 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001903 if not self.worktree:
1904 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001905 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001906
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001907 # If using depth then we should not get all the tags since they may
1908 # be outside of the depth.
1909 if no_tags or depth:
1910 cmd.append('--no-tags')
1911 else:
1912 cmd.append('--tags')
1913
David Pursehouse74cfd272015-10-14 10:50:15 +09001914 if prune:
1915 cmd.append('--prune')
1916
Conley Owens80b87fe2014-05-09 17:13:44 -07001917 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001918 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001919 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001920 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001921 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001922 spec.append('tag')
1923 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001924
David Pursehouse403b64e2015-04-27 10:41:33 +09001925 if not self.manifest.IsMirror:
1926 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06001927 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001928 # Shallow checkout of a specific commit, fetch from that commit and not
1929 # the heads only as the commit might be deeper in the history.
1930 spec.append(branch)
1931 else:
1932 if is_sha1:
1933 branch = self.upstream
1934 if branch is not None and branch.strip():
1935 if not branch.startswith('refs/'):
1936 branch = R_HEADS + branch
1937 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001938 cmd.extend(spec)
1939
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001940 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001941 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001942 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001943 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001944 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001945 ok = True
1946 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001947 # If needed, run the 'git remote prune' the first time through the loop
1948 elif (not _i and
1949 "error:" in gitcmd.stderr and
1950 "git remote prune" in gitcmd.stderr):
1951 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001952 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001953 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001954 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001955 break
1956 continue
Brian Harring14a66742012-09-28 20:21:57 -07001957 elif current_branch_only and is_sha1 and ret == 128:
1958 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1959 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1960 # abort the optimization attempt and do a full sync.
1961 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001962 elif ret < 0:
1963 # Git died with a signal, exit immediately
1964 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001965 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001966
1967 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001968 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001969 if old_packed != '':
1970 _lwrite(packed_refs, old_packed)
1971 else:
1972 os.remove(packed_refs)
1973 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001974
1975 if is_sha1 and current_branch_only and self.upstream:
1976 # We just synced the upstream given branch; verify we
1977 # got what we wanted, else trigger a second run of all
1978 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001979 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06001980 if not depth:
1981 # Avoid infinite recursion when depth is True (since depth implies
1982 # current_branch_only)
1983 return self._RemoteFetch(name=name, current_branch_only=False,
1984 initial=False, quiet=quiet, alt_dir=alt_dir)
1985 if self.clone_depth:
1986 self.clone_depth = None
1987 return self._RemoteFetch(name=name, current_branch_only=current_branch_only,
1988 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07001989
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001990 return ok
1991
1992 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001993 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001994 return False
1995
1996 remote = self.GetRemote(self.remote.name)
1997 bundle_url = remote.url + '/clone.bundle'
1998 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001999 if GetSchemeFromUrl(bundle_url) not in (
2000 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002001 return False
2002
2003 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2004 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002005
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002006 exist_dst = os.path.exists(bundle_dst)
2007 exist_tmp = os.path.exists(bundle_tmp)
2008
2009 if not initial and not exist_dst and not exist_tmp:
2010 return False
2011
2012 if not exist_dst:
2013 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2014 if not exist_dst:
2015 return False
2016
2017 cmd = ['fetch']
2018 if quiet:
2019 cmd.append('--quiet')
2020 if not self.worktree:
2021 cmd.append('--update-head-ok')
2022 cmd.append(bundle_dst)
2023 for f in remote.fetch:
2024 cmd.append(str(f))
2025 cmd.append('refs/tags/*:refs/tags/*')
2026
2027 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002028 if os.path.exists(bundle_dst):
2029 os.remove(bundle_dst)
2030 if os.path.exists(bundle_tmp):
2031 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002032 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002033
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002034 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002035 if os.path.exists(dstPath):
2036 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07002037
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002038 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002039 if quiet:
2040 cmd += ['--silent']
2041 if os.path.exists(tmpPath):
2042 size = os.stat(tmpPath).st_size
2043 if size >= 1024:
2044 cmd += ['--continue-at', '%d' % (size,)]
2045 else:
2046 os.remove(tmpPath)
2047 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2048 cmd += ['--proxy', os.environ['http_proxy']]
Dan Willemsen0745bb22015-08-17 13:41:45 -07002049 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002050 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002051 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002052 if srcUrl.startswith('persistent-'):
2053 srcUrl = srcUrl[len('persistent-'):]
2054 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002055
Dave Borowitz137d0132015-01-02 11:12:54 -08002056 if IsTrace():
2057 Trace('%s', ' '.join(cmd))
2058 try:
2059 proc = subprocess.Popen(cmd)
2060 except OSError:
2061 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002062
Dave Borowitz137d0132015-01-02 11:12:54 -08002063 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002064
Dave Borowitz137d0132015-01-02 11:12:54 -08002065 if curlret == 22:
2066 # From curl man page:
2067 # 22: HTTP page not retrieved. The requested url was not found or
2068 # returned another error with the HTTP error code being 400 or above.
2069 # This return code only appears if -f, --fail is used.
2070 if not quiet:
2071 print("Server does not provide clone.bundle; ignoring.",
2072 file=sys.stderr)
2073 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002074
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002075 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002076 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002077 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002078 return True
2079 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002080 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002081 return False
2082 else:
2083 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002084
Kris Giesingc8d882a2014-12-23 13:02:32 -08002085 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002086 try:
2087 with open(path) as f:
2088 if f.read(16) == '# v2 git bundle\n':
2089 return True
2090 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002091 if not quiet:
2092 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002093 return False
2094 except OSError:
2095 return False
2096
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002097 def _Checkout(self, rev, quiet=False):
2098 cmd = ['checkout']
2099 if quiet:
2100 cmd.append('-q')
2101 cmd.append(rev)
2102 cmd.append('--')
2103 if GitCommand(self, cmd).Wait() != 0:
2104 if self._allrefs:
2105 raise GitError('%s checkout %s ' % (self.name, rev))
2106
Anthony King7bdac712014-07-16 12:56:40 +01002107 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002108 cmd = ['cherry-pick']
2109 cmd.append(rev)
2110 cmd.append('--')
2111 if GitCommand(self, cmd).Wait() != 0:
2112 if self._allrefs:
2113 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2114
Anthony King7bdac712014-07-16 12:56:40 +01002115 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002116 cmd = ['revert']
2117 cmd.append('--no-edit')
2118 cmd.append(rev)
2119 cmd.append('--')
2120 if GitCommand(self, cmd).Wait() != 0:
2121 if self._allrefs:
2122 raise GitError('%s revert %s ' % (self.name, rev))
2123
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002124 def _ResetHard(self, rev, quiet=True):
2125 cmd = ['reset', '--hard']
2126 if quiet:
2127 cmd.append('-q')
2128 cmd.append(rev)
2129 if GitCommand(self, cmd).Wait() != 0:
2130 raise GitError('%s reset --hard %s ' % (self.name, rev))
2131
Anthony King7bdac712014-07-16 12:56:40 +01002132 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002133 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134 if onto is not None:
2135 cmd.extend(['--onto', onto])
2136 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002137 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002138 raise GitError('%s rebase %s ' % (self.name, upstream))
2139
Pierre Tardy3d125942012-05-04 12:18:12 +02002140 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002141 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002142 if ffonly:
2143 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002144 if GitCommand(self, cmd).Wait() != 0:
2145 raise GitError('%s merge %s ' % (self.name, head))
2146
Kevin Degiabaa7f32014-11-12 11:27:45 -07002147 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002148 init_git_dir = not os.path.exists(self.gitdir)
2149 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002150 try:
2151 # Initialize the bare repository, which contains all of the objects.
2152 if init_obj_dir:
2153 os.makedirs(self.objdir)
2154 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002155
Kevin Degib1a07b82015-07-27 13:33:43 -06002156 # If we have a separate directory to hold refs, initialize it as well.
2157 if self.objdir != self.gitdir:
2158 if init_git_dir:
2159 os.makedirs(self.gitdir)
Kevin Degi384b3c52014-10-16 16:02:58 -06002160
Kevin Degib1a07b82015-07-27 13:33:43 -06002161 if init_obj_dir or init_git_dir:
2162 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2163 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002164 try:
2165 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2166 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002167 if force_sync:
David Pursehouse25857b82015-08-19 18:06:22 +09002168 print("Retrying clone after deleting %s" % self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002169 try:
2170 shutil.rmtree(os.path.realpath(self.gitdir))
2171 if self.worktree and os.path.exists(
2172 os.path.realpath(self.worktree)):
2173 shutil.rmtree(os.path.realpath(self.worktree))
2174 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2175 except:
2176 raise e
2177 raise e
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002178
Kevin Degib1a07b82015-07-27 13:33:43 -06002179 if init_git_dir:
2180 mp = self.manifest.manifestProject
2181 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002182
Kevin Degib1a07b82015-07-27 13:33:43 -06002183 if ref_dir or mirror_git:
2184 if not mirror_git:
2185 mirror_git = os.path.join(ref_dir, self.name + '.git')
2186 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2187 self.relpath + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002188
Kevin Degib1a07b82015-07-27 13:33:43 -06002189 if os.path.exists(mirror_git):
2190 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002191
Kevin Degib1a07b82015-07-27 13:33:43 -06002192 elif os.path.exists(repo_git):
2193 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002194
Kevin Degib1a07b82015-07-27 13:33:43 -06002195 else:
2196 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002197
Kevin Degib1a07b82015-07-27 13:33:43 -06002198 if ref_dir:
2199 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2200 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002201
Kevin Degib1a07b82015-07-27 13:33:43 -06002202 self._UpdateHooks()
Jimmie Westera0444582012-10-24 13:44:42 +02002203
Kevin Degib1a07b82015-07-27 13:33:43 -06002204 m = self.manifest.manifestProject.config
2205 for key in ['user.name', 'user.email']:
2206 if m.Has(key, include_defaults=False):
2207 self.config.SetString(key, m.GetString(key))
2208 if self.manifest.IsMirror:
2209 self.config.SetString('core.bare', 'true')
2210 else:
2211 self.config.SetString('core.bare', None)
2212 except Exception:
2213 if init_obj_dir and os.path.exists(self.objdir):
2214 shutil.rmtree(self.objdir)
2215 if init_git_dir and os.path.exists(self.gitdir):
2216 shutil.rmtree(self.gitdir)
2217 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002218
Jimmie Westera0444582012-10-24 13:44:42 +02002219 def _UpdateHooks(self):
2220 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002221 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002222
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002223 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002224 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002225 if not os.path.exists(hooks):
2226 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002227 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002228 name = os.path.basename(stock_hook)
2229
Victor Boivie65e0f352011-04-18 11:23:29 +02002230 if name in ('commit-msg',) and not self.remote.review \
2231 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002232 # Don't install a Gerrit Code Review hook if this
2233 # project does not appear to use it for reviews.
2234 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002235 # Since the manifest project is one of those, but also
2236 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002237 continue
2238
2239 dst = os.path.join(hooks, name)
2240 if os.path.islink(dst):
2241 continue
2242 if os.path.exists(dst):
2243 if filecmp.cmp(stock_hook, dst, shallow=False):
2244 os.remove(dst)
2245 else:
David Pursehousedc2545c2015-08-24 14:43:45 +09002246 _warn("%s: Not replacing locally modified %s hook", self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002247 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002248 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002249 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002250 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002251 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002252 raise GitError('filesystem must support symlinks')
2253 else:
2254 raise
2255
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002256 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002257 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002258 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002259 remote.url = self.remote.url
2260 remote.review = self.remote.review
2261 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002262
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002263 if self.worktree:
2264 remote.ResetFetch(mirror=False)
2265 else:
2266 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002267 remote.Save()
2268
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002269 def _InitMRef(self):
2270 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002271 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002272
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002273 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002274 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002275
2276 def _InitAnyMRef(self, ref):
2277 cur = self.bare_ref.symref(ref)
2278
2279 if self.revisionId:
2280 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2281 msg = 'manifest set to %s' % self.revisionId
2282 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002283 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002284 else:
2285 remote = self.GetRemote(self.remote.name)
2286 dst = remote.ToLocal(self.revisionExpr)
2287 if cur != dst:
2288 msg = 'manifest set to %s' % self.revisionExpr
2289 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002290
Kevin Degi384b3c52014-10-16 16:02:58 -06002291 def _CheckDirReference(self, srcdir, destdir, share_refs):
2292 symlink_files = self.shareable_files
2293 symlink_dirs = self.shareable_dirs
2294 if share_refs:
2295 symlink_files += self.working_tree_files
2296 symlink_dirs += self.working_tree_dirs
2297 to_symlink = symlink_files + symlink_dirs
2298 for name in set(to_symlink):
2299 dst = os.path.realpath(os.path.join(destdir, name))
2300 if os.path.lexists(dst):
2301 src = os.path.realpath(os.path.join(srcdir, name))
2302 # Fail if the links are pointing to the wrong place
2303 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002304 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002305 'work tree. If you\'re comfortable with the '
2306 'possibility of losing the work tree\'s git metadata,'
2307 ' use `repo sync --force-sync {0}` to '
2308 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002309
David James8d201162013-10-11 17:03:19 -07002310 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2311 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2312
2313 Args:
2314 gitdir: The bare git repository. Must already be initialized.
2315 dotgit: The repository you would like to initialize.
2316 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2317 Only one work tree can store refs under a given |gitdir|.
2318 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2319 This saves you the effort of initializing |dotgit| yourself.
2320 """
Kevin Degi384b3c52014-10-16 16:02:58 -06002321 symlink_files = self.shareable_files
2322 symlink_dirs = self.shareable_dirs
David James8d201162013-10-11 17:03:19 -07002323 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002324 symlink_files += self.working_tree_files
2325 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002326 to_symlink = symlink_files + symlink_dirs
2327
2328 to_copy = []
2329 if copy_all:
2330 to_copy = os.listdir(gitdir)
2331
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002332 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002333 for name in set(to_copy).union(to_symlink):
2334 try:
2335 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002336 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002337
Kevin Degi384b3c52014-10-16 16:02:58 -06002338 if os.path.lexists(dst):
2339 continue
David James8d201162013-10-11 17:03:19 -07002340
2341 # If the source dir doesn't exist, create an empty dir.
2342 if name in symlink_dirs and not os.path.lexists(src):
2343 os.makedirs(src)
2344
Conley Owens80b87fe2014-05-09 17:13:44 -07002345 # If the source file doesn't exist, ensure the destination
2346 # file doesn't either.
2347 if name in symlink_files and not os.path.lexists(src):
2348 try:
2349 os.remove(dst)
2350 except OSError:
2351 pass
2352
David James8d201162013-10-11 17:03:19 -07002353 if name in to_symlink:
2354 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2355 elif copy_all and not os.path.islink(dst):
2356 if os.path.isdir(src):
2357 shutil.copytree(src, dst)
2358 elif os.path.isfile(src):
2359 shutil.copy(src, dst)
2360 except OSError as e:
2361 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002362 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002363 else:
2364 raise
2365
Kevin Degiabaa7f32014-11-12 11:27:45 -07002366 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002367 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002368 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002369 try:
2370 if init_dotgit:
2371 os.makedirs(dotgit)
2372 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2373 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002374
Kevin Degiabaa7f32014-11-12 11:27:45 -07002375 try:
2376 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2377 except GitError as e:
2378 if force_sync:
2379 try:
2380 shutil.rmtree(dotgit)
2381 return self._InitWorkTree(force_sync=False)
2382 except:
2383 raise e
2384 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002385
Kevin Degib1a07b82015-07-27 13:33:43 -06002386 if init_dotgit:
2387 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002388
Kevin Degib1a07b82015-07-27 13:33:43 -06002389 cmd = ['read-tree', '--reset', '-u']
2390 cmd.append('-v')
2391 cmd.append(HEAD)
2392 if GitCommand(self, cmd).Wait() != 0:
2393 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002394
Kevin Degib1a07b82015-07-27 13:33:43 -06002395 self._CopyAndLinkFiles()
2396 except Exception:
2397 if init_dotgit:
2398 shutil.rmtree(dotgit)
2399 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002400
2401 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002402 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002403
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002404 def _revlist(self, *args, **kw):
2405 a = []
2406 a.extend(args)
2407 a.append('--')
2408 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002409
2410 @property
2411 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002412 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002413
Julien Camperguedd654222014-01-09 16:21:37 +01002414 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2415 """Get logs between two revisions of this project."""
2416 comp = '..'
2417 if rev1:
2418 revs = [rev1]
2419 if rev2:
2420 revs.extend([comp, rev2])
2421 cmd = ['log', ''.join(revs)]
2422 out = DiffColoring(self.config)
2423 if out.is_on and color:
2424 cmd.append('--color')
2425 if oneline:
2426 cmd.append('--oneline')
2427
2428 try:
2429 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2430 if log.Wait() == 0:
2431 return log.stdout
2432 except GitError:
2433 # worktree may not exist if groups changed for example. In that case,
2434 # try in gitdir instead.
2435 if not os.path.exists(self.worktree):
2436 return self.bare_git.log(*cmd[1:])
2437 else:
2438 raise
2439 return None
2440
2441 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2442 """Get the list of logs from this revision to given revisionId"""
2443 logs = {}
2444 selfId = self.GetRevisionId(self._allrefs)
2445 toId = toProject.GetRevisionId(toProject._allrefs)
2446
2447 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2448 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2449 return logs
2450
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002451 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002452 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002453 self._project = project
2454 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002455 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002456
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002457 def LsOthers(self):
2458 p = GitCommand(self._project,
2459 ['ls-files',
2460 '-z',
2461 '--others',
2462 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002463 bare=False,
David James8d201162013-10-11 17:03:19 -07002464 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002465 capture_stdout=True,
2466 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002467 if p.Wait() == 0:
2468 out = p.stdout
2469 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002470 return out[:-1].split('\0') # pylint: disable=W1401
2471 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002472 return []
2473
2474 def DiffZ(self, name, *args):
2475 cmd = [name]
2476 cmd.append('-z')
2477 cmd.extend(args)
2478 p = GitCommand(self._project,
2479 cmd,
David James8d201162013-10-11 17:03:19 -07002480 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002481 bare=False,
2482 capture_stdout=True,
2483 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002484 try:
2485 out = p.process.stdout.read()
2486 r = {}
2487 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002488 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002489 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002490 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002491 info = next(out)
2492 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002493 except StopIteration:
2494 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002495
2496 class _Info(object):
2497 def __init__(self, path, omode, nmode, oid, nid, state):
2498 self.path = path
2499 self.src_path = None
2500 self.old_mode = omode
2501 self.new_mode = nmode
2502 self.old_id = oid
2503 self.new_id = nid
2504
2505 if len(state) == 1:
2506 self.status = state
2507 self.level = None
2508 else:
2509 self.status = state[:1]
2510 self.level = state[1:]
2511 while self.level.startswith('0'):
2512 self.level = self.level[1:]
2513
2514 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002515 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002516 if info.status in ('R', 'C'):
2517 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002518 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002519 r[info.path] = info
2520 return r
2521 finally:
2522 p.Wait()
2523
2524 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002525 if self._bare:
2526 path = os.path.join(self._project.gitdir, HEAD)
2527 else:
2528 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002529 try:
2530 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002531 except IOError as e:
2532 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002533 try:
2534 line = fd.read()
2535 finally:
2536 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302537 try:
2538 line = line.decode()
2539 except AttributeError:
2540 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002541 if line.startswith('ref: '):
2542 return line[5:-1]
2543 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002544
2545 def SetHead(self, ref, message=None):
2546 cmdv = []
2547 if message is not None:
2548 cmdv.extend(['-m', message])
2549 cmdv.append(HEAD)
2550 cmdv.append(ref)
2551 self.symbolic_ref(*cmdv)
2552
2553 def DetachHead(self, new, message=None):
2554 cmdv = ['--no-deref']
2555 if message is not None:
2556 cmdv.extend(['-m', message])
2557 cmdv.append(HEAD)
2558 cmdv.append(new)
2559 self.update_ref(*cmdv)
2560
2561 def UpdateRef(self, name, new, old=None,
2562 message=None,
2563 detach=False):
2564 cmdv = []
2565 if message is not None:
2566 cmdv.extend(['-m', message])
2567 if detach:
2568 cmdv.append('--no-deref')
2569 cmdv.append(name)
2570 cmdv.append(new)
2571 if old is not None:
2572 cmdv.append(old)
2573 self.update_ref(*cmdv)
2574
2575 def DeleteRef(self, name, old=None):
2576 if not old:
2577 old = self.rev_parse(name)
2578 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002579 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002580
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002581 def rev_list(self, *args, **kw):
2582 if 'format' in kw:
2583 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2584 else:
2585 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002586 cmdv.extend(args)
2587 p = GitCommand(self._project,
2588 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002589 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002590 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002591 capture_stdout=True,
2592 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002593 r = []
2594 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002595 if line[-1] == '\n':
2596 line = line[:-1]
2597 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002598 if p.Wait() != 0:
2599 raise GitError('%s rev-list %s: %s' % (
2600 self._project.name,
2601 str(args),
2602 p.stderr))
2603 return r
2604
2605 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002606 """Allow arbitrary git commands using pythonic syntax.
2607
2608 This allows you to do things like:
2609 git_obj.rev_parse('HEAD')
2610
2611 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2612 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002613 Any other positional arguments will be passed to the git command, and the
2614 following keyword arguments are supported:
2615 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002616
2617 Args:
2618 name: The name of the git command to call. Any '_' characters will
2619 be replaced with '-'.
2620
2621 Returns:
2622 A callable object that will try to call git with the named command.
2623 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002624 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002625 def runner(*args, **kwargs):
2626 cmdv = []
2627 config = kwargs.pop('config', None)
2628 for k in kwargs:
2629 raise TypeError('%s() got an unexpected keyword argument %r'
2630 % (name, k))
2631 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002632 if not git_require((1, 7, 2)):
2633 raise ValueError('cannot set config on command line for %s()'
2634 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302635 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002636 cmdv.append('-c')
2637 cmdv.append('%s=%s' % (k, v))
2638 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002639 cmdv.extend(args)
2640 p = GitCommand(self._project,
2641 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002642 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002643 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002644 capture_stdout=True,
2645 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002646 if p.Wait() != 0:
2647 raise GitError('%s %s: %s' % (
2648 self._project.name,
2649 name,
2650 p.stderr))
2651 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302652 try:
Conley Owensedd01512013-09-26 12:59:58 -07002653 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302654 except AttributeError:
2655 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002656 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2657 return r[:-1]
2658 return r
2659 return runner
2660
2661
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002662class _PriorSyncFailedError(Exception):
2663 def __str__(self):
2664 return 'prior sync failed; rebase still in progress'
2665
2666class _DirtyError(Exception):
2667 def __str__(self):
2668 return 'contains uncommitted changes'
2669
2670class _InfoMessage(object):
2671 def __init__(self, project, text):
2672 self.project = project
2673 self.text = text
2674
2675 def Print(self, syncbuf):
2676 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2677 syncbuf.out.nl()
2678
2679class _Failure(object):
2680 def __init__(self, project, why):
2681 self.project = project
2682 self.why = why
2683
2684 def Print(self, syncbuf):
2685 syncbuf.out.fail('error: %s/: %s',
2686 self.project.relpath,
2687 str(self.why))
2688 syncbuf.out.nl()
2689
2690class _Later(object):
2691 def __init__(self, project, action):
2692 self.project = project
2693 self.action = action
2694
2695 def Run(self, syncbuf):
2696 out = syncbuf.out
2697 out.project('project %s/', self.project.relpath)
2698 out.nl()
2699 try:
2700 self.action()
2701 out.nl()
2702 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002703 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002704 out.nl()
2705 return False
2706
2707class _SyncColoring(Coloring):
2708 def __init__(self, config):
2709 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002710 self.project = self.printer('header', attr='bold')
2711 self.info = self.printer('info')
2712 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002713
2714class SyncBuffer(object):
2715 def __init__(self, config, detach_head=False):
2716 self._messages = []
2717 self._failures = []
2718 self._later_queue1 = []
2719 self._later_queue2 = []
2720
2721 self.out = _SyncColoring(config)
2722 self.out.redirect(sys.stderr)
2723
2724 self.detach_head = detach_head
2725 self.clean = True
2726
2727 def info(self, project, fmt, *args):
2728 self._messages.append(_InfoMessage(project, fmt % args))
2729
2730 def fail(self, project, err=None):
2731 self._failures.append(_Failure(project, err))
2732 self.clean = False
2733
2734 def later1(self, project, what):
2735 self._later_queue1.append(_Later(project, what))
2736
2737 def later2(self, project, what):
2738 self._later_queue2.append(_Later(project, what))
2739
2740 def Finish(self):
2741 self._PrintMessages()
2742 self._RunLater()
2743 self._PrintMessages()
2744 return self.clean
2745
2746 def _RunLater(self):
2747 for q in ['_later_queue1', '_later_queue2']:
2748 if not self._RunQueue(q):
2749 return
2750
2751 def _RunQueue(self, queue):
2752 for m in getattr(self, queue):
2753 if not m.Run(self):
2754 self.clean = False
2755 return False
2756 setattr(self, queue, [])
2757 return True
2758
2759 def _PrintMessages(self):
2760 for m in self._messages:
2761 m.Print(self)
2762 for m in self._failures:
2763 m.Print(self)
2764
2765 self._messages = []
2766 self._failures = []
2767
2768
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002769class MetaProject(Project):
2770 """A special project housed under .repo.
2771 """
2772 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002773 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002774 manifest=manifest,
2775 name=name,
2776 gitdir=gitdir,
2777 objdir=gitdir,
2778 worktree=worktree,
2779 remote=RemoteSpec('origin'),
2780 relpath='.repo/%s' % name,
2781 revisionExpr='refs/heads/master',
2782 revisionId=None,
2783 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002784
2785 def PreSync(self):
2786 if self.Exists:
2787 cb = self.CurrentBranch
2788 if cb:
2789 base = self.GetBranch(cb).merge
2790 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002791 self.revisionExpr = base
2792 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002793
Anthony King7bdac712014-07-16 12:56:40 +01002794 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002795 """ Prepare MetaProject for manifest branch switch
2796 """
2797
2798 # detach and delete manifest branch, allowing a new
2799 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002800 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002801 self.Sync_LocalHalf(syncbuf)
2802 syncbuf.Finish()
2803
2804 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002805 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002806 capture_stdout=True,
2807 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002808
2809
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002810 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002811 def LastFetch(self):
2812 try:
2813 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2814 return os.path.getmtime(fh)
2815 except OSError:
2816 return 0
2817
2818 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002819 def HasChanges(self):
2820 """Has the remote received new commits not yet checked out?
2821 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002822 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002823 return False
2824
David Pursehouse8a68ff92012-09-24 12:15:13 +09002825 all_refs = self.bare_ref.all
2826 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002827 head = self.work_git.GetHead()
2828 if head.startswith(R_HEADS):
2829 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002830 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002831 except KeyError:
2832 head = None
2833
2834 if revid == head:
2835 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002836 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002837 return True
2838 return False