blob: 4f2b203ad92715116827f8d7cf271ced6d0fe2f7 [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
Wink Saville4c426ef2015-06-03 08:05:17 -0700252 if os.path.exists(absDest):
253 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,
575 optimized_fetch=False):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800576 """Init a Project object.
577
578 Args:
579 manifest: The XmlManifest object.
580 name: The `name` attribute of manifest.xml's project element.
581 remote: RemoteSpec object specifying its remote's properties.
582 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700583 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800584 worktree: Absolute path of git working tree.
585 relpath: Relative path of git working tree to repo's top directory.
586 revisionExpr: The `revision` attribute of manifest.xml's project element.
587 revisionId: git commit id for checking out.
588 rebase: The `rebase` attribute of manifest.xml's project element.
589 groups: The `groups` attribute of manifest.xml's project element.
590 sync_c: The `sync-c` attribute of manifest.xml's project element.
591 sync_s: The `sync-s` attribute of manifest.xml's project element.
592 upstream: The `upstream` attribute of manifest.xml's project element.
593 parent: The parent Project object.
594 is_derived: False if the project was explicitly defined in the manifest;
595 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400596 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900597 optimized_fetch: If True, when a project is set to a sha1 revision, only
598 fetch from the remote if the sha1 is not present locally.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800599 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700600 self.manifest = manifest
601 self.name = name
602 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800603 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700604 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800605 if worktree:
606 self.worktree = worktree.replace('\\', '/')
607 else:
608 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700609 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700610 self.revisionExpr = revisionExpr
611
612 if revisionId is None \
613 and revisionExpr \
614 and IsId(revisionExpr):
615 self.revisionId = revisionExpr
616 else:
617 self.revisionId = revisionId
618
Mike Pontillod3153822012-02-28 11:53:24 -0800619 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700620 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700621 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800622 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900623 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700624 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800625 self.parent = parent
626 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900627 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800628 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800629
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700630 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700631 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500632 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500633 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700634 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100635 gitdir=self.gitdir,
636 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700637
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800638 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700639 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800640 else:
641 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700642 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700643 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700644 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400645 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700646
Doug Anderson37282b42011-03-04 11:54:18 -0800647 # This will be filled in if a project is later identified to be the
648 # project containing repo hooks.
649 self.enabled_repo_hooks = []
650
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700651 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800652 def Derived(self):
653 return self.is_derived
654
655 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700656 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600657 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700658
659 @property
660 def CurrentBranch(self):
661 """Obtain the name of the currently checked out branch.
662 The branch name omits the 'refs/heads/' prefix.
663 None is returned if the project is on a detached HEAD.
664 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700665 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700666 if b.startswith(R_HEADS):
667 return b[len(R_HEADS):]
668 return None
669
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700670 def IsRebaseInProgress(self):
671 w = self.worktree
672 g = os.path.join(w, '.git')
673 return os.path.exists(os.path.join(g, 'rebase-apply')) \
674 or os.path.exists(os.path.join(g, 'rebase-merge')) \
675 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200676
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700677 def IsDirty(self, consider_untracked=True):
678 """Is the working directory modified in some way?
679 """
680 self.work_git.update_index('-q',
681 '--unmerged',
682 '--ignore-missing',
683 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900684 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700685 return True
686 if self.work_git.DiffZ('diff-files'):
687 return True
688 if consider_untracked and self.work_git.LsOthers():
689 return True
690 return False
691
692 _userident_name = None
693 _userident_email = None
694
695 @property
696 def UserName(self):
697 """Obtain the user's personal name.
698 """
699 if self._userident_name is None:
700 self._LoadUserIdentity()
701 return self._userident_name
702
703 @property
704 def UserEmail(self):
705 """Obtain the user's email address. This is very likely
706 to be their Gerrit login.
707 """
708 if self._userident_email is None:
709 self._LoadUserIdentity()
710 return self._userident_email
711
712 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900713 u = self.bare_git.var('GIT_COMMITTER_IDENT')
714 m = re.compile("^(.*) <([^>]*)> ").match(u)
715 if m:
716 self._userident_name = m.group(1)
717 self._userident_email = m.group(2)
718 else:
719 self._userident_name = ''
720 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700721
722 def GetRemote(self, name):
723 """Get the configuration for a single remote.
724 """
725 return self.config.GetRemote(name)
726
727 def GetBranch(self, name):
728 """Get the configuration for a single branch.
729 """
730 return self.config.GetBranch(name)
731
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700732 def GetBranches(self):
733 """Get all existing local branches.
734 """
735 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900736 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700737 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700738
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530739 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700740 if name.startswith(R_HEADS):
741 name = name[len(R_HEADS):]
742 b = self.GetBranch(name)
743 b.current = name == current
744 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900745 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700746 heads[name] = b
747
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530748 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700749 if name.startswith(R_PUB):
750 name = name[len(R_PUB):]
751 b = heads.get(name)
752 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900753 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700754
755 return heads
756
Colin Cross5acde752012-03-28 20:15:45 -0700757 def MatchesGroups(self, manifest_groups):
758 """Returns true if the manifest groups specified at init should cause
759 this project to be synced.
760 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700761 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700762
763 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700764 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700765 manifest_groups: "-group1,group2"
766 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500767
768 The special manifest group "default" will match any project that
769 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700770 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500771 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700772 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500773 if not 'notdefault' in expanded_project_groups:
774 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700775
Conley Owens971de8e2012-04-16 10:36:08 -0700776 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700777 for group in expanded_manifest_groups:
778 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700779 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700780 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700781 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700782
Conley Owens971de8e2012-04-16 10:36:08 -0700783 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784
785## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700786 def UncommitedFiles(self, get_all=True):
787 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700789 Args:
790 get_all: a boolean, if True - get information about all different
791 uncommitted files. If False - return as soon as any kind of
792 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500793 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700794 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500795 self.work_git.update_index('-q',
796 '--unmerged',
797 '--ignore-missing',
798 '--refresh')
799 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700800 details.append("rebase in progress")
801 if not get_all:
802 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500803
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700804 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
805 if changes:
806 details.extend(changes)
807 if not get_all:
808 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500809
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700810 changes = self.work_git.DiffZ('diff-files').keys()
811 if changes:
812 details.extend(changes)
813 if not get_all:
814 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500815
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700816 changes = self.work_git.LsOthers()
817 if changes:
818 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500819
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700820 return details
821
822 def HasChanges(self):
823 """Returns true if there are uncommitted changes.
824 """
825 if self.UncommitedFiles(get_all=False):
826 return True
827 else:
828 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500829
Terence Haddock4655e812011-03-31 12:33:34 +0200830 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700831 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200832
833 Args:
834 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835 """
836 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200837 if output_redir == None:
838 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700839 print(file=output_redir)
840 print('project %s/' % self.relpath, file=output_redir)
841 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700842 return
843
844 self.work_git.update_index('-q',
845 '--unmerged',
846 '--ignore-missing',
847 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700848 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
850 df = self.work_git.DiffZ('diff-files')
851 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100852 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700853 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854
855 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200856 if not output_redir == None:
857 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700858 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700859
860 branch = self.CurrentBranch
861 if branch is None:
862 out.nobranch('(*** NO BRANCH ***)')
863 else:
864 out.branch('branch %s', branch)
865 out.nl()
866
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700867 if rb:
868 out.important('prior sync failed; rebase still in progress')
869 out.nl()
870
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871 paths = list()
872 paths.extend(di.keys())
873 paths.extend(df.keys())
874 paths.extend(do)
875
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530876 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900877 try:
878 i = di[p]
879 except KeyError:
880 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900882 try:
883 f = df[p]
884 except KeyError:
885 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200886
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900887 if i:
888 i_status = i.status.upper()
889 else:
890 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700891
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900892 if f:
893 f_status = f.status.lower()
894 else:
895 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896
897 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800898 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899 i.src_path, p, i.level)
900 else:
901 line = ' %s%s\t%s' % (i_status, f_status, p)
902
903 if i and not f:
904 out.added('%s', line)
905 elif (i and f) or (not i and f):
906 out.changed('%s', line)
907 elif not i and not f:
908 out.untracked('%s', line)
909 else:
910 out.write('%s', line)
911 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200912
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700913 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914
pelyad67872d2012-03-28 14:49:58 +0300915 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916 """Prints the status of the repository to stdout.
917 """
918 out = DiffColoring(self.config)
919 cmd = ['diff']
920 if out.is_on:
921 cmd.append('--color')
922 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300923 if absolute_paths:
924 cmd.append('--src-prefix=a/%s/' % self.relpath)
925 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926 cmd.append('--')
927 p = GitCommand(self,
928 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100929 capture_stdout=True,
930 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931 has_diff = False
932 for line in p.process.stdout:
933 if not has_diff:
934 out.nl()
935 out.project('project %s/' % self.relpath)
936 out.nl()
937 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700938 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 p.Wait()
940
941
942## Publish / Upload ##
943
David Pursehouse8a68ff92012-09-24 12:15:13 +0900944 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945 """Was the branch published (uploaded) for code review?
946 If so, returns the SHA-1 hash of the last published
947 state for the branch.
948 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700949 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900950 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700951 try:
952 return self.bare_git.rev_parse(key)
953 except GitError:
954 return None
955 else:
956 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900957 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700958 except KeyError:
959 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700960
David Pursehouse8a68ff92012-09-24 12:15:13 +0900961 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 """Prunes any stale published refs.
963 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900964 if all_refs is None:
965 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966 heads = set()
967 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530968 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 if name.startswith(R_HEADS):
970 heads.add(name)
971 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900972 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530974 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700975 n = name[len(R_PUB):]
976 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900977 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700979 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980 """List any branches which can be uploaded for review.
981 """
982 heads = {}
983 pubed = {}
984
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530985 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900987 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700988 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900989 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990
991 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530992 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900993 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700994 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700995 if selected_branch and branch != selected_branch:
996 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800998 rb = self.GetUploadableBranch(branch)
999 if rb:
1000 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001 return ready
1002
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001003 def GetUploadableBranch(self, branch_name):
1004 """Get a single uploadable branch, or None.
1005 """
1006 branch = self.GetBranch(branch_name)
1007 base = branch.LocalMerge
1008 if branch.LocalMerge:
1009 rb = ReviewableBranch(self, branch, base)
1010 if rb.commits:
1011 return rb
1012 return None
1013
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001014 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001015 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001016 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001017 draft=False,
1018 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001019 """Uploads the named branch for code review.
1020 """
1021 if branch is None:
1022 branch = self.CurrentBranch
1023 if branch is None:
1024 raise GitError('not currently on a branch')
1025
1026 branch = self.GetBranch(branch)
1027 if not branch.LocalMerge:
1028 raise GitError('branch %s does not track a remote' % branch.name)
1029 if not branch.remote.review:
1030 raise GitError('remote %s has no review url' % branch.remote.name)
1031
Bryan Jacobsf609f912013-05-06 13:36:24 -04001032 if dest_branch is None:
1033 dest_branch = self.dest_branch
1034 if dest_branch is None:
1035 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036 if not dest_branch.startswith(R_HEADS):
1037 dest_branch = R_HEADS + dest_branch
1038
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001039 if not branch.remote.projectname:
1040 branch.remote.projectname = self.name
1041 branch.remote.Save()
1042
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001043 url = branch.remote.ReviewUrl(self.UserEmail)
1044 if url is None:
1045 raise UploadError('review not configured')
1046 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001047
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001048 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001049 rp = ['gerrit receive-pack']
1050 for e in people[0]:
1051 rp.append('--reviewer=%s' % sq(e))
1052 for e in people[1]:
1053 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001054 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001055
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001056 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001057
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001058 if dest_branch.startswith(R_HEADS):
1059 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001060
1061 upload_type = 'for'
1062 if draft:
1063 upload_type = 'drafts'
1064
1065 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1066 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001067 if auto_topic:
1068 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001069 if not url.startswith('ssh://'):
1070 rp = ['r=%s' % p for p in people[0]] + \
1071 ['cc=%s' % p for p in people[1]]
1072 if rp:
1073 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001074 cmd.append(ref_spec)
1075
Anthony King7bdac712014-07-16 12:56:40 +01001076 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001077 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078
1079 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1080 self.bare_git.UpdateRef(R_PUB + branch.name,
1081 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001082 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083
1084
1085## Sync ##
1086
Julien Campergue335f5ef2013-10-16 11:02:35 +02001087 def _ExtractArchive(self, tarpath, path=None):
1088 """Extract the given tar on its current location
1089
1090 Args:
1091 - tarpath: The path to the actual tar file
1092
1093 """
1094 try:
1095 with tarfile.open(tarpath, 'r') as tar:
1096 tar.extractall(path=path)
1097 return True
1098 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001099 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001100 return False
1101
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001102 def Sync_NetworkHalf(self,
1103 quiet=False,
1104 is_new=None,
1105 current_branch_only=False,
Kevin Degiabaa7f32014-11-12 11:27:45 -07001106 force_sync=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001107 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001108 no_tags=False,
David Pursehouseb1553542014-09-04 21:28:09 +09001109 archive=False,
1110 optimized_fetch=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111 """Perform only the network IO portion of the sync process.
1112 Local working directory/branch state is not affected.
1113 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001114 if archive and not isinstance(self, MetaProject):
1115 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001116 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001117 return False
1118
1119 name = self.relpath.replace('\\', '/')
1120 name = name.replace('/', '_')
1121 tarpath = '%s.tar' % name
1122 topdir = self.manifest.topdir
1123
1124 try:
1125 self._FetchArchive(tarpath, cwd=topdir)
1126 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001127 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001128 return False
1129
1130 # From now on, we only need absolute tarpath
1131 tarpath = os.path.join(topdir, tarpath)
1132
1133 if not self._ExtractArchive(tarpath, path=topdir):
1134 return False
1135 try:
1136 os.remove(tarpath)
1137 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001138 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001139 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001140 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001141 if is_new is None:
1142 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001143 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001144 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001145 else:
1146 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001147 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001148
1149 if is_new:
1150 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1151 try:
1152 fd = open(alt, 'rb')
1153 try:
1154 alt_dir = fd.readline().rstrip()
1155 finally:
1156 fd.close()
1157 except IOError:
1158 alt_dir = None
1159 else:
1160 alt_dir = None
1161
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001162 if clone_bundle \
1163 and alt_dir is None \
1164 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001165 is_new = False
1166
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001167 if not current_branch_only:
1168 if self.sync_c:
1169 current_branch_only = True
1170 elif not self.manifest._loaded:
1171 # Manifest cannot check defaults until it syncs.
1172 current_branch_only = False
1173 elif self.manifest.default.sync_c:
1174 current_branch_only = True
1175
David Pursehouseb1553542014-09-04 21:28:09 +09001176 need_to_fetch = not (optimized_fetch and \
1177 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1178 if (need_to_fetch
Conley Owens666d5342014-05-01 13:09:57 -07001179 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1180 current_branch_only=current_branch_only,
1181 no_tags=no_tags)):
Anthony King7bdac712014-07-16 12:56:40 +01001182 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001183
1184 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001185 self._InitMRef()
1186 else:
1187 self._InitMirrorHead()
1188 try:
1189 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1190 except OSError:
1191 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001193
1194 def PostRepoUpgrade(self):
1195 self._InitHooks()
1196
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001197 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001198 for copyfile in self.copyfiles:
1199 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001200 for linkfile in self.linkfiles:
1201 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001202
Julien Camperguedd654222014-01-09 16:21:37 +01001203 def GetCommitRevisionId(self):
1204 """Get revisionId of a commit.
1205
1206 Use this method instead of GetRevisionId to get the id of the commit rather
1207 than the id of the current git object (for example, a tag)
1208
1209 """
1210 if not self.revisionExpr.startswith(R_TAGS):
1211 return self.GetRevisionId(self._allrefs)
1212
1213 try:
1214 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1215 except GitError:
1216 raise ManifestInvalidRevisionError(
1217 'revision %s in %s not found' % (self.revisionExpr,
1218 self.name))
1219
David Pursehouse8a68ff92012-09-24 12:15:13 +09001220 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001221 if self.revisionId:
1222 return self.revisionId
1223
1224 rem = self.GetRemote(self.remote.name)
1225 rev = rem.ToLocal(self.revisionExpr)
1226
David Pursehouse8a68ff92012-09-24 12:15:13 +09001227 if all_refs is not None and rev in all_refs:
1228 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001229
1230 try:
1231 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1232 except GitError:
1233 raise ManifestInvalidRevisionError(
1234 'revision %s in %s not found' % (self.revisionExpr,
1235 self.name))
1236
Kevin Degiabaa7f32014-11-12 11:27:45 -07001237 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001238 """Perform only the local IO portion of the sync process.
1239 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001240 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001241 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001242 all_refs = self.bare_ref.all
1243 self.CleanPublishedCache(all_refs)
1244 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001245
David Pursehouse1d947b32012-10-25 12:23:11 +09001246 def _doff():
1247 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001248 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001249
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001250 head = self.work_git.GetHead()
1251 if head.startswith(R_HEADS):
1252 branch = head[len(R_HEADS):]
1253 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001254 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001255 except KeyError:
1256 head = None
1257 else:
1258 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001259
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001260 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001261 # Currently on a detached HEAD. The user is assumed to
1262 # not have any local modifications worth worrying about.
1263 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001264 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001265 syncbuf.fail(self, _PriorSyncFailedError())
1266 return
1267
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001268 if head == revid:
1269 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001270 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001271 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001272 if not syncbuf.detach_head:
1273 return
1274 else:
1275 lost = self._revlist(not_rev(revid), HEAD)
1276 if lost:
1277 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001278
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001280 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001281 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001282 syncbuf.fail(self, e)
1283 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001284 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001285 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001287 if head == revid:
1288 # No changes; don't do anything further.
1289 #
1290 return
1291
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001294 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001295 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001296 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001298 syncbuf.info(self,
1299 "leaving %s; does not track upstream",
1300 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001302 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001303 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001304 syncbuf.fail(self, e)
1305 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001306 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001307 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001309 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001310 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001312 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001313 if not_merged:
1314 if upstream_gain:
1315 # The user has published this branch and some of those
1316 # commits are not yet merged upstream. We do not want
1317 # to rewrite the published commits so we punt.
1318 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001319 syncbuf.fail(self,
1320 "branch %s is published (but not merged) and is now %d commits behind"
1321 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001322 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001323 elif pub == head:
1324 # All published commits are merged, and thus we are a
1325 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001326 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001327 syncbuf.later1(self, _doff)
1328 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001330 # Examine the local commits not in the remote. Find the
1331 # last one attributed to this user, if any.
1332 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001333 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001334 last_mine = None
1335 cnt_mine = 0
1336 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301337 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001338 if committer_email == self.UserEmail:
1339 last_mine = commit_id
1340 cnt_mine += 1
1341
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001342 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001343 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344
1345 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001346 syncbuf.fail(self, _DirtyError())
1347 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001348
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001349 # If the upstream switched on us, warn the user.
1350 #
1351 if branch.merge != self.revisionExpr:
1352 if branch.merge and self.revisionExpr:
1353 syncbuf.info(self,
1354 'manifest switched %s...%s',
1355 branch.merge,
1356 self.revisionExpr)
1357 elif branch.merge:
1358 syncbuf.info(self,
1359 'manifest no longer tracks %s',
1360 branch.merge)
1361
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001362 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001364 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001366 syncbuf.info(self,
1367 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001368 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001369
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001370 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001371 if not ID_RE.match(self.revisionExpr):
1372 # in case of manifest sync the revisionExpr might be a SHA1
1373 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001374 if not branch.merge.startswith('refs/'):
1375 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376 branch.Save()
1377
Mike Pontillod3153822012-02-28 11:53:24 -08001378 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001379 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001380 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001381 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001382 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001383 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001384 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001385 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001386 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001387 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001388 syncbuf.fail(self, e)
1389 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001390 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001391 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001392
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001393 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394 # dest should already be an absolute path, but src is project relative
1395 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001396 abssrc = os.path.join(self.worktree, src)
1397 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001399 def AddLinkFile(self, src, dest, absdest):
1400 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001401 # make src relative path to dest
1402 absdestdir = os.path.dirname(absdest)
1403 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001404 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001405
James W. Mills24c13082012-04-12 15:04:13 -05001406 def AddAnnotation(self, name, value, keep):
1407 self.annotations.append(_Annotation(name, value, keep))
1408
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001409 def DownloadPatchSet(self, change_id, patch_id):
1410 """Download a single patch set of a single change to FETCH_HEAD.
1411 """
1412 remote = self.GetRemote(self.remote.name)
1413
1414 cmd = ['fetch', remote.name]
1415 cmd.append('refs/changes/%2.2d/%d/%d' \
1416 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001417 if GitCommand(self, cmd, bare=True).Wait() != 0:
1418 return None
1419 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001420 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001421 change_id,
1422 patch_id,
1423 self.bare_git.rev_parse('FETCH_HEAD'))
1424
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001425
1426## Branch Management ##
1427
1428 def StartBranch(self, name):
1429 """Create a new branch off the manifest's revision.
1430 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001431 head = self.work_git.GetHead()
1432 if head == (R_HEADS + name):
1433 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001434
David Pursehouse8a68ff92012-09-24 12:15:13 +09001435 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001436 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001437 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001438 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001439 capture_stdout=True,
1440 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001441
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001442 branch = self.GetBranch(name)
1443 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001444 branch.merge = self.revisionExpr
Alexandre Boeglin38258272015-04-30 14:50:33 +02001445 if not branch.merge.startswith('refs/') and not ID_RE.match(self.revisionExpr):
Conley Owens04f2f0e2014-10-01 17:22:46 -07001446 branch.merge = R_HEADS + self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001447 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001448
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001449 if head.startswith(R_HEADS):
1450 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001451 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001452 except KeyError:
1453 head = None
1454
1455 if revid and head and revid == head:
1456 ref = os.path.join(self.gitdir, R_HEADS + name)
1457 try:
1458 os.makedirs(os.path.dirname(ref))
1459 except OSError:
1460 pass
1461 _lwrite(ref, '%s\n' % revid)
1462 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1463 'ref: %s%s\n' % (R_HEADS, name))
1464 branch.Save()
1465 return True
1466
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001467 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001468 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001469 capture_stdout=True,
1470 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001471 branch.Save()
1472 return True
1473 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001474
Wink Saville02d79452009-04-10 13:01:24 -07001475 def CheckoutBranch(self, name):
1476 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001477
1478 Args:
1479 name: The name of the branch to checkout.
1480
1481 Returns:
1482 True if the checkout succeeded; False if it didn't; None if the branch
1483 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001484 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001485 rev = R_HEADS + name
1486 head = self.work_git.GetHead()
1487 if head == rev:
1488 # Already on the branch
1489 #
1490 return True
Wink Saville02d79452009-04-10 13:01:24 -07001491
David Pursehouse8a68ff92012-09-24 12:15:13 +09001492 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001493 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001494 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001495 except KeyError:
1496 # Branch does not exist in this project
1497 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001498 return None
Wink Saville02d79452009-04-10 13:01:24 -07001499
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001500 if head.startswith(R_HEADS):
1501 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001502 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001503 except KeyError:
1504 head = None
1505
1506 if head == revid:
1507 # Same revision; just update HEAD to point to the new
1508 # target branch, but otherwise take no other action.
1509 #
1510 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1511 'ref: %s%s\n' % (R_HEADS, name))
1512 return True
1513
1514 return GitCommand(self,
1515 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001516 capture_stdout=True,
1517 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001518
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001519 def AbandonBranch(self, name):
1520 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001521
1522 Args:
1523 name: The name of the branch to abandon.
1524
1525 Returns:
1526 True if the abandon succeeded; False if it didn't; None if the branch
1527 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001528 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001529 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001530 all_refs = self.bare_ref.all
1531 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001532 # Doesn't exist
1533 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001534
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001535 head = self.work_git.GetHead()
1536 if head == rev:
1537 # We can't destroy the branch while we are sitting
1538 # on it. Switch to a detached HEAD.
1539 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001540 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001541
David Pursehouse8a68ff92012-09-24 12:15:13 +09001542 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001543 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001544 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1545 '%s\n' % revid)
1546 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001547 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001548
1549 return GitCommand(self,
1550 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001551 capture_stdout=True,
1552 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001553
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001554 def PruneHeads(self):
1555 """Prune any topic branches already merged into upstream.
1556 """
1557 cb = self.CurrentBranch
1558 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001559 left = self._allrefs
1560 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001561 if name.startswith(R_HEADS):
1562 name = name[len(R_HEADS):]
1563 if cb is None or name != cb:
1564 kill.append(name)
1565
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001566 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001567 if cb is not None \
1568 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001569 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001570 self.work_git.DetachHead(HEAD)
1571 kill.append(cb)
1572
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001573 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001574 old = self.bare_git.GetHead()
1575 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001576 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1577
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001578 try:
1579 self.bare_git.DetachHead(rev)
1580
1581 b = ['branch', '-d']
1582 b.extend(kill)
1583 b = GitCommand(self, b, bare=True,
1584 capture_stdout=True,
1585 capture_stderr=True)
1586 b.Wait()
1587 finally:
1588 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001589 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001590
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001591 for branch in kill:
1592 if (R_HEADS + branch) not in left:
1593 self.CleanPublishedCache()
1594 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001595
1596 if cb and cb not in kill:
1597 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001598 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001599
1600 kept = []
1601 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001602 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001603 branch = self.GetBranch(branch)
1604 base = branch.LocalMerge
1605 if not base:
1606 base = rev
1607 kept.append(ReviewableBranch(self, branch, base))
1608 return kept
1609
1610
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001611## Submodule Management ##
1612
1613 def GetRegisteredSubprojects(self):
1614 result = []
1615 def rec(subprojects):
1616 if not subprojects:
1617 return
1618 result.extend(subprojects)
1619 for p in subprojects:
1620 rec(p.subprojects)
1621 rec(self.subprojects)
1622 return result
1623
1624 def _GetSubmodules(self):
1625 # Unfortunately we cannot call `git submodule status --recursive` here
1626 # because the working tree might not exist yet, and it cannot be used
1627 # without a working tree in its current implementation.
1628
1629 def get_submodules(gitdir, rev):
1630 # Parse .gitmodules for submodule sub_paths and sub_urls
1631 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1632 if not sub_paths:
1633 return []
1634 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1635 # revision of submodule repository
1636 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1637 submodules = []
1638 for sub_path, sub_url in zip(sub_paths, sub_urls):
1639 try:
1640 sub_rev = sub_revs[sub_path]
1641 except KeyError:
1642 # Ignore non-exist submodules
1643 continue
1644 submodules.append((sub_rev, sub_path, sub_url))
1645 return submodules
1646
1647 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1648 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1649 def parse_gitmodules(gitdir, rev):
1650 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1651 try:
Anthony King7bdac712014-07-16 12:56:40 +01001652 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1653 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001654 except GitError:
1655 return [], []
1656 if p.Wait() != 0:
1657 return [], []
1658
1659 gitmodules_lines = []
1660 fd, temp_gitmodules_path = tempfile.mkstemp()
1661 try:
1662 os.write(fd, p.stdout)
1663 os.close(fd)
1664 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001665 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1666 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001667 if p.Wait() != 0:
1668 return [], []
1669 gitmodules_lines = p.stdout.split('\n')
1670 except GitError:
1671 return [], []
1672 finally:
1673 os.remove(temp_gitmodules_path)
1674
1675 names = set()
1676 paths = {}
1677 urls = {}
1678 for line in gitmodules_lines:
1679 if not line:
1680 continue
1681 m = re_path.match(line)
1682 if m:
1683 names.add(m.group(1))
1684 paths[m.group(1)] = m.group(2)
1685 continue
1686 m = re_url.match(line)
1687 if m:
1688 names.add(m.group(1))
1689 urls[m.group(1)] = m.group(2)
1690 continue
1691 names = sorted(names)
1692 return ([paths.get(name, '') for name in names],
1693 [urls.get(name, '') for name in names])
1694
1695 def git_ls_tree(gitdir, rev, paths):
1696 cmd = ['ls-tree', rev, '--']
1697 cmd.extend(paths)
1698 try:
Anthony King7bdac712014-07-16 12:56:40 +01001699 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1700 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001701 except GitError:
1702 return []
1703 if p.Wait() != 0:
1704 return []
1705 objects = {}
1706 for line in p.stdout.split('\n'):
1707 if not line.strip():
1708 continue
1709 object_rev, object_path = line.split()[2:4]
1710 objects[object_path] = object_rev
1711 return objects
1712
1713 try:
1714 rev = self.GetRevisionId()
1715 except GitError:
1716 return []
1717 return get_submodules(self.gitdir, rev)
1718
1719 def GetDerivedSubprojects(self):
1720 result = []
1721 if not self.Exists:
1722 # If git repo does not exist yet, querying its submodules will
1723 # mess up its states; so return here.
1724 return result
1725 for rev, path, url in self._GetSubmodules():
1726 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001727 relpath, worktree, gitdir, objdir = \
1728 self.manifest.GetSubprojectPaths(self, name, path)
1729 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001730 if project:
1731 result.extend(project.GetDerivedSubprojects())
1732 continue
David James8d201162013-10-11 17:03:19 -07001733
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001734 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001735 url=url,
1736 review=self.remote.review,
1737 revision=self.remote.revision)
1738 subproject = Project(manifest=self.manifest,
1739 name=name,
1740 remote=remote,
1741 gitdir=gitdir,
1742 objdir=objdir,
1743 worktree=worktree,
1744 relpath=relpath,
1745 revisionExpr=self.revisionExpr,
1746 revisionId=rev,
1747 rebase=self.rebase,
1748 groups=self.groups,
1749 sync_c=self.sync_c,
1750 sync_s=self.sync_s,
1751 parent=self,
1752 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001753 result.append(subproject)
1754 result.extend(subproject.GetDerivedSubprojects())
1755 return result
1756
1757
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001759 def _CheckForSha1(self):
1760 try:
1761 # if revision (sha or tag) is not present then following function
1762 # throws an error.
1763 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1764 return True
1765 except GitError:
1766 # There is no such persistent revision. We have to fetch it.
1767 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768
Julien Campergue335f5ef2013-10-16 11:02:35 +02001769 def _FetchArchive(self, tarpath, cwd=None):
1770 cmd = ['archive', '-v', '-o', tarpath]
1771 cmd.append('--remote=%s' % self.remote.url)
1772 cmd.append('--prefix=%s/' % self.relpath)
1773 cmd.append(self.revisionExpr)
1774
1775 command = GitCommand(self, cmd, cwd=cwd,
1776 capture_stdout=True,
1777 capture_stderr=True)
1778
1779 if command.Wait() != 0:
1780 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1781
Conley Owens80b87fe2014-05-09 17:13:44 -07001782
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001783 def _RemoteFetch(self, name=None,
1784 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001785 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001786 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001787 alt_dir=None,
1788 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001789
1790 is_sha1 = False
1791 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001792 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001793
David Pursehouse9bc422f2014-04-15 10:28:56 +09001794 # The depth should not be used when fetching to a mirror because
1795 # it will result in a shallow repository that cannot be cloned or
1796 # fetched from.
1797 if not self.manifest.IsMirror:
1798 if self.clone_depth:
1799 depth = self.clone_depth
1800 else:
1801 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001802 # The repo project should never be synced with partial depth
1803 if self.relpath == '.repo/repo':
1804 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001805
Shawn Pearce69e04d82014-01-29 12:48:54 -08001806 if depth:
1807 current_branch_only = True
1808
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001809 if ID_RE.match(self.revisionExpr) is not None:
1810 is_sha1 = True
1811
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001812 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001813 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001814 # this is a tag and its sha1 value should never change
1815 tag_name = self.revisionExpr[len(R_TAGS):]
1816
1817 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001818 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001819 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001820 if is_sha1 and not depth:
1821 # When syncing a specific commit and --depth is not set:
1822 # * if upstream is explicitly specified and is not a sha1, fetch only
1823 # upstream as users expect only upstream to be fetch.
1824 # Note: The commit might not be in upstream in which case the sync
1825 # will fail.
1826 # * otherwise, fetch all branches to make sure we end up with the
1827 # specific commit.
1828 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001829
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001830 if not name:
1831 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001832
1833 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001834 remote = self.GetRemote(name)
1835 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001836 ssh_proxy = True
1837
Shawn O. Pearce88443382010-10-08 10:02:09 +02001838 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001839 if alt_dir and 'objects' == os.path.basename(alt_dir):
1840 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001841 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1842 remote = self.GetRemote(name)
1843
David Pursehouse8a68ff92012-09-24 12:15:13 +09001844 all_refs = self.bare_ref.all
1845 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001846 tmp = set()
1847
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301848 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001849 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001850 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001851 all_refs[r] = ref_id
1852 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001853 continue
1854
David Pursehouse8a68ff92012-09-24 12:15:13 +09001855 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001856 continue
1857
David Pursehouse8a68ff92012-09-24 12:15:13 +09001858 r = 'refs/_alt/%s' % ref_id
1859 all_refs[r] = ref_id
1860 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001861 tmp.add(r)
1862
Shawn O. Pearce88443382010-10-08 10:02:09 +02001863 tmp_packed = ''
1864 old_packed = ''
1865
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301866 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001867 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001868 tmp_packed += line
1869 if r not in tmp:
1870 old_packed += line
1871
1872 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001873 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001874 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001875
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001876 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001877
Conley Owensf97e8382015-01-21 11:12:46 -08001878 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001879 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07001880 else:
1881 # If this repo has shallow objects, then we don't know which refs have
1882 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
1883 # do this with projects that don't have shallow objects, since it is less
1884 # efficient.
1885 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
1886 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07001887
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001888 if quiet:
1889 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001890 if not self.worktree:
1891 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001892 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001893
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001894 # If using depth then we should not get all the tags since they may
1895 # be outside of the depth.
1896 if no_tags or depth:
1897 cmd.append('--no-tags')
1898 else:
1899 cmd.append('--tags')
1900
Conley Owens80b87fe2014-05-09 17:13:44 -07001901 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001902 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001903 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001904 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001905 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001906 spec.append('tag')
1907 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001908
David Pursehouse403b64e2015-04-27 10:41:33 +09001909 if not self.manifest.IsMirror:
1910 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06001911 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001912 # Shallow checkout of a specific commit, fetch from that commit and not
1913 # the heads only as the commit might be deeper in the history.
1914 spec.append(branch)
1915 else:
1916 if is_sha1:
1917 branch = self.upstream
1918 if branch is not None and branch.strip():
1919 if not branch.startswith('refs/'):
1920 branch = R_HEADS + branch
1921 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001922 cmd.extend(spec)
1923
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001924 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001925 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001926 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001927 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001928 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001929 ok = True
1930 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001931 # If needed, run the 'git remote prune' the first time through the loop
1932 elif (not _i and
1933 "error:" in gitcmd.stderr and
1934 "git remote prune" in gitcmd.stderr):
1935 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001936 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001937 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001938 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001939 break
1940 continue
Brian Harring14a66742012-09-28 20:21:57 -07001941 elif current_branch_only and is_sha1 and ret == 128:
1942 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1943 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1944 # abort the optimization attempt and do a full sync.
1945 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001946 elif ret < 0:
1947 # Git died with a signal, exit immediately
1948 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001949 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001950
1951 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001952 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001953 if old_packed != '':
1954 _lwrite(packed_refs, old_packed)
1955 else:
1956 os.remove(packed_refs)
1957 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001958
1959 if is_sha1 and current_branch_only and self.upstream:
1960 # We just synced the upstream given branch; verify we
1961 # got what we wanted, else trigger a second run of all
1962 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001963 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06001964 if not depth:
1965 # Avoid infinite recursion when depth is True (since depth implies
1966 # current_branch_only)
1967 return self._RemoteFetch(name=name, current_branch_only=False,
1968 initial=False, quiet=quiet, alt_dir=alt_dir)
1969 if self.clone_depth:
1970 self.clone_depth = None
1971 return self._RemoteFetch(name=name, current_branch_only=current_branch_only,
1972 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07001973
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001974 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001975
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001976 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001977 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001978 return False
1979
1980 remote = self.GetRemote(self.remote.name)
1981 bundle_url = remote.url + '/clone.bundle'
1982 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001983 if GetSchemeFromUrl(bundle_url) not in (
1984 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001985 return False
1986
1987 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1988 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1989
1990 exist_dst = os.path.exists(bundle_dst)
1991 exist_tmp = os.path.exists(bundle_tmp)
1992
1993 if not initial and not exist_dst and not exist_tmp:
1994 return False
1995
1996 if not exist_dst:
1997 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1998 if not exist_dst:
1999 return False
2000
2001 cmd = ['fetch']
2002 if quiet:
2003 cmd.append('--quiet')
2004 if not self.worktree:
2005 cmd.append('--update-head-ok')
2006 cmd.append(bundle_dst)
2007 for f in remote.fetch:
2008 cmd.append(str(f))
2009 cmd.append('refs/tags/*:refs/tags/*')
2010
2011 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002012 if os.path.exists(bundle_dst):
2013 os.remove(bundle_dst)
2014 if os.path.exists(bundle_tmp):
2015 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002016 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002017
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002018 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002019 if os.path.exists(dstPath):
2020 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002021
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002022 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002023 if quiet:
2024 cmd += ['--silent']
2025 if os.path.exists(tmpPath):
2026 size = os.stat(tmpPath).st_size
2027 if size >= 1024:
2028 cmd += ['--continue-at', '%d' % (size,)]
2029 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002030 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002031 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2032 cmd += ['--proxy', os.environ['http_proxy']]
Dan Willemsen0745bb22015-08-17 13:41:45 -07002033 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002034 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002035 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002036 if srcUrl.startswith('persistent-'):
2037 srcUrl = srcUrl[len('persistent-'):]
2038 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002039
Dave Borowitz137d0132015-01-02 11:12:54 -08002040 if IsTrace():
2041 Trace('%s', ' '.join(cmd))
2042 try:
2043 proc = subprocess.Popen(cmd)
2044 except OSError:
2045 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002046
Dave Borowitz137d0132015-01-02 11:12:54 -08002047 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002048
Dave Borowitz137d0132015-01-02 11:12:54 -08002049 if curlret == 22:
2050 # From curl man page:
2051 # 22: HTTP page not retrieved. The requested url was not found or
2052 # returned another error with the HTTP error code being 400 or above.
2053 # This return code only appears if -f, --fail is used.
2054 if not quiet:
2055 print("Server does not provide clone.bundle; ignoring.",
2056 file=sys.stderr)
2057 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002058
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002059 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002060 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002061 os.rename(tmpPath, dstPath)
2062 return True
2063 else:
2064 os.remove(tmpPath)
2065 return False
2066 else:
2067 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002068
Kris Giesingc8d882a2014-12-23 13:02:32 -08002069 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002070 try:
2071 with open(path) as f:
2072 if f.read(16) == '# v2 git bundle\n':
2073 return True
2074 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002075 if not quiet:
2076 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002077 return False
2078 except OSError:
2079 return False
2080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002081 def _Checkout(self, rev, quiet=False):
2082 cmd = ['checkout']
2083 if quiet:
2084 cmd.append('-q')
2085 cmd.append(rev)
2086 cmd.append('--')
2087 if GitCommand(self, cmd).Wait() != 0:
2088 if self._allrefs:
2089 raise GitError('%s checkout %s ' % (self.name, rev))
2090
Anthony King7bdac712014-07-16 12:56:40 +01002091 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002092 cmd = ['cherry-pick']
2093 cmd.append(rev)
2094 cmd.append('--')
2095 if GitCommand(self, cmd).Wait() != 0:
2096 if self._allrefs:
2097 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2098
Anthony King7bdac712014-07-16 12:56:40 +01002099 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002100 cmd = ['revert']
2101 cmd.append('--no-edit')
2102 cmd.append(rev)
2103 cmd.append('--')
2104 if GitCommand(self, cmd).Wait() != 0:
2105 if self._allrefs:
2106 raise GitError('%s revert %s ' % (self.name, rev))
2107
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002108 def _ResetHard(self, rev, quiet=True):
2109 cmd = ['reset', '--hard']
2110 if quiet:
2111 cmd.append('-q')
2112 cmd.append(rev)
2113 if GitCommand(self, cmd).Wait() != 0:
2114 raise GitError('%s reset --hard %s ' % (self.name, rev))
2115
Anthony King7bdac712014-07-16 12:56:40 +01002116 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002117 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002118 if onto is not None:
2119 cmd.extend(['--onto', onto])
2120 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002121 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002122 raise GitError('%s rebase %s ' % (self.name, upstream))
2123
Pierre Tardy3d125942012-05-04 12:18:12 +02002124 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002125 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002126 if ffonly:
2127 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002128 if GitCommand(self, cmd).Wait() != 0:
2129 raise GitError('%s merge %s ' % (self.name, head))
2130
Kevin Degiabaa7f32014-11-12 11:27:45 -07002131 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002132 init_git_dir = not os.path.exists(self.gitdir)
2133 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002134 try:
2135 # Initialize the bare repository, which contains all of the objects.
2136 if init_obj_dir:
2137 os.makedirs(self.objdir)
2138 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002139
Kevin Degib1a07b82015-07-27 13:33:43 -06002140 # If we have a separate directory to hold refs, initialize it as well.
2141 if self.objdir != self.gitdir:
2142 if init_git_dir:
2143 os.makedirs(self.gitdir)
2144
2145 if init_obj_dir or init_git_dir:
2146 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2147 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002148 try:
2149 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2150 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002151 if force_sync:
David Pursehouse25857b82015-08-19 18:06:22 +09002152 print("Retrying clone after deleting %s" % self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002153 try:
2154 shutil.rmtree(os.path.realpath(self.gitdir))
2155 if self.worktree and os.path.exists(
2156 os.path.realpath(self.worktree)):
2157 shutil.rmtree(os.path.realpath(self.worktree))
2158 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2159 except:
2160 raise e
2161 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002162
Kevin Degi384b3c52014-10-16 16:02:58 -06002163 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002164 mp = self.manifest.manifestProject
2165 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002166
Kevin Degib1a07b82015-07-27 13:33:43 -06002167 if ref_dir or mirror_git:
2168 if not mirror_git:
2169 mirror_git = os.path.join(ref_dir, self.name + '.git')
2170 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2171 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002172
Kevin Degib1a07b82015-07-27 13:33:43 -06002173 if os.path.exists(mirror_git):
2174 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002175
Kevin Degib1a07b82015-07-27 13:33:43 -06002176 elif os.path.exists(repo_git):
2177 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002178
Kevin Degib1a07b82015-07-27 13:33:43 -06002179 else:
2180 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002181
Kevin Degib1a07b82015-07-27 13:33:43 -06002182 if ref_dir:
2183 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2184 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002185
Kevin Degib1a07b82015-07-27 13:33:43 -06002186 self._UpdateHooks()
2187
2188 m = self.manifest.manifestProject.config
2189 for key in ['user.name', 'user.email']:
2190 if m.Has(key, include_defaults=False):
2191 self.config.SetString(key, m.GetString(key))
2192 if self.manifest.IsMirror:
2193 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002194 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002195 self.config.SetString('core.bare', None)
2196 except Exception:
2197 if init_obj_dir and os.path.exists(self.objdir):
2198 shutil.rmtree(self.objdir)
2199 if init_git_dir and os.path.exists(self.gitdir):
2200 shutil.rmtree(self.gitdir)
2201 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002202
Jimmie Westera0444582012-10-24 13:44:42 +02002203 def _UpdateHooks(self):
2204 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002205 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002206
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002207 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002208 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002209 if not os.path.exists(hooks):
2210 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002211 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002212 name = os.path.basename(stock_hook)
2213
Victor Boivie65e0f352011-04-18 11:23:29 +02002214 if name in ('commit-msg',) and not self.remote.review \
2215 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002216 # Don't install a Gerrit Code Review hook if this
2217 # project does not appear to use it for reviews.
2218 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002219 # Since the manifest project is one of those, but also
2220 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002221 continue
2222
2223 dst = os.path.join(hooks, name)
2224 if os.path.islink(dst):
2225 continue
2226 if os.path.exists(dst):
2227 if filecmp.cmp(stock_hook, dst, shallow=False):
2228 os.remove(dst)
2229 else:
2230 _error("%s: Not replacing %s hook", self.relpath, name)
2231 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002232 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002233 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002234 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002235 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002236 raise GitError('filesystem must support symlinks')
2237 else:
2238 raise
2239
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002240 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002241 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002242 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002243 remote.url = self.remote.url
2244 remote.review = self.remote.review
2245 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002246
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002247 if self.worktree:
2248 remote.ResetFetch(mirror=False)
2249 else:
2250 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002251 remote.Save()
2252
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002253 def _InitMRef(self):
2254 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002255 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002256
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002257 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002258 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002259
2260 def _InitAnyMRef(self, ref):
2261 cur = self.bare_ref.symref(ref)
2262
2263 if self.revisionId:
2264 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2265 msg = 'manifest set to %s' % self.revisionId
2266 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002267 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002268 else:
2269 remote = self.GetRemote(self.remote.name)
2270 dst = remote.ToLocal(self.revisionExpr)
2271 if cur != dst:
2272 msg = 'manifest set to %s' % self.revisionExpr
2273 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002274
Kevin Degi384b3c52014-10-16 16:02:58 -06002275 def _CheckDirReference(self, srcdir, destdir, share_refs):
2276 symlink_files = self.shareable_files
2277 symlink_dirs = self.shareable_dirs
2278 if share_refs:
2279 symlink_files += self.working_tree_files
2280 symlink_dirs += self.working_tree_dirs
2281 to_symlink = symlink_files + symlink_dirs
2282 for name in set(to_symlink):
2283 dst = os.path.realpath(os.path.join(destdir, name))
2284 if os.path.lexists(dst):
2285 src = os.path.realpath(os.path.join(srcdir, name))
2286 # Fail if the links are pointing to the wrong place
2287 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002288 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002289 'work tree. If you\'re comfortable with the '
2290 'possibility of losing the work tree\'s git metadata,'
2291 ' use `repo sync --force-sync {0}` to '
2292 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002293
David James8d201162013-10-11 17:03:19 -07002294 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2295 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2296
2297 Args:
2298 gitdir: The bare git repository. Must already be initialized.
2299 dotgit: The repository you would like to initialize.
2300 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2301 Only one work tree can store refs under a given |gitdir|.
2302 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2303 This saves you the effort of initializing |dotgit| yourself.
2304 """
Kevin Degi384b3c52014-10-16 16:02:58 -06002305 symlink_files = self.shareable_files
2306 symlink_dirs = self.shareable_dirs
David James8d201162013-10-11 17:03:19 -07002307 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002308 symlink_files += self.working_tree_files
2309 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002310 to_symlink = symlink_files + symlink_dirs
2311
2312 to_copy = []
2313 if copy_all:
2314 to_copy = os.listdir(gitdir)
2315
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002316 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002317 for name in set(to_copy).union(to_symlink):
2318 try:
2319 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002320 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002321
Kevin Degi384b3c52014-10-16 16:02:58 -06002322 if os.path.lexists(dst):
2323 continue
David James8d201162013-10-11 17:03:19 -07002324
2325 # If the source dir doesn't exist, create an empty dir.
2326 if name in symlink_dirs and not os.path.lexists(src):
2327 os.makedirs(src)
2328
Conley Owens80b87fe2014-05-09 17:13:44 -07002329 # If the source file doesn't exist, ensure the destination
2330 # file doesn't either.
2331 if name in symlink_files and not os.path.lexists(src):
2332 try:
2333 os.remove(dst)
2334 except OSError:
2335 pass
2336
David James8d201162013-10-11 17:03:19 -07002337 if name in to_symlink:
2338 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2339 elif copy_all and not os.path.islink(dst):
2340 if os.path.isdir(src):
2341 shutil.copytree(src, dst)
2342 elif os.path.isfile(src):
2343 shutil.copy(src, dst)
2344 except OSError as e:
2345 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002346 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002347 else:
2348 raise
2349
Kevin Degiabaa7f32014-11-12 11:27:45 -07002350 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002351 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002352 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002353 try:
2354 if init_dotgit:
2355 os.makedirs(dotgit)
2356 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2357 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002358
Kevin Degiabaa7f32014-11-12 11:27:45 -07002359 try:
2360 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2361 except GitError as e:
2362 if force_sync:
2363 try:
2364 shutil.rmtree(dotgit)
2365 return self._InitWorkTree(force_sync=False)
2366 except:
2367 raise e
2368 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002369
Kevin Degib1a07b82015-07-27 13:33:43 -06002370 if init_dotgit:
2371 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002372
Kevin Degib1a07b82015-07-27 13:33:43 -06002373 cmd = ['read-tree', '--reset', '-u']
2374 cmd.append('-v')
2375 cmd.append(HEAD)
2376 if GitCommand(self, cmd).Wait() != 0:
2377 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002378
Kevin Degib1a07b82015-07-27 13:33:43 -06002379 self._CopyAndLinkFiles()
2380 except Exception:
2381 if init_dotgit:
2382 shutil.rmtree(dotgit)
2383 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002384
2385 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002386 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002387
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002388 def _revlist(self, *args, **kw):
2389 a = []
2390 a.extend(args)
2391 a.append('--')
2392 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002393
2394 @property
2395 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002396 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002397
Julien Camperguedd654222014-01-09 16:21:37 +01002398 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2399 """Get logs between two revisions of this project."""
2400 comp = '..'
2401 if rev1:
2402 revs = [rev1]
2403 if rev2:
2404 revs.extend([comp, rev2])
2405 cmd = ['log', ''.join(revs)]
2406 out = DiffColoring(self.config)
2407 if out.is_on and color:
2408 cmd.append('--color')
2409 if oneline:
2410 cmd.append('--oneline')
2411
2412 try:
2413 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2414 if log.Wait() == 0:
2415 return log.stdout
2416 except GitError:
2417 # worktree may not exist if groups changed for example. In that case,
2418 # try in gitdir instead.
2419 if not os.path.exists(self.worktree):
2420 return self.bare_git.log(*cmd[1:])
2421 else:
2422 raise
2423 return None
2424
2425 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2426 """Get the list of logs from this revision to given revisionId"""
2427 logs = {}
2428 selfId = self.GetRevisionId(self._allrefs)
2429 toId = toProject.GetRevisionId(toProject._allrefs)
2430
2431 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2432 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2433 return logs
2434
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002435 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002436 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002437 self._project = project
2438 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002439 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002440
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002441 def LsOthers(self):
2442 p = GitCommand(self._project,
2443 ['ls-files',
2444 '-z',
2445 '--others',
2446 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002447 bare=False,
David James8d201162013-10-11 17:03:19 -07002448 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002449 capture_stdout=True,
2450 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002451 if p.Wait() == 0:
2452 out = p.stdout
2453 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002454 return out[:-1].split('\0') # pylint: disable=W1401
2455 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002456 return []
2457
2458 def DiffZ(self, name, *args):
2459 cmd = [name]
2460 cmd.append('-z')
2461 cmd.extend(args)
2462 p = GitCommand(self._project,
2463 cmd,
David James8d201162013-10-11 17:03:19 -07002464 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002465 bare=False,
2466 capture_stdout=True,
2467 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002468 try:
2469 out = p.process.stdout.read()
2470 r = {}
2471 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002472 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002473 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002474 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002475 info = next(out)
2476 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002477 except StopIteration:
2478 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002479
2480 class _Info(object):
2481 def __init__(self, path, omode, nmode, oid, nid, state):
2482 self.path = path
2483 self.src_path = None
2484 self.old_mode = omode
2485 self.new_mode = nmode
2486 self.old_id = oid
2487 self.new_id = nid
2488
2489 if len(state) == 1:
2490 self.status = state
2491 self.level = None
2492 else:
2493 self.status = state[:1]
2494 self.level = state[1:]
2495 while self.level.startswith('0'):
2496 self.level = self.level[1:]
2497
2498 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002499 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002500 if info.status in ('R', 'C'):
2501 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002502 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002503 r[info.path] = info
2504 return r
2505 finally:
2506 p.Wait()
2507
2508 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002509 if self._bare:
2510 path = os.path.join(self._project.gitdir, HEAD)
2511 else:
2512 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002513 try:
2514 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002515 except IOError as e:
2516 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002517 try:
2518 line = fd.read()
2519 finally:
2520 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302521 try:
2522 line = line.decode()
2523 except AttributeError:
2524 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002525 if line.startswith('ref: '):
2526 return line[5:-1]
2527 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002528
2529 def SetHead(self, ref, message=None):
2530 cmdv = []
2531 if message is not None:
2532 cmdv.extend(['-m', message])
2533 cmdv.append(HEAD)
2534 cmdv.append(ref)
2535 self.symbolic_ref(*cmdv)
2536
2537 def DetachHead(self, new, message=None):
2538 cmdv = ['--no-deref']
2539 if message is not None:
2540 cmdv.extend(['-m', message])
2541 cmdv.append(HEAD)
2542 cmdv.append(new)
2543 self.update_ref(*cmdv)
2544
2545 def UpdateRef(self, name, new, old=None,
2546 message=None,
2547 detach=False):
2548 cmdv = []
2549 if message is not None:
2550 cmdv.extend(['-m', message])
2551 if detach:
2552 cmdv.append('--no-deref')
2553 cmdv.append(name)
2554 cmdv.append(new)
2555 if old is not None:
2556 cmdv.append(old)
2557 self.update_ref(*cmdv)
2558
2559 def DeleteRef(self, name, old=None):
2560 if not old:
2561 old = self.rev_parse(name)
2562 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002563 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002564
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002565 def rev_list(self, *args, **kw):
2566 if 'format' in kw:
2567 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2568 else:
2569 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002570 cmdv.extend(args)
2571 p = GitCommand(self._project,
2572 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002573 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002574 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002575 capture_stdout=True,
2576 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002577 r = []
2578 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002579 if line[-1] == '\n':
2580 line = line[:-1]
2581 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002582 if p.Wait() != 0:
2583 raise GitError('%s rev-list %s: %s' % (
2584 self._project.name,
2585 str(args),
2586 p.stderr))
2587 return r
2588
2589 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002590 """Allow arbitrary git commands using pythonic syntax.
2591
2592 This allows you to do things like:
2593 git_obj.rev_parse('HEAD')
2594
2595 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2596 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002597 Any other positional arguments will be passed to the git command, and the
2598 following keyword arguments are supported:
2599 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002600
2601 Args:
2602 name: The name of the git command to call. Any '_' characters will
2603 be replaced with '-'.
2604
2605 Returns:
2606 A callable object that will try to call git with the named command.
2607 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002609 def runner(*args, **kwargs):
2610 cmdv = []
2611 config = kwargs.pop('config', None)
2612 for k in kwargs:
2613 raise TypeError('%s() got an unexpected keyword argument %r'
2614 % (name, k))
2615 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002616 if not git_require((1, 7, 2)):
2617 raise ValueError('cannot set config on command line for %s()'
2618 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302619 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002620 cmdv.append('-c')
2621 cmdv.append('%s=%s' % (k, v))
2622 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002623 cmdv.extend(args)
2624 p = GitCommand(self._project,
2625 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002626 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002627 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002628 capture_stdout=True,
2629 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002630 if p.Wait() != 0:
2631 raise GitError('%s %s: %s' % (
2632 self._project.name,
2633 name,
2634 p.stderr))
2635 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302636 try:
Conley Owensedd01512013-09-26 12:59:58 -07002637 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302638 except AttributeError:
2639 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002640 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2641 return r[:-1]
2642 return r
2643 return runner
2644
2645
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002646class _PriorSyncFailedError(Exception):
2647 def __str__(self):
2648 return 'prior sync failed; rebase still in progress'
2649
2650class _DirtyError(Exception):
2651 def __str__(self):
2652 return 'contains uncommitted changes'
2653
2654class _InfoMessage(object):
2655 def __init__(self, project, text):
2656 self.project = project
2657 self.text = text
2658
2659 def Print(self, syncbuf):
2660 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2661 syncbuf.out.nl()
2662
2663class _Failure(object):
2664 def __init__(self, project, why):
2665 self.project = project
2666 self.why = why
2667
2668 def Print(self, syncbuf):
2669 syncbuf.out.fail('error: %s/: %s',
2670 self.project.relpath,
2671 str(self.why))
2672 syncbuf.out.nl()
2673
2674class _Later(object):
2675 def __init__(self, project, action):
2676 self.project = project
2677 self.action = action
2678
2679 def Run(self, syncbuf):
2680 out = syncbuf.out
2681 out.project('project %s/', self.project.relpath)
2682 out.nl()
2683 try:
2684 self.action()
2685 out.nl()
2686 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002687 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002688 out.nl()
2689 return False
2690
2691class _SyncColoring(Coloring):
2692 def __init__(self, config):
2693 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002694 self.project = self.printer('header', attr='bold')
2695 self.info = self.printer('info')
2696 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002697
2698class SyncBuffer(object):
2699 def __init__(self, config, detach_head=False):
2700 self._messages = []
2701 self._failures = []
2702 self._later_queue1 = []
2703 self._later_queue2 = []
2704
2705 self.out = _SyncColoring(config)
2706 self.out.redirect(sys.stderr)
2707
2708 self.detach_head = detach_head
2709 self.clean = True
2710
2711 def info(self, project, fmt, *args):
2712 self._messages.append(_InfoMessage(project, fmt % args))
2713
2714 def fail(self, project, err=None):
2715 self._failures.append(_Failure(project, err))
2716 self.clean = False
2717
2718 def later1(self, project, what):
2719 self._later_queue1.append(_Later(project, what))
2720
2721 def later2(self, project, what):
2722 self._later_queue2.append(_Later(project, what))
2723
2724 def Finish(self):
2725 self._PrintMessages()
2726 self._RunLater()
2727 self._PrintMessages()
2728 return self.clean
2729
2730 def _RunLater(self):
2731 for q in ['_later_queue1', '_later_queue2']:
2732 if not self._RunQueue(q):
2733 return
2734
2735 def _RunQueue(self, queue):
2736 for m in getattr(self, queue):
2737 if not m.Run(self):
2738 self.clean = False
2739 return False
2740 setattr(self, queue, [])
2741 return True
2742
2743 def _PrintMessages(self):
2744 for m in self._messages:
2745 m.Print(self)
2746 for m in self._failures:
2747 m.Print(self)
2748
2749 self._messages = []
2750 self._failures = []
2751
2752
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002753class MetaProject(Project):
2754 """A special project housed under .repo.
2755 """
2756 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002757 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002758 manifest=manifest,
2759 name=name,
2760 gitdir=gitdir,
2761 objdir=gitdir,
2762 worktree=worktree,
2763 remote=RemoteSpec('origin'),
2764 relpath='.repo/%s' % name,
2765 revisionExpr='refs/heads/master',
2766 revisionId=None,
2767 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002768
2769 def PreSync(self):
2770 if self.Exists:
2771 cb = self.CurrentBranch
2772 if cb:
2773 base = self.GetBranch(cb).merge
2774 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002775 self.revisionExpr = base
2776 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002777
Anthony King7bdac712014-07-16 12:56:40 +01002778 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002779 """ Prepare MetaProject for manifest branch switch
2780 """
2781
2782 # detach and delete manifest branch, allowing a new
2783 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002784 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002785 self.Sync_LocalHalf(syncbuf)
2786 syncbuf.Finish()
2787
2788 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002789 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002790 capture_stdout=True,
2791 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002792
2793
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002794 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002795 def LastFetch(self):
2796 try:
2797 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2798 return os.path.getmtime(fh)
2799 except OSError:
2800 return 0
2801
2802 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002803 def HasChanges(self):
2804 """Has the remote received new commits not yet checked out?
2805 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002806 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002807 return False
2808
David Pursehouse8a68ff92012-09-24 12:15:13 +09002809 all_refs = self.bare_ref.all
2810 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002811 head = self.work_git.GetHead()
2812 if head.startswith(R_HEADS):
2813 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002814 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002815 except KeyError:
2816 head = None
2817
2818 if revid == head:
2819 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002820 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002821 return True
2822 return False