blob: 5d8f61e1309304a07c862e9260575df5b2c71553 [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,
Simran Basib9a1b732015-08-20 12:19:28 -0700575 optimized_fetch=False,
576 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800577 """Init a Project object.
578
579 Args:
580 manifest: The XmlManifest object.
581 name: The `name` attribute of manifest.xml's project element.
582 remote: RemoteSpec object specifying its remote's properties.
583 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700584 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800585 worktree: Absolute path of git working tree.
586 relpath: Relative path of git working tree to repo's top directory.
587 revisionExpr: The `revision` attribute of manifest.xml's project element.
588 revisionId: git commit id for checking out.
589 rebase: The `rebase` attribute of manifest.xml's project element.
590 groups: The `groups` attribute of manifest.xml's project element.
591 sync_c: The `sync-c` attribute of manifest.xml's project element.
592 sync_s: The `sync-s` attribute of manifest.xml's project element.
593 upstream: The `upstream` attribute of manifest.xml's project element.
594 parent: The parent Project object.
595 is_derived: False if the project was explicitly defined in the manifest;
596 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400597 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900598 optimized_fetch: If True, when a project is set to a sha1 revision, only
599 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700600 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800601 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700602 self.manifest = manifest
603 self.name = name
604 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800605 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700606 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800607 if worktree:
608 self.worktree = worktree.replace('\\', '/')
609 else:
610 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700611 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700612 self.revisionExpr = revisionExpr
613
614 if revisionId is None \
615 and revisionExpr \
616 and IsId(revisionExpr):
617 self.revisionId = revisionExpr
618 else:
619 self.revisionId = revisionId
620
Mike Pontillod3153822012-02-28 11:53:24 -0800621 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700622 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700623 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800624 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900625 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700626 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800627 self.parent = parent
628 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900629 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800630 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800631
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700632 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700633 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500634 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500635 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700636 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100637 gitdir=self.gitdir,
638 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700639
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800640 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700641 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800642 else:
643 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700644 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700645 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700646 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400647 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700648 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700649
Doug Anderson37282b42011-03-04 11:54:18 -0800650 # This will be filled in if a project is later identified to be the
651 # project containing repo hooks.
652 self.enabled_repo_hooks = []
653
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800655 def Derived(self):
656 return self.is_derived
657
658 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700659 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600660 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700661
662 @property
663 def CurrentBranch(self):
664 """Obtain the name of the currently checked out branch.
665 The branch name omits the 'refs/heads/' prefix.
666 None is returned if the project is on a detached HEAD.
667 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700668 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669 if b.startswith(R_HEADS):
670 return b[len(R_HEADS):]
671 return None
672
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700673 def IsRebaseInProgress(self):
674 w = self.worktree
675 g = os.path.join(w, '.git')
676 return os.path.exists(os.path.join(g, 'rebase-apply')) \
677 or os.path.exists(os.path.join(g, 'rebase-merge')) \
678 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200679
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700680 def IsDirty(self, consider_untracked=True):
681 """Is the working directory modified in some way?
682 """
683 self.work_git.update_index('-q',
684 '--unmerged',
685 '--ignore-missing',
686 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900687 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700688 return True
689 if self.work_git.DiffZ('diff-files'):
690 return True
691 if consider_untracked and self.work_git.LsOthers():
692 return True
693 return False
694
695 _userident_name = None
696 _userident_email = None
697
698 @property
699 def UserName(self):
700 """Obtain the user's personal name.
701 """
702 if self._userident_name is None:
703 self._LoadUserIdentity()
704 return self._userident_name
705
706 @property
707 def UserEmail(self):
708 """Obtain the user's email address. This is very likely
709 to be their Gerrit login.
710 """
711 if self._userident_email is None:
712 self._LoadUserIdentity()
713 return self._userident_email
714
715 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900716 u = self.bare_git.var('GIT_COMMITTER_IDENT')
717 m = re.compile("^(.*) <([^>]*)> ").match(u)
718 if m:
719 self._userident_name = m.group(1)
720 self._userident_email = m.group(2)
721 else:
722 self._userident_name = ''
723 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700724
725 def GetRemote(self, name):
726 """Get the configuration for a single remote.
727 """
728 return self.config.GetRemote(name)
729
730 def GetBranch(self, name):
731 """Get the configuration for a single branch.
732 """
733 return self.config.GetBranch(name)
734
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700735 def GetBranches(self):
736 """Get all existing local branches.
737 """
738 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900739 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700740 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700741
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530742 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700743 if name.startswith(R_HEADS):
744 name = name[len(R_HEADS):]
745 b = self.GetBranch(name)
746 b.current = name == current
747 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900748 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700749 heads[name] = b
750
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530751 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700752 if name.startswith(R_PUB):
753 name = name[len(R_PUB):]
754 b = heads.get(name)
755 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900756 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700757
758 return heads
759
Colin Cross5acde752012-03-28 20:15:45 -0700760 def MatchesGroups(self, manifest_groups):
761 """Returns true if the manifest groups specified at init should cause
762 this project to be synced.
763 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700764 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700765
766 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700767 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700768 manifest_groups: "-group1,group2"
769 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500770
771 The special manifest group "default" will match any project that
772 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700773 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500774 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700775 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500776 if not 'notdefault' in expanded_project_groups:
777 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700778
Conley Owens971de8e2012-04-16 10:36:08 -0700779 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700780 for group in expanded_manifest_groups:
781 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700782 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700783 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700784 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700785
Conley Owens971de8e2012-04-16 10:36:08 -0700786 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700787
788## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700789 def UncommitedFiles(self, get_all=True):
790 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700792 Args:
793 get_all: a boolean, if True - get information about all different
794 uncommitted files. If False - return as soon as any kind of
795 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500796 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700797 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500798 self.work_git.update_index('-q',
799 '--unmerged',
800 '--ignore-missing',
801 '--refresh')
802 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700803 details.append("rebase in progress")
804 if not get_all:
805 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500806
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700807 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
808 if changes:
809 details.extend(changes)
810 if not get_all:
811 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500812
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700813 changes = self.work_git.DiffZ('diff-files').keys()
814 if changes:
815 details.extend(changes)
816 if not get_all:
817 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500818
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700819 changes = self.work_git.LsOthers()
820 if changes:
821 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500822
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700823 return details
824
825 def HasChanges(self):
826 """Returns true if there are uncommitted changes.
827 """
828 if self.UncommitedFiles(get_all=False):
829 return True
830 else:
831 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500832
Terence Haddock4655e812011-03-31 12:33:34 +0200833 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200835
836 Args:
837 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 """
839 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200840 if output_redir == None:
841 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700842 print(file=output_redir)
843 print('project %s/' % self.relpath, file=output_redir)
844 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845 return
846
847 self.work_git.update_index('-q',
848 '--unmerged',
849 '--ignore-missing',
850 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700851 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700852 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
853 df = self.work_git.DiffZ('diff-files')
854 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100855 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700856 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857
858 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200859 if not output_redir == None:
860 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700861 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862
863 branch = self.CurrentBranch
864 if branch is None:
865 out.nobranch('(*** NO BRANCH ***)')
866 else:
867 out.branch('branch %s', branch)
868 out.nl()
869
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700870 if rb:
871 out.important('prior sync failed; rebase still in progress')
872 out.nl()
873
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874 paths = list()
875 paths.extend(di.keys())
876 paths.extend(df.keys())
877 paths.extend(do)
878
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530879 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900880 try:
881 i = di[p]
882 except KeyError:
883 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700884
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900885 try:
886 f = df[p]
887 except KeyError:
888 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200889
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900890 if i:
891 i_status = i.status.upper()
892 else:
893 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900895 if f:
896 f_status = f.status.lower()
897 else:
898 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899
900 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800901 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902 i.src_path, p, i.level)
903 else:
904 line = ' %s%s\t%s' % (i_status, f_status, p)
905
906 if i and not f:
907 out.added('%s', line)
908 elif (i and f) or (not i and f):
909 out.changed('%s', line)
910 elif not i and not f:
911 out.untracked('%s', line)
912 else:
913 out.write('%s', line)
914 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200915
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700916 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917
pelyad67872d2012-03-28 14:49:58 +0300918 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919 """Prints the status of the repository to stdout.
920 """
921 out = DiffColoring(self.config)
922 cmd = ['diff']
923 if out.is_on:
924 cmd.append('--color')
925 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300926 if absolute_paths:
927 cmd.append('--src-prefix=a/%s/' % self.relpath)
928 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929 cmd.append('--')
930 p = GitCommand(self,
931 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100932 capture_stdout=True,
933 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934 has_diff = False
935 for line in p.process.stdout:
936 if not has_diff:
937 out.nl()
938 out.project('project %s/' % self.relpath)
939 out.nl()
940 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700941 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 p.Wait()
943
944
945## Publish / Upload ##
946
David Pursehouse8a68ff92012-09-24 12:15:13 +0900947 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 """Was the branch published (uploaded) for code review?
949 If so, returns the SHA-1 hash of the last published
950 state for the branch.
951 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700952 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900953 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700954 try:
955 return self.bare_git.rev_parse(key)
956 except GitError:
957 return None
958 else:
959 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900960 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700961 except KeyError:
962 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963
David Pursehouse8a68ff92012-09-24 12:15:13 +0900964 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700965 """Prunes any stale published refs.
966 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900967 if all_refs is None:
968 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 heads = set()
970 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530971 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 if name.startswith(R_HEADS):
973 heads.add(name)
974 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900975 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700976
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530977 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978 n = name[len(R_PUB):]
979 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900980 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700982 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 """List any branches which can be uploaded for review.
984 """
985 heads = {}
986 pubed = {}
987
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530988 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900990 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900992 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700993
994 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530995 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900996 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700998 if selected_branch and branch != selected_branch:
999 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001001 rb = self.GetUploadableBranch(branch)
1002 if rb:
1003 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001004 return ready
1005
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001006 def GetUploadableBranch(self, branch_name):
1007 """Get a single uploadable branch, or None.
1008 """
1009 branch = self.GetBranch(branch_name)
1010 base = branch.LocalMerge
1011 if branch.LocalMerge:
1012 rb = ReviewableBranch(self, branch, base)
1013 if rb.commits:
1014 return rb
1015 return None
1016
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001017 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001018 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001019 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001020 draft=False,
1021 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022 """Uploads the named branch for code review.
1023 """
1024 if branch is None:
1025 branch = self.CurrentBranch
1026 if branch is None:
1027 raise GitError('not currently on a branch')
1028
1029 branch = self.GetBranch(branch)
1030 if not branch.LocalMerge:
1031 raise GitError('branch %s does not track a remote' % branch.name)
1032 if not branch.remote.review:
1033 raise GitError('remote %s has no review url' % branch.remote.name)
1034
Bryan Jacobsf609f912013-05-06 13:36:24 -04001035 if dest_branch is None:
1036 dest_branch = self.dest_branch
1037 if dest_branch is None:
1038 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039 if not dest_branch.startswith(R_HEADS):
1040 dest_branch = R_HEADS + dest_branch
1041
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001042 if not branch.remote.projectname:
1043 branch.remote.projectname = self.name
1044 branch.remote.Save()
1045
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001046 url = branch.remote.ReviewUrl(self.UserEmail)
1047 if url is None:
1048 raise UploadError('review not configured')
1049 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001050
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001051 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001052 rp = ['gerrit receive-pack']
1053 for e in people[0]:
1054 rp.append('--reviewer=%s' % sq(e))
1055 for e in people[1]:
1056 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001057 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001058
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001059 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001060
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001061 if dest_branch.startswith(R_HEADS):
1062 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001063
1064 upload_type = 'for'
1065 if draft:
1066 upload_type = 'drafts'
1067
1068 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1069 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001070 if auto_topic:
1071 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001072 if not url.startswith('ssh://'):
1073 rp = ['r=%s' % p for p in people[0]] + \
1074 ['cc=%s' % p for p in people[1]]
1075 if rp:
1076 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001077 cmd.append(ref_spec)
1078
Anthony King7bdac712014-07-16 12:56:40 +01001079 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001080 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081
1082 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1083 self.bare_git.UpdateRef(R_PUB + branch.name,
1084 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001085 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086
1087
1088## Sync ##
1089
Julien Campergue335f5ef2013-10-16 11:02:35 +02001090 def _ExtractArchive(self, tarpath, path=None):
1091 """Extract the given tar on its current location
1092
1093 Args:
1094 - tarpath: The path to the actual tar file
1095
1096 """
1097 try:
1098 with tarfile.open(tarpath, 'r') as tar:
1099 tar.extractall(path=path)
1100 return True
1101 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001102 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001103 return False
1104
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001105 def Sync_NetworkHalf(self,
1106 quiet=False,
1107 is_new=None,
1108 current_branch_only=False,
Kevin Degiabaa7f32014-11-12 11:27:45 -07001109 force_sync=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001110 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001111 no_tags=False,
David Pursehouseb1553542014-09-04 21:28:09 +09001112 archive=False,
1113 optimized_fetch=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001114 """Perform only the network IO portion of the sync process.
1115 Local working directory/branch state is not affected.
1116 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001117 if archive and not isinstance(self, MetaProject):
1118 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001119 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001120 return False
1121
1122 name = self.relpath.replace('\\', '/')
1123 name = name.replace('/', '_')
1124 tarpath = '%s.tar' % name
1125 topdir = self.manifest.topdir
1126
1127 try:
1128 self._FetchArchive(tarpath, cwd=topdir)
1129 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001130 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001131 return False
1132
1133 # From now on, we only need absolute tarpath
1134 tarpath = os.path.join(topdir, tarpath)
1135
1136 if not self._ExtractArchive(tarpath, path=topdir):
1137 return False
1138 try:
1139 os.remove(tarpath)
1140 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001141 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001142 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001143 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001144 if is_new is None:
1145 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001146 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001147 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001148 else:
1149 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001150 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001151
1152 if is_new:
1153 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1154 try:
1155 fd = open(alt, 'rb')
1156 try:
1157 alt_dir = fd.readline().rstrip()
1158 finally:
1159 fd.close()
1160 except IOError:
1161 alt_dir = None
1162 else:
1163 alt_dir = None
1164
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001165 if clone_bundle \
1166 and alt_dir is None \
1167 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001168 is_new = False
1169
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001170 if not current_branch_only:
1171 if self.sync_c:
1172 current_branch_only = True
1173 elif not self.manifest._loaded:
1174 # Manifest cannot check defaults until it syncs.
1175 current_branch_only = False
1176 elif self.manifest.default.sync_c:
1177 current_branch_only = True
1178
David Pursehouseb1553542014-09-04 21:28:09 +09001179 need_to_fetch = not (optimized_fetch and \
1180 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1181 if (need_to_fetch
Conley Owens666d5342014-05-01 13:09:57 -07001182 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1183 current_branch_only=current_branch_only,
1184 no_tags=no_tags)):
Anthony King7bdac712014-07-16 12:56:40 +01001185 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001186
1187 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001188 self._InitMRef()
1189 else:
1190 self._InitMirrorHead()
1191 try:
1192 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1193 except OSError:
1194 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001196
1197 def PostRepoUpgrade(self):
1198 self._InitHooks()
1199
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001200 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001201 if self.manifest.isGitcClient:
1202 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001203 for copyfile in self.copyfiles:
1204 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001205 for linkfile in self.linkfiles:
1206 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001207
Julien Camperguedd654222014-01-09 16:21:37 +01001208 def GetCommitRevisionId(self):
1209 """Get revisionId of a commit.
1210
1211 Use this method instead of GetRevisionId to get the id of the commit rather
1212 than the id of the current git object (for example, a tag)
1213
1214 """
1215 if not self.revisionExpr.startswith(R_TAGS):
1216 return self.GetRevisionId(self._allrefs)
1217
1218 try:
1219 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1220 except GitError:
1221 raise ManifestInvalidRevisionError(
1222 'revision %s in %s not found' % (self.revisionExpr,
1223 self.name))
1224
David Pursehouse8a68ff92012-09-24 12:15:13 +09001225 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001226 if self.revisionId:
1227 return self.revisionId
1228
1229 rem = self.GetRemote(self.remote.name)
1230 rev = rem.ToLocal(self.revisionExpr)
1231
David Pursehouse8a68ff92012-09-24 12:15:13 +09001232 if all_refs is not None and rev in all_refs:
1233 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001234
1235 try:
1236 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1237 except GitError:
1238 raise ManifestInvalidRevisionError(
1239 'revision %s in %s not found' % (self.revisionExpr,
1240 self.name))
1241
Kevin Degiabaa7f32014-11-12 11:27:45 -07001242 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001243 """Perform only the local IO portion of the sync process.
1244 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001245 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001246 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001247 all_refs = self.bare_ref.all
1248 self.CleanPublishedCache(all_refs)
1249 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001250
David Pursehouse1d947b32012-10-25 12:23:11 +09001251 def _doff():
1252 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001253 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001254
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001255 head = self.work_git.GetHead()
1256 if head.startswith(R_HEADS):
1257 branch = head[len(R_HEADS):]
1258 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001259 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001260 except KeyError:
1261 head = None
1262 else:
1263 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001264
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001265 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001266 # Currently on a detached HEAD. The user is assumed to
1267 # not have any local modifications worth worrying about.
1268 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001269 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001270 syncbuf.fail(self, _PriorSyncFailedError())
1271 return
1272
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001273 if head == revid:
1274 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001275 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001276 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001277 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001278 # The copy/linkfile config may have changed.
1279 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001280 return
1281 else:
1282 lost = self._revlist(not_rev(revid), HEAD)
1283 if lost:
1284 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001285
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001287 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001288 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001289 syncbuf.fail(self, e)
1290 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001291 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001292 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001294 if head == revid:
1295 # No changes; don't do anything further.
1296 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001297 # The copy/linkfile config may have changed.
1298 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001299 return
1300
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001303 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001304 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001305 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001307 syncbuf.info(self,
1308 "leaving %s; does not track upstream",
1309 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001311 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001312 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001313 syncbuf.fail(self, e)
1314 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001315 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001316 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001318 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001319 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001321 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 if not_merged:
1323 if upstream_gain:
1324 # The user has published this branch and some of those
1325 # commits are not yet merged upstream. We do not want
1326 # to rewrite the published commits so we punt.
1327 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001328 syncbuf.fail(self,
1329 "branch %s is published (but not merged) and is now %d commits behind"
1330 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001331 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001332 elif pub == head:
1333 # All published commits are merged, and thus we are a
1334 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001335 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001336 syncbuf.later1(self, _doff)
1337 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001338
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001339 # Examine the local commits not in the remote. Find the
1340 # last one attributed to this user, if any.
1341 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001342 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001343 last_mine = None
1344 cnt_mine = 0
1345 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301346 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001347 if committer_email == self.UserEmail:
1348 last_mine = commit_id
1349 cnt_mine += 1
1350
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001351 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001352 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353
1354 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001355 syncbuf.fail(self, _DirtyError())
1356 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001358 # If the upstream switched on us, warn the user.
1359 #
1360 if branch.merge != self.revisionExpr:
1361 if branch.merge and self.revisionExpr:
1362 syncbuf.info(self,
1363 'manifest switched %s...%s',
1364 branch.merge,
1365 self.revisionExpr)
1366 elif branch.merge:
1367 syncbuf.info(self,
1368 'manifest no longer tracks %s',
1369 branch.merge)
1370
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001371 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001372 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001373 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001374 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001375 syncbuf.info(self,
1376 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001377 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001378
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001379 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001380 if not ID_RE.match(self.revisionExpr):
1381 # in case of manifest sync the revisionExpr might be a SHA1
1382 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001383 if not branch.merge.startswith('refs/'):
1384 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385 branch.Save()
1386
Mike Pontillod3153822012-02-28 11:53:24 -08001387 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001388 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001389 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001390 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001391 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001392 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001394 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001395 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001396 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001397 syncbuf.fail(self, e)
1398 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001400 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001401
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001402 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403 # dest should already be an absolute path, but src is project relative
1404 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001405 abssrc = os.path.join(self.worktree, src)
1406 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001408 def AddLinkFile(self, src, dest, absdest):
1409 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001410 # make src relative path to dest
1411 absdestdir = os.path.dirname(absdest)
1412 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001413 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001414
James W. Mills24c13082012-04-12 15:04:13 -05001415 def AddAnnotation(self, name, value, keep):
1416 self.annotations.append(_Annotation(name, value, keep))
1417
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001418 def DownloadPatchSet(self, change_id, patch_id):
1419 """Download a single patch set of a single change to FETCH_HEAD.
1420 """
1421 remote = self.GetRemote(self.remote.name)
1422
1423 cmd = ['fetch', remote.name]
1424 cmd.append('refs/changes/%2.2d/%d/%d' \
1425 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001426 if GitCommand(self, cmd, bare=True).Wait() != 0:
1427 return None
1428 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001429 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001430 change_id,
1431 patch_id,
1432 self.bare_git.rev_parse('FETCH_HEAD'))
1433
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001434
1435## Branch Management ##
1436
Simran Basib9a1b732015-08-20 12:19:28 -07001437 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001438 """Create a new branch off the manifest's revision.
1439 """
Simran Basib9a1b732015-08-20 12:19:28 -07001440 if not branch_merge:
1441 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001442 head = self.work_git.GetHead()
1443 if head == (R_HEADS + name):
1444 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001445
David Pursehouse8a68ff92012-09-24 12:15:13 +09001446 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001447 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001448 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001449 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001450 capture_stdout=True,
1451 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001452
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001453 branch = self.GetBranch(name)
1454 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001455 branch.merge = branch_merge
1456 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1457 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001458 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001459
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001460 if head.startswith(R_HEADS):
1461 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001462 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001463 except KeyError:
1464 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001465 if revid and head and revid == head:
1466 ref = os.path.join(self.gitdir, R_HEADS + name)
1467 try:
1468 os.makedirs(os.path.dirname(ref))
1469 except OSError:
1470 pass
1471 _lwrite(ref, '%s\n' % revid)
1472 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1473 'ref: %s%s\n' % (R_HEADS, name))
1474 branch.Save()
1475 return True
1476
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001477 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001478 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001479 capture_stdout=True,
1480 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001481 branch.Save()
1482 return True
1483 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001484
Wink Saville02d79452009-04-10 13:01:24 -07001485 def CheckoutBranch(self, name):
1486 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001487
1488 Args:
1489 name: The name of the branch to checkout.
1490
1491 Returns:
1492 True if the checkout succeeded; False if it didn't; None if the branch
1493 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001494 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001495 rev = R_HEADS + name
1496 head = self.work_git.GetHead()
1497 if head == rev:
1498 # Already on the branch
1499 #
1500 return True
Wink Saville02d79452009-04-10 13:01:24 -07001501
David Pursehouse8a68ff92012-09-24 12:15:13 +09001502 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001503 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001504 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001505 except KeyError:
1506 # Branch does not exist in this project
1507 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001508 return None
Wink Saville02d79452009-04-10 13:01:24 -07001509
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001510 if head.startswith(R_HEADS):
1511 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001512 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001513 except KeyError:
1514 head = None
1515
1516 if head == revid:
1517 # Same revision; just update HEAD to point to the new
1518 # target branch, but otherwise take no other action.
1519 #
1520 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1521 'ref: %s%s\n' % (R_HEADS, name))
1522 return True
1523
1524 return GitCommand(self,
1525 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001526 capture_stdout=True,
1527 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001528
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001529 def AbandonBranch(self, name):
1530 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001531
1532 Args:
1533 name: The name of the branch to abandon.
1534
1535 Returns:
1536 True if the abandon succeeded; False if it didn't; None if the branch
1537 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001538 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001539 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001540 all_refs = self.bare_ref.all
1541 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001542 # Doesn't exist
1543 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001544
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001545 head = self.work_git.GetHead()
1546 if head == rev:
1547 # We can't destroy the branch while we are sitting
1548 # on it. Switch to a detached HEAD.
1549 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001550 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001551
David Pursehouse8a68ff92012-09-24 12:15:13 +09001552 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001553 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001554 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1555 '%s\n' % revid)
1556 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001557 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001558
1559 return GitCommand(self,
1560 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001561 capture_stdout=True,
1562 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001563
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001564 def PruneHeads(self):
1565 """Prune any topic branches already merged into upstream.
1566 """
1567 cb = self.CurrentBranch
1568 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001569 left = self._allrefs
1570 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001571 if name.startswith(R_HEADS):
1572 name = name[len(R_HEADS):]
1573 if cb is None or name != cb:
1574 kill.append(name)
1575
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001576 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001577 if cb is not None \
1578 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001579 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001580 self.work_git.DetachHead(HEAD)
1581 kill.append(cb)
1582
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001583 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001584 old = self.bare_git.GetHead()
1585 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001586 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1587
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001588 try:
1589 self.bare_git.DetachHead(rev)
1590
1591 b = ['branch', '-d']
1592 b.extend(kill)
1593 b = GitCommand(self, b, bare=True,
1594 capture_stdout=True,
1595 capture_stderr=True)
1596 b.Wait()
1597 finally:
1598 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001599 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001600
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001601 for branch in kill:
1602 if (R_HEADS + branch) not in left:
1603 self.CleanPublishedCache()
1604 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001605
1606 if cb and cb not in kill:
1607 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001608 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001609
1610 kept = []
1611 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001612 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001613 branch = self.GetBranch(branch)
1614 base = branch.LocalMerge
1615 if not base:
1616 base = rev
1617 kept.append(ReviewableBranch(self, branch, base))
1618 return kept
1619
1620
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001621## Submodule Management ##
1622
1623 def GetRegisteredSubprojects(self):
1624 result = []
1625 def rec(subprojects):
1626 if not subprojects:
1627 return
1628 result.extend(subprojects)
1629 for p in subprojects:
1630 rec(p.subprojects)
1631 rec(self.subprojects)
1632 return result
1633
1634 def _GetSubmodules(self):
1635 # Unfortunately we cannot call `git submodule status --recursive` here
1636 # because the working tree might not exist yet, and it cannot be used
1637 # without a working tree in its current implementation.
1638
1639 def get_submodules(gitdir, rev):
1640 # Parse .gitmodules for submodule sub_paths and sub_urls
1641 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1642 if not sub_paths:
1643 return []
1644 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1645 # revision of submodule repository
1646 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1647 submodules = []
1648 for sub_path, sub_url in zip(sub_paths, sub_urls):
1649 try:
1650 sub_rev = sub_revs[sub_path]
1651 except KeyError:
1652 # Ignore non-exist submodules
1653 continue
1654 submodules.append((sub_rev, sub_path, sub_url))
1655 return submodules
1656
1657 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1658 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1659 def parse_gitmodules(gitdir, rev):
1660 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1661 try:
Anthony King7bdac712014-07-16 12:56:40 +01001662 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1663 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001664 except GitError:
1665 return [], []
1666 if p.Wait() != 0:
1667 return [], []
1668
1669 gitmodules_lines = []
1670 fd, temp_gitmodules_path = tempfile.mkstemp()
1671 try:
1672 os.write(fd, p.stdout)
1673 os.close(fd)
1674 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001675 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1676 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001677 if p.Wait() != 0:
1678 return [], []
1679 gitmodules_lines = p.stdout.split('\n')
1680 except GitError:
1681 return [], []
1682 finally:
1683 os.remove(temp_gitmodules_path)
1684
1685 names = set()
1686 paths = {}
1687 urls = {}
1688 for line in gitmodules_lines:
1689 if not line:
1690 continue
1691 m = re_path.match(line)
1692 if m:
1693 names.add(m.group(1))
1694 paths[m.group(1)] = m.group(2)
1695 continue
1696 m = re_url.match(line)
1697 if m:
1698 names.add(m.group(1))
1699 urls[m.group(1)] = m.group(2)
1700 continue
1701 names = sorted(names)
1702 return ([paths.get(name, '') for name in names],
1703 [urls.get(name, '') for name in names])
1704
1705 def git_ls_tree(gitdir, rev, paths):
1706 cmd = ['ls-tree', rev, '--']
1707 cmd.extend(paths)
1708 try:
Anthony King7bdac712014-07-16 12:56:40 +01001709 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1710 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001711 except GitError:
1712 return []
1713 if p.Wait() != 0:
1714 return []
1715 objects = {}
1716 for line in p.stdout.split('\n'):
1717 if not line.strip():
1718 continue
1719 object_rev, object_path = line.split()[2:4]
1720 objects[object_path] = object_rev
1721 return objects
1722
1723 try:
1724 rev = self.GetRevisionId()
1725 except GitError:
1726 return []
1727 return get_submodules(self.gitdir, rev)
1728
1729 def GetDerivedSubprojects(self):
1730 result = []
1731 if not self.Exists:
1732 # If git repo does not exist yet, querying its submodules will
1733 # mess up its states; so return here.
1734 return result
1735 for rev, path, url in self._GetSubmodules():
1736 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001737 relpath, worktree, gitdir, objdir = \
1738 self.manifest.GetSubprojectPaths(self, name, path)
1739 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001740 if project:
1741 result.extend(project.GetDerivedSubprojects())
1742 continue
David James8d201162013-10-11 17:03:19 -07001743
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001744 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001745 url=url,
1746 review=self.remote.review,
1747 revision=self.remote.revision)
1748 subproject = Project(manifest=self.manifest,
1749 name=name,
1750 remote=remote,
1751 gitdir=gitdir,
1752 objdir=objdir,
1753 worktree=worktree,
1754 relpath=relpath,
1755 revisionExpr=self.revisionExpr,
1756 revisionId=rev,
1757 rebase=self.rebase,
1758 groups=self.groups,
1759 sync_c=self.sync_c,
1760 sync_s=self.sync_s,
1761 parent=self,
1762 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001763 result.append(subproject)
1764 result.extend(subproject.GetDerivedSubprojects())
1765 return result
1766
1767
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001769 def _CheckForSha1(self):
1770 try:
1771 # if revision (sha or tag) is not present then following function
1772 # throws an error.
1773 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1774 return True
1775 except GitError:
1776 # There is no such persistent revision. We have to fetch it.
1777 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001778
Julien Campergue335f5ef2013-10-16 11:02:35 +02001779 def _FetchArchive(self, tarpath, cwd=None):
1780 cmd = ['archive', '-v', '-o', tarpath]
1781 cmd.append('--remote=%s' % self.remote.url)
1782 cmd.append('--prefix=%s/' % self.relpath)
1783 cmd.append(self.revisionExpr)
1784
1785 command = GitCommand(self, cmd, cwd=cwd,
1786 capture_stdout=True,
1787 capture_stderr=True)
1788
1789 if command.Wait() != 0:
1790 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1791
Conley Owens80b87fe2014-05-09 17:13:44 -07001792
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001793 def _RemoteFetch(self, name=None,
1794 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001795 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001796 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001797 alt_dir=None,
1798 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001799
1800 is_sha1 = False
1801 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001802 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001803
David Pursehouse9bc422f2014-04-15 10:28:56 +09001804 # The depth should not be used when fetching to a mirror because
1805 # it will result in a shallow repository that cannot be cloned or
1806 # fetched from.
1807 if not self.manifest.IsMirror:
1808 if self.clone_depth:
1809 depth = self.clone_depth
1810 else:
1811 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001812 # The repo project should never be synced with partial depth
1813 if self.relpath == '.repo/repo':
1814 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001815
Shawn Pearce69e04d82014-01-29 12:48:54 -08001816 if depth:
1817 current_branch_only = True
1818
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001819 if ID_RE.match(self.revisionExpr) is not None:
1820 is_sha1 = True
1821
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001822 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001823 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001824 # this is a tag and its sha1 value should never change
1825 tag_name = self.revisionExpr[len(R_TAGS):]
1826
1827 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001828 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001829 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001830 if is_sha1 and not depth:
1831 # When syncing a specific commit and --depth is not set:
1832 # * if upstream is explicitly specified and is not a sha1, fetch only
1833 # upstream as users expect only upstream to be fetch.
1834 # Note: The commit might not be in upstream in which case the sync
1835 # will fail.
1836 # * otherwise, fetch all branches to make sure we end up with the
1837 # specific commit.
1838 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001839
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001840 if not name:
1841 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001842
1843 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001844 remote = self.GetRemote(name)
1845 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001846 ssh_proxy = True
1847
Shawn O. Pearce88443382010-10-08 10:02:09 +02001848 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001849 if alt_dir and 'objects' == os.path.basename(alt_dir):
1850 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001851 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1852 remote = self.GetRemote(name)
1853
David Pursehouse8a68ff92012-09-24 12:15:13 +09001854 all_refs = self.bare_ref.all
1855 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001856 tmp = set()
1857
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301858 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001859 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001860 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001861 all_refs[r] = ref_id
1862 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001863 continue
1864
David Pursehouse8a68ff92012-09-24 12:15:13 +09001865 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001866 continue
1867
David Pursehouse8a68ff92012-09-24 12:15:13 +09001868 r = 'refs/_alt/%s' % ref_id
1869 all_refs[r] = ref_id
1870 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001871 tmp.add(r)
1872
Shawn O. Pearce88443382010-10-08 10:02:09 +02001873 tmp_packed = ''
1874 old_packed = ''
1875
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301876 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001877 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001878 tmp_packed += line
1879 if r not in tmp:
1880 old_packed += line
1881
1882 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001883 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001884 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001885
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001886 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001887
Conley Owensf97e8382015-01-21 11:12:46 -08001888 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001889 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07001890 else:
1891 # If this repo has shallow objects, then we don't know which refs have
1892 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
1893 # do this with projects that don't have shallow objects, since it is less
1894 # efficient.
1895 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
1896 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07001897
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001898 if quiet:
1899 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001900 if not self.worktree:
1901 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001902 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001903
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001904 # If using depth then we should not get all the tags since they may
1905 # be outside of the depth.
1906 if no_tags or depth:
1907 cmd.append('--no-tags')
1908 else:
1909 cmd.append('--tags')
1910
Conley Owens80b87fe2014-05-09 17:13:44 -07001911 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001912 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001913 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001914 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001915 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001916 spec.append('tag')
1917 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001918
David Pursehouse403b64e2015-04-27 10:41:33 +09001919 if not self.manifest.IsMirror:
1920 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06001921 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001922 # Shallow checkout of a specific commit, fetch from that commit and not
1923 # the heads only as the commit might be deeper in the history.
1924 spec.append(branch)
1925 else:
1926 if is_sha1:
1927 branch = self.upstream
1928 if branch is not None and branch.strip():
1929 if not branch.startswith('refs/'):
1930 branch = R_HEADS + branch
1931 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001932 cmd.extend(spec)
1933
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001934 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001935 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001936 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001937 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001938 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001939 ok = True
1940 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001941 # If needed, run the 'git remote prune' the first time through the loop
1942 elif (not _i and
1943 "error:" in gitcmd.stderr and
1944 "git remote prune" in gitcmd.stderr):
1945 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001946 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001947 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001948 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001949 break
1950 continue
Brian Harring14a66742012-09-28 20:21:57 -07001951 elif current_branch_only and is_sha1 and ret == 128:
1952 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1953 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1954 # abort the optimization attempt and do a full sync.
1955 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001956 elif ret < 0:
1957 # Git died with a signal, exit immediately
1958 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001959 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001960
1961 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001962 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001963 if old_packed != '':
1964 _lwrite(packed_refs, old_packed)
1965 else:
1966 os.remove(packed_refs)
1967 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001968
1969 if is_sha1 and current_branch_only and self.upstream:
1970 # We just synced the upstream given branch; verify we
1971 # got what we wanted, else trigger a second run of all
1972 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001973 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06001974 if not depth:
1975 # Avoid infinite recursion when depth is True (since depth implies
1976 # current_branch_only)
1977 return self._RemoteFetch(name=name, current_branch_only=False,
1978 initial=False, quiet=quiet, alt_dir=alt_dir)
1979 if self.clone_depth:
1980 self.clone_depth = None
1981 return self._RemoteFetch(name=name, current_branch_only=current_branch_only,
1982 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07001983
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001984 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001985
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001986 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001987 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001988 return False
1989
1990 remote = self.GetRemote(self.remote.name)
1991 bundle_url = remote.url + '/clone.bundle'
1992 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001993 if GetSchemeFromUrl(bundle_url) not in (
1994 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001995 return False
1996
1997 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1998 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1999
2000 exist_dst = os.path.exists(bundle_dst)
2001 exist_tmp = os.path.exists(bundle_tmp)
2002
2003 if not initial and not exist_dst and not exist_tmp:
2004 return False
2005
2006 if not exist_dst:
2007 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2008 if not exist_dst:
2009 return False
2010
2011 cmd = ['fetch']
2012 if quiet:
2013 cmd.append('--quiet')
2014 if not self.worktree:
2015 cmd.append('--update-head-ok')
2016 cmd.append(bundle_dst)
2017 for f in remote.fetch:
2018 cmd.append(str(f))
2019 cmd.append('refs/tags/*:refs/tags/*')
2020
2021 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002022 if os.path.exists(bundle_dst):
2023 os.remove(bundle_dst)
2024 if os.path.exists(bundle_tmp):
2025 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002026 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002027
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002028 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002029 if os.path.exists(dstPath):
2030 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002031
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002032 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002033 if quiet:
2034 cmd += ['--silent']
2035 if os.path.exists(tmpPath):
2036 size = os.stat(tmpPath).st_size
2037 if size >= 1024:
2038 cmd += ['--continue-at', '%d' % (size,)]
2039 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002040 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002041 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2042 cmd += ['--proxy', os.environ['http_proxy']]
Dan Willemsen0745bb22015-08-17 13:41:45 -07002043 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002044 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002045 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002046 if srcUrl.startswith('persistent-'):
2047 srcUrl = srcUrl[len('persistent-'):]
2048 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002049
Dave Borowitz137d0132015-01-02 11:12:54 -08002050 if IsTrace():
2051 Trace('%s', ' '.join(cmd))
2052 try:
2053 proc = subprocess.Popen(cmd)
2054 except OSError:
2055 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002056
Dave Borowitz137d0132015-01-02 11:12:54 -08002057 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002058
Dave Borowitz137d0132015-01-02 11:12:54 -08002059 if curlret == 22:
2060 # From curl man page:
2061 # 22: HTTP page not retrieved. The requested url was not found or
2062 # returned another error with the HTTP error code being 400 or above.
2063 # This return code only appears if -f, --fail is used.
2064 if not quiet:
2065 print("Server does not provide clone.bundle; ignoring.",
2066 file=sys.stderr)
2067 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002068
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002069 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002070 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002071 os.rename(tmpPath, dstPath)
2072 return True
2073 else:
2074 os.remove(tmpPath)
2075 return False
2076 else:
2077 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002078
Kris Giesingc8d882a2014-12-23 13:02:32 -08002079 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002080 try:
2081 with open(path) as f:
2082 if f.read(16) == '# v2 git bundle\n':
2083 return True
2084 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002085 if not quiet:
2086 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002087 return False
2088 except OSError:
2089 return False
2090
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002091 def _Checkout(self, rev, quiet=False):
2092 cmd = ['checkout']
2093 if quiet:
2094 cmd.append('-q')
2095 cmd.append(rev)
2096 cmd.append('--')
2097 if GitCommand(self, cmd).Wait() != 0:
2098 if self._allrefs:
2099 raise GitError('%s checkout %s ' % (self.name, rev))
2100
Anthony King7bdac712014-07-16 12:56:40 +01002101 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002102 cmd = ['cherry-pick']
2103 cmd.append(rev)
2104 cmd.append('--')
2105 if GitCommand(self, cmd).Wait() != 0:
2106 if self._allrefs:
2107 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2108
Anthony King7bdac712014-07-16 12:56:40 +01002109 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002110 cmd = ['revert']
2111 cmd.append('--no-edit')
2112 cmd.append(rev)
2113 cmd.append('--')
2114 if GitCommand(self, cmd).Wait() != 0:
2115 if self._allrefs:
2116 raise GitError('%s revert %s ' % (self.name, rev))
2117
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002118 def _ResetHard(self, rev, quiet=True):
2119 cmd = ['reset', '--hard']
2120 if quiet:
2121 cmd.append('-q')
2122 cmd.append(rev)
2123 if GitCommand(self, cmd).Wait() != 0:
2124 raise GitError('%s reset --hard %s ' % (self.name, rev))
2125
Anthony King7bdac712014-07-16 12:56:40 +01002126 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002127 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002128 if onto is not None:
2129 cmd.extend(['--onto', onto])
2130 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002131 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002132 raise GitError('%s rebase %s ' % (self.name, upstream))
2133
Pierre Tardy3d125942012-05-04 12:18:12 +02002134 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002135 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002136 if ffonly:
2137 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002138 if GitCommand(self, cmd).Wait() != 0:
2139 raise GitError('%s merge %s ' % (self.name, head))
2140
Kevin Degiabaa7f32014-11-12 11:27:45 -07002141 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002142 init_git_dir = not os.path.exists(self.gitdir)
2143 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002144 try:
2145 # Initialize the bare repository, which contains all of the objects.
2146 if init_obj_dir:
2147 os.makedirs(self.objdir)
2148 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002149
Kevin Degib1a07b82015-07-27 13:33:43 -06002150 # If we have a separate directory to hold refs, initialize it as well.
2151 if self.objdir != self.gitdir:
2152 if init_git_dir:
2153 os.makedirs(self.gitdir)
2154
2155 if init_obj_dir or init_git_dir:
2156 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2157 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002158 try:
2159 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2160 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002161 if force_sync:
David Pursehouse25857b82015-08-19 18:06:22 +09002162 print("Retrying clone after deleting %s" % self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002163 try:
2164 shutil.rmtree(os.path.realpath(self.gitdir))
2165 if self.worktree and os.path.exists(
2166 os.path.realpath(self.worktree)):
2167 shutil.rmtree(os.path.realpath(self.worktree))
2168 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2169 except:
2170 raise e
2171 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002172
Kevin Degi384b3c52014-10-16 16:02:58 -06002173 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002174 mp = self.manifest.manifestProject
2175 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002176
Kevin Degib1a07b82015-07-27 13:33:43 -06002177 if ref_dir or mirror_git:
2178 if not mirror_git:
2179 mirror_git = os.path.join(ref_dir, self.name + '.git')
2180 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2181 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002182
Kevin Degib1a07b82015-07-27 13:33:43 -06002183 if os.path.exists(mirror_git):
2184 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002185
Kevin Degib1a07b82015-07-27 13:33:43 -06002186 elif os.path.exists(repo_git):
2187 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002188
Kevin Degib1a07b82015-07-27 13:33:43 -06002189 else:
2190 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002191
Kevin Degib1a07b82015-07-27 13:33:43 -06002192 if ref_dir:
2193 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2194 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002195
Kevin Degib1a07b82015-07-27 13:33:43 -06002196 self._UpdateHooks()
2197
2198 m = self.manifest.manifestProject.config
2199 for key in ['user.name', 'user.email']:
2200 if m.Has(key, include_defaults=False):
2201 self.config.SetString(key, m.GetString(key))
2202 if self.manifest.IsMirror:
2203 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002204 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002205 self.config.SetString('core.bare', None)
2206 except Exception:
2207 if init_obj_dir and os.path.exists(self.objdir):
2208 shutil.rmtree(self.objdir)
2209 if init_git_dir and os.path.exists(self.gitdir):
2210 shutil.rmtree(self.gitdir)
2211 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002212
Jimmie Westera0444582012-10-24 13:44:42 +02002213 def _UpdateHooks(self):
2214 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002215 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002216
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002217 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002218 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002219 if not os.path.exists(hooks):
2220 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002221 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002222 name = os.path.basename(stock_hook)
2223
Victor Boivie65e0f352011-04-18 11:23:29 +02002224 if name in ('commit-msg',) and not self.remote.review \
2225 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002226 # Don't install a Gerrit Code Review hook if this
2227 # project does not appear to use it for reviews.
2228 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002229 # Since the manifest project is one of those, but also
2230 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002231 continue
2232
2233 dst = os.path.join(hooks, name)
2234 if os.path.islink(dst):
2235 continue
2236 if os.path.exists(dst):
2237 if filecmp.cmp(stock_hook, dst, shallow=False):
2238 os.remove(dst)
2239 else:
David Pursehousedc2545c2015-08-24 14:43:45 +09002240 _warn("%s: Not replacing locally modified %s hook", self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002241 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002242 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002243 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002244 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002245 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002246 raise GitError('filesystem must support symlinks')
2247 else:
2248 raise
2249
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002250 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002251 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002252 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002253 remote.url = self.remote.url
2254 remote.review = self.remote.review
2255 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002256
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002257 if self.worktree:
2258 remote.ResetFetch(mirror=False)
2259 else:
2260 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002261 remote.Save()
2262
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002263 def _InitMRef(self):
2264 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002265 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002266
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002267 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002268 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002269
2270 def _InitAnyMRef(self, ref):
2271 cur = self.bare_ref.symref(ref)
2272
2273 if self.revisionId:
2274 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2275 msg = 'manifest set to %s' % self.revisionId
2276 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002277 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002278 else:
2279 remote = self.GetRemote(self.remote.name)
2280 dst = remote.ToLocal(self.revisionExpr)
2281 if cur != dst:
2282 msg = 'manifest set to %s' % self.revisionExpr
2283 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002284
Kevin Degi384b3c52014-10-16 16:02:58 -06002285 def _CheckDirReference(self, srcdir, destdir, share_refs):
2286 symlink_files = self.shareable_files
2287 symlink_dirs = self.shareable_dirs
2288 if share_refs:
2289 symlink_files += self.working_tree_files
2290 symlink_dirs += self.working_tree_dirs
2291 to_symlink = symlink_files + symlink_dirs
2292 for name in set(to_symlink):
2293 dst = os.path.realpath(os.path.join(destdir, name))
2294 if os.path.lexists(dst):
2295 src = os.path.realpath(os.path.join(srcdir, name))
2296 # Fail if the links are pointing to the wrong place
2297 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002298 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002299 'work tree. If you\'re comfortable with the '
2300 'possibility of losing the work tree\'s git metadata,'
2301 ' use `repo sync --force-sync {0}` to '
2302 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002303
David James8d201162013-10-11 17:03:19 -07002304 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2305 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2306
2307 Args:
2308 gitdir: The bare git repository. Must already be initialized.
2309 dotgit: The repository you would like to initialize.
2310 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2311 Only one work tree can store refs under a given |gitdir|.
2312 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2313 This saves you the effort of initializing |dotgit| yourself.
2314 """
Kevin Degi384b3c52014-10-16 16:02:58 -06002315 symlink_files = self.shareable_files
2316 symlink_dirs = self.shareable_dirs
David James8d201162013-10-11 17:03:19 -07002317 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002318 symlink_files += self.working_tree_files
2319 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002320 to_symlink = symlink_files + symlink_dirs
2321
2322 to_copy = []
2323 if copy_all:
2324 to_copy = os.listdir(gitdir)
2325
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002326 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002327 for name in set(to_copy).union(to_symlink):
2328 try:
2329 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002330 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002331
Kevin Degi384b3c52014-10-16 16:02:58 -06002332 if os.path.lexists(dst):
2333 continue
David James8d201162013-10-11 17:03:19 -07002334
2335 # If the source dir doesn't exist, create an empty dir.
2336 if name in symlink_dirs and not os.path.lexists(src):
2337 os.makedirs(src)
2338
Conley Owens80b87fe2014-05-09 17:13:44 -07002339 # If the source file doesn't exist, ensure the destination
2340 # file doesn't either.
2341 if name in symlink_files and not os.path.lexists(src):
2342 try:
2343 os.remove(dst)
2344 except OSError:
2345 pass
2346
David James8d201162013-10-11 17:03:19 -07002347 if name in to_symlink:
2348 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2349 elif copy_all and not os.path.islink(dst):
2350 if os.path.isdir(src):
2351 shutil.copytree(src, dst)
2352 elif os.path.isfile(src):
2353 shutil.copy(src, dst)
2354 except OSError as e:
2355 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002356 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002357 else:
2358 raise
2359
Kevin Degiabaa7f32014-11-12 11:27:45 -07002360 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002361 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002362 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002363 try:
2364 if init_dotgit:
2365 os.makedirs(dotgit)
2366 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2367 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002368
Kevin Degiabaa7f32014-11-12 11:27:45 -07002369 try:
2370 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2371 except GitError as e:
2372 if force_sync:
2373 try:
2374 shutil.rmtree(dotgit)
2375 return self._InitWorkTree(force_sync=False)
2376 except:
2377 raise e
2378 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002379
Kevin Degib1a07b82015-07-27 13:33:43 -06002380 if init_dotgit:
2381 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002382
Kevin Degib1a07b82015-07-27 13:33:43 -06002383 cmd = ['read-tree', '--reset', '-u']
2384 cmd.append('-v')
2385 cmd.append(HEAD)
2386 if GitCommand(self, cmd).Wait() != 0:
2387 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002388
Kevin Degib1a07b82015-07-27 13:33:43 -06002389 self._CopyAndLinkFiles()
2390 except Exception:
2391 if init_dotgit:
2392 shutil.rmtree(dotgit)
2393 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002394
2395 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002396 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002397
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002398 def _revlist(self, *args, **kw):
2399 a = []
2400 a.extend(args)
2401 a.append('--')
2402 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002403
2404 @property
2405 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002406 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002407
Julien Camperguedd654222014-01-09 16:21:37 +01002408 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2409 """Get logs between two revisions of this project."""
2410 comp = '..'
2411 if rev1:
2412 revs = [rev1]
2413 if rev2:
2414 revs.extend([comp, rev2])
2415 cmd = ['log', ''.join(revs)]
2416 out = DiffColoring(self.config)
2417 if out.is_on and color:
2418 cmd.append('--color')
2419 if oneline:
2420 cmd.append('--oneline')
2421
2422 try:
2423 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2424 if log.Wait() == 0:
2425 return log.stdout
2426 except GitError:
2427 # worktree may not exist if groups changed for example. In that case,
2428 # try in gitdir instead.
2429 if not os.path.exists(self.worktree):
2430 return self.bare_git.log(*cmd[1:])
2431 else:
2432 raise
2433 return None
2434
2435 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2436 """Get the list of logs from this revision to given revisionId"""
2437 logs = {}
2438 selfId = self.GetRevisionId(self._allrefs)
2439 toId = toProject.GetRevisionId(toProject._allrefs)
2440
2441 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2442 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2443 return logs
2444
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002445 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002446 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002447 self._project = project
2448 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002449 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002450
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002451 def LsOthers(self):
2452 p = GitCommand(self._project,
2453 ['ls-files',
2454 '-z',
2455 '--others',
2456 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002457 bare=False,
David James8d201162013-10-11 17:03:19 -07002458 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002459 capture_stdout=True,
2460 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002461 if p.Wait() == 0:
2462 out = p.stdout
2463 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002464 return out[:-1].split('\0') # pylint: disable=W1401
2465 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002466 return []
2467
2468 def DiffZ(self, name, *args):
2469 cmd = [name]
2470 cmd.append('-z')
2471 cmd.extend(args)
2472 p = GitCommand(self._project,
2473 cmd,
David James8d201162013-10-11 17:03:19 -07002474 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002475 bare=False,
2476 capture_stdout=True,
2477 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002478 try:
2479 out = p.process.stdout.read()
2480 r = {}
2481 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002482 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002483 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002484 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002485 info = next(out)
2486 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002487 except StopIteration:
2488 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002489
2490 class _Info(object):
2491 def __init__(self, path, omode, nmode, oid, nid, state):
2492 self.path = path
2493 self.src_path = None
2494 self.old_mode = omode
2495 self.new_mode = nmode
2496 self.old_id = oid
2497 self.new_id = nid
2498
2499 if len(state) == 1:
2500 self.status = state
2501 self.level = None
2502 else:
2503 self.status = state[:1]
2504 self.level = state[1:]
2505 while self.level.startswith('0'):
2506 self.level = self.level[1:]
2507
2508 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002509 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002510 if info.status in ('R', 'C'):
2511 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002512 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002513 r[info.path] = info
2514 return r
2515 finally:
2516 p.Wait()
2517
2518 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002519 if self._bare:
2520 path = os.path.join(self._project.gitdir, HEAD)
2521 else:
2522 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002523 try:
2524 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002525 except IOError as e:
2526 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002527 try:
2528 line = fd.read()
2529 finally:
2530 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302531 try:
2532 line = line.decode()
2533 except AttributeError:
2534 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002535 if line.startswith('ref: '):
2536 return line[5:-1]
2537 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002538
2539 def SetHead(self, ref, message=None):
2540 cmdv = []
2541 if message is not None:
2542 cmdv.extend(['-m', message])
2543 cmdv.append(HEAD)
2544 cmdv.append(ref)
2545 self.symbolic_ref(*cmdv)
2546
2547 def DetachHead(self, new, message=None):
2548 cmdv = ['--no-deref']
2549 if message is not None:
2550 cmdv.extend(['-m', message])
2551 cmdv.append(HEAD)
2552 cmdv.append(new)
2553 self.update_ref(*cmdv)
2554
2555 def UpdateRef(self, name, new, old=None,
2556 message=None,
2557 detach=False):
2558 cmdv = []
2559 if message is not None:
2560 cmdv.extend(['-m', message])
2561 if detach:
2562 cmdv.append('--no-deref')
2563 cmdv.append(name)
2564 cmdv.append(new)
2565 if old is not None:
2566 cmdv.append(old)
2567 self.update_ref(*cmdv)
2568
2569 def DeleteRef(self, name, old=None):
2570 if not old:
2571 old = self.rev_parse(name)
2572 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002573 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002574
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002575 def rev_list(self, *args, **kw):
2576 if 'format' in kw:
2577 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2578 else:
2579 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002580 cmdv.extend(args)
2581 p = GitCommand(self._project,
2582 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002583 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002584 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002585 capture_stdout=True,
2586 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002587 r = []
2588 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002589 if line[-1] == '\n':
2590 line = line[:-1]
2591 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002592 if p.Wait() != 0:
2593 raise GitError('%s rev-list %s: %s' % (
2594 self._project.name,
2595 str(args),
2596 p.stderr))
2597 return r
2598
2599 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002600 """Allow arbitrary git commands using pythonic syntax.
2601
2602 This allows you to do things like:
2603 git_obj.rev_parse('HEAD')
2604
2605 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2606 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002607 Any other positional arguments will be passed to the git command, and the
2608 following keyword arguments are supported:
2609 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002610
2611 Args:
2612 name: The name of the git command to call. Any '_' characters will
2613 be replaced with '-'.
2614
2615 Returns:
2616 A callable object that will try to call git with the named command.
2617 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002618 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002619 def runner(*args, **kwargs):
2620 cmdv = []
2621 config = kwargs.pop('config', None)
2622 for k in kwargs:
2623 raise TypeError('%s() got an unexpected keyword argument %r'
2624 % (name, k))
2625 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002626 if not git_require((1, 7, 2)):
2627 raise ValueError('cannot set config on command line for %s()'
2628 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302629 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002630 cmdv.append('-c')
2631 cmdv.append('%s=%s' % (k, v))
2632 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002633 cmdv.extend(args)
2634 p = GitCommand(self._project,
2635 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002636 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002637 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002638 capture_stdout=True,
2639 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002640 if p.Wait() != 0:
2641 raise GitError('%s %s: %s' % (
2642 self._project.name,
2643 name,
2644 p.stderr))
2645 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302646 try:
Conley Owensedd01512013-09-26 12:59:58 -07002647 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302648 except AttributeError:
2649 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002650 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2651 return r[:-1]
2652 return r
2653 return runner
2654
2655
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002656class _PriorSyncFailedError(Exception):
2657 def __str__(self):
2658 return 'prior sync failed; rebase still in progress'
2659
2660class _DirtyError(Exception):
2661 def __str__(self):
2662 return 'contains uncommitted changes'
2663
2664class _InfoMessage(object):
2665 def __init__(self, project, text):
2666 self.project = project
2667 self.text = text
2668
2669 def Print(self, syncbuf):
2670 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2671 syncbuf.out.nl()
2672
2673class _Failure(object):
2674 def __init__(self, project, why):
2675 self.project = project
2676 self.why = why
2677
2678 def Print(self, syncbuf):
2679 syncbuf.out.fail('error: %s/: %s',
2680 self.project.relpath,
2681 str(self.why))
2682 syncbuf.out.nl()
2683
2684class _Later(object):
2685 def __init__(self, project, action):
2686 self.project = project
2687 self.action = action
2688
2689 def Run(self, syncbuf):
2690 out = syncbuf.out
2691 out.project('project %s/', self.project.relpath)
2692 out.nl()
2693 try:
2694 self.action()
2695 out.nl()
2696 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002697 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002698 out.nl()
2699 return False
2700
2701class _SyncColoring(Coloring):
2702 def __init__(self, config):
2703 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002704 self.project = self.printer('header', attr='bold')
2705 self.info = self.printer('info')
2706 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002707
2708class SyncBuffer(object):
2709 def __init__(self, config, detach_head=False):
2710 self._messages = []
2711 self._failures = []
2712 self._later_queue1 = []
2713 self._later_queue2 = []
2714
2715 self.out = _SyncColoring(config)
2716 self.out.redirect(sys.stderr)
2717
2718 self.detach_head = detach_head
2719 self.clean = True
2720
2721 def info(self, project, fmt, *args):
2722 self._messages.append(_InfoMessage(project, fmt % args))
2723
2724 def fail(self, project, err=None):
2725 self._failures.append(_Failure(project, err))
2726 self.clean = False
2727
2728 def later1(self, project, what):
2729 self._later_queue1.append(_Later(project, what))
2730
2731 def later2(self, project, what):
2732 self._later_queue2.append(_Later(project, what))
2733
2734 def Finish(self):
2735 self._PrintMessages()
2736 self._RunLater()
2737 self._PrintMessages()
2738 return self.clean
2739
2740 def _RunLater(self):
2741 for q in ['_later_queue1', '_later_queue2']:
2742 if not self._RunQueue(q):
2743 return
2744
2745 def _RunQueue(self, queue):
2746 for m in getattr(self, queue):
2747 if not m.Run(self):
2748 self.clean = False
2749 return False
2750 setattr(self, queue, [])
2751 return True
2752
2753 def _PrintMessages(self):
2754 for m in self._messages:
2755 m.Print(self)
2756 for m in self._failures:
2757 m.Print(self)
2758
2759 self._messages = []
2760 self._failures = []
2761
2762
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002763class MetaProject(Project):
2764 """A special project housed under .repo.
2765 """
2766 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002767 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002768 manifest=manifest,
2769 name=name,
2770 gitdir=gitdir,
2771 objdir=gitdir,
2772 worktree=worktree,
2773 remote=RemoteSpec('origin'),
2774 relpath='.repo/%s' % name,
2775 revisionExpr='refs/heads/master',
2776 revisionId=None,
2777 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002778
2779 def PreSync(self):
2780 if self.Exists:
2781 cb = self.CurrentBranch
2782 if cb:
2783 base = self.GetBranch(cb).merge
2784 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002785 self.revisionExpr = base
2786 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002787
Anthony King7bdac712014-07-16 12:56:40 +01002788 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002789 """ Prepare MetaProject for manifest branch switch
2790 """
2791
2792 # detach and delete manifest branch, allowing a new
2793 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002794 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002795 self.Sync_LocalHalf(syncbuf)
2796 syncbuf.Finish()
2797
2798 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002799 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002800 capture_stdout=True,
2801 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002802
2803
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002804 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002805 def LastFetch(self):
2806 try:
2807 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2808 return os.path.getmtime(fh)
2809 except OSError:
2810 return 0
2811
2812 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002813 def HasChanges(self):
2814 """Has the remote received new commits not yet checked out?
2815 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002816 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002817 return False
2818
David Pursehouse8a68ff92012-09-24 12:15:13 +09002819 all_refs = self.bare_ref.all
2820 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002821 head = self.work_git.GetHead()
2822 if head.startswith(R_HEADS):
2823 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002824 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002825 except KeyError:
2826 head = None
2827
2828 if revid == head:
2829 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002830 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002831 return True
2832 return False