blob: 437edcaac6dfc839eed343ad9e5453d4dc757c4b [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070018import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Dave Borowitz137d0132015-01-02 11:12:54 -080029import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070032from git_command import GitCommand, git_require
Dan Willemsen0745bb22015-08-17 13:41:45 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070034from error import GitError, HookError, UploadError, DownloadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080035from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080036from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070037from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070038
Shawn O. Pearced237b692009-04-17 18:49:50 -070039from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
David Pursehouse59bbb582013-05-17 10:49:33 +090041from pyversion import is_python3
42if not is_python3():
43 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053044 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090045 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053046
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070047def _lwrite(path, content):
48 lock = '%s.lock' % path
49
Chirayu Desai303a82f2014-08-19 22:57:17 +053050 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070051 try:
52 fd.write(content)
53 finally:
54 fd.close()
55
56 try:
57 os.rename(lock, path)
58 except OSError:
59 os.remove(lock)
60 raise
61
Shawn O. Pearce48244782009-04-16 08:25:57 -070062def _error(fmt, *args):
63 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070064 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070065
David Pursehousef33929d2015-08-24 14:39:14 +090066def _warn(fmt, *args):
67 msg = fmt % args
68 print('warn: %s' % msg, file=sys.stderr)
69
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070070def not_rev(r):
71 return '^' + r
72
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080073def sq(r):
74 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080075
Jonathan Nieder93719792015-03-17 11:29:58 -070076_project_hook_list = None
77def _ProjectHooks():
78 """List the hooks present in the 'hooks' directory.
79
80 These hooks are project hooks and are copied to the '.git/hooks' directory
81 of all subprojects.
82
83 This function caches the list of hooks (based on the contents of the
84 'repo/hooks' directory) on the first call.
85
86 Returns:
87 A list of absolute paths to all of the files in the hooks directory.
88 """
89 global _project_hook_list
90 if _project_hook_list is None:
91 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
92 d = os.path.join(d, 'hooks')
93 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
94 return _project_hook_list
95
96
Shawn O. Pearce632768b2008-10-23 11:58:52 -070097class DownloadedChange(object):
98 _commit_cache = None
99
100 def __init__(self, project, base, change_id, ps_id, commit):
101 self.project = project
102 self.base = base
103 self.change_id = change_id
104 self.ps_id = ps_id
105 self.commit = commit
106
107 @property
108 def commits(self):
109 if self._commit_cache is None:
110 self._commit_cache = self.project.bare_git.rev_list(
111 '--abbrev=8',
112 '--abbrev-commit',
113 '--pretty=oneline',
114 '--reverse',
115 '--date-order',
116 not_rev(self.base),
117 self.commit,
118 '--')
119 return self._commit_cache
120
121
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700122class ReviewableBranch(object):
123 _commit_cache = None
124
125 def __init__(self, project, branch, base):
126 self.project = project
127 self.branch = branch
128 self.base = base
129
130 @property
131 def name(self):
132 return self.branch.name
133
134 @property
135 def commits(self):
136 if self._commit_cache is None:
137 self._commit_cache = self.project.bare_git.rev_list(
138 '--abbrev=8',
139 '--abbrev-commit',
140 '--pretty=oneline',
141 '--reverse',
142 '--date-order',
143 not_rev(self.base),
144 R_HEADS + self.name,
145 '--')
146 return self._commit_cache
147
148 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800149 def unabbrev_commits(self):
150 r = dict()
151 for commit in self.project.bare_git.rev_list(
152 not_rev(self.base),
153 R_HEADS + self.name,
154 '--'):
155 r[commit[0:8]] = commit
156 return r
157
158 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159 def date(self):
160 return self.project.bare_git.log(
161 '--pretty=format:%cd',
162 '-n', '1',
163 R_HEADS + self.name,
164 '--')
165
Bryan Jacobsf609f912013-05-06 13:36:24 -0400166 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800167 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700168 people,
Brian Harring435370c2012-07-28 15:37:04 -0700169 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400170 draft=draft,
171 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700173 def GetPublishedRefs(self):
174 refs = {}
175 output = self.project.bare_git.ls_remote(
176 self.branch.remote.SshReviewUrl(self.project.UserEmail),
177 'refs/changes/*')
178 for line in output.split('\n'):
179 try:
180 (sha, ref) = line.split()
181 refs[sha] = ref
182 except ValueError:
183 pass
184
185 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186
187class StatusColoring(Coloring):
188 def __init__(self, config):
189 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100190 self.project = self.printer('header', attr='bold')
191 self.branch = self.printer('header', attr='bold')
192 self.nobranch = self.printer('nobranch', fg='red')
193 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700194
Anthony King7bdac712014-07-16 12:56:40 +0100195 self.added = self.printer('added', fg='green')
196 self.changed = self.printer('changed', fg='red')
197 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700198
199
200class DiffColoring(Coloring):
201 def __init__(self, config):
202 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100203 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204
Anthony King7bdac712014-07-16 12:56:40 +0100205class _Annotation(object):
James W. Mills24c13082012-04-12 15:04:13 -0500206 def __init__(self, name, value, keep):
207 self.name = name
208 self.value = value
209 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210
Anthony King7bdac712014-07-16 12:56:40 +0100211class _CopyFile(object):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800212 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213 self.src = src
214 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800215 self.abs_src = abssrc
216 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217
218 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800219 src = self.abs_src
220 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221 # copy file if it does not exist or is out of date
222 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
223 try:
224 # remove existing file first, since it might be read-only
225 if os.path.exists(dest):
226 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400227 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200228 dest_dir = os.path.dirname(dest)
229 if not os.path.isdir(dest_dir):
230 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231 shutil.copy(src, dest)
232 # make the file read-only
233 mode = os.stat(dest)[stat.ST_MODE]
234 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
235 os.chmod(dest, mode)
236 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700237 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238
Anthony King7bdac712014-07-16 12:56:40 +0100239class _LinkFile(object):
Wink Saville4c426ef2015-06-03 08:05:17 -0700240 def __init__(self, git_worktree, src, dest, relsrc, absdest):
241 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500242 self.src = src
243 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700244 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500245 self.abs_dest = absdest
246
Wink Saville4c426ef2015-06-03 08:05:17 -0700247 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500248 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700249 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500250 try:
251 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800252 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700253 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500254 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700255 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500256 if not os.path.isdir(dest_dir):
257 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700258 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500259 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700260 _error('Cannot link file %s to %s', relSrc, absDest)
261
262 def _Link(self):
263 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
264 on the src linking all of the files in the source in to the destination
265 directory.
266 """
267 # We use the absSrc to handle the situation where the current directory
268 # is not the root of the repo
269 absSrc = os.path.join(self.git_worktree, self.src)
270 if os.path.exists(absSrc):
271 # Entity exists so just a simple one to one link operation
272 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
273 else:
274 # Entity doesn't exist assume there is a wild card
275 absDestDir = self.abs_dest
276 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
277 _error('Link error: src with wildcard, %s must be a directory',
278 absDestDir)
279 else:
280 absSrcFiles = glob.glob(absSrc)
281 for absSrcFile in absSrcFiles:
282 # Create a releative path from source dir to destination dir
283 absSrcDir = os.path.dirname(absSrcFile)
284 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
285
286 # Get the source file name
287 srcFile = os.path.basename(absSrcFile)
288
289 # Now form the final full paths to srcFile. They will be
290 # absolute for the desintaiton and relative for the srouce.
291 absDest = os.path.join(absDestDir, srcFile)
292 relSrc = os.path.join(relSrcDir, srcFile)
293 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500294
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700295class RemoteSpec(object):
296 def __init__(self,
297 name,
Anthony King7bdac712014-07-16 12:56:40 +0100298 url=None,
299 review=None,
300 revision=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700301 self.name = name
302 self.url = url
303 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100304 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700305
Doug Anderson37282b42011-03-04 11:54:18 -0800306class RepoHook(object):
307 """A RepoHook contains information about a script to run as a hook.
308
309 Hooks are used to run a python script before running an upload (for instance,
310 to run presubmit checks). Eventually, we may have hooks for other actions.
311
312 This shouldn't be confused with files in the 'repo/hooks' directory. Those
313 files are copied into each '.git/hooks' folder for each project. Repo-level
314 hooks are associated instead with repo actions.
315
316 Hooks are always python. When a hook is run, we will load the hook into the
317 interpreter and execute its main() function.
318 """
319 def __init__(self,
320 hook_type,
321 hooks_project,
322 topdir,
323 abort_if_user_denies=False):
324 """RepoHook constructor.
325
326 Params:
327 hook_type: A string representing the type of hook. This is also used
328 to figure out the name of the file containing the hook. For
329 example: 'pre-upload'.
330 hooks_project: The project containing the repo hooks. If you have a
331 manifest, this is manifest.repo_hooks_project. OK if this is None,
332 which will make the hook a no-op.
333 topdir: Repo's top directory (the one containing the .repo directory).
334 Scripts will run with CWD as this directory. If you have a manifest,
335 this is manifest.topdir
336 abort_if_user_denies: If True, we'll throw a HookError() if the user
337 doesn't allow us to run the hook.
338 """
339 self._hook_type = hook_type
340 self._hooks_project = hooks_project
341 self._topdir = topdir
342 self._abort_if_user_denies = abort_if_user_denies
343
344 # Store the full path to the script for convenience.
345 if self._hooks_project:
346 self._script_fullpath = os.path.join(self._hooks_project.worktree,
347 self._hook_type + '.py')
348 else:
349 self._script_fullpath = None
350
351 def _GetHash(self):
352 """Return a hash of the contents of the hooks directory.
353
354 We'll just use git to do this. This hash has the property that if anything
355 changes in the directory we will return a different has.
356
357 SECURITY CONSIDERATION:
358 This hash only represents the contents of files in the hook directory, not
359 any other files imported or called by hooks. Changes to imported files
360 can change the script behavior without affecting the hash.
361
362 Returns:
363 A string representing the hash. This will always be ASCII so that it can
364 be printed to the user easily.
365 """
366 assert self._hooks_project, "Must have hooks to calculate their hash."
367
368 # We will use the work_git object rather than just calling GetRevisionId().
369 # That gives us a hash of the latest checked in version of the files that
370 # the user will actually be executing. Specifically, GetRevisionId()
371 # doesn't appear to change even if a user checks out a different version
372 # of the hooks repo (via git checkout) nor if a user commits their own revs.
373 #
374 # NOTE: Local (non-committed) changes will not be factored into this hash.
375 # I think this is OK, since we're really only worried about warning the user
376 # about upstream changes.
377 return self._hooks_project.work_git.rev_parse('HEAD')
378
379 def _GetMustVerb(self):
380 """Return 'must' if the hook is required; 'should' if not."""
381 if self._abort_if_user_denies:
382 return 'must'
383 else:
384 return 'should'
385
386 def _CheckForHookApproval(self):
387 """Check to see whether this hook has been approved.
388
389 We'll look at the hash of all of the hooks. If this matches the hash that
390 the user last approved, we're done. If it doesn't, we'll ask the user
391 about approval.
392
393 Note that we ask permission for each individual hook even though we use
394 the hash of all hooks when detecting changes. We'd like the user to be
395 able to approve / deny each hook individually. We only use the hash of all
396 hooks because there is no other easy way to detect changes to local imports.
397
398 Returns:
399 True if this hook is approved to run; False otherwise.
400
401 Raises:
402 HookError: Raised if the user doesn't approve and abort_if_user_denies
403 was passed to the consturctor.
404 """
Doug Anderson37282b42011-03-04 11:54:18 -0800405 hooks_config = self._hooks_project.config
406 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
407
408 # Get the last hash that the user approved for this hook; may be None.
409 old_hash = hooks_config.GetString(git_approval_key)
410
411 # Get the current hash so we can tell if scripts changed since approval.
412 new_hash = self._GetHash()
413
414 if old_hash is not None:
415 # User previously approved hook and asked not to be prompted again.
416 if new_hash == old_hash:
417 # Approval matched. We're done.
418 return True
419 else:
420 # Give the user a reason why we're prompting, since they last told
421 # us to "never ask again".
422 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
423 self._hook_type)
424 else:
425 prompt = ''
426
427 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
428 if sys.stdout.isatty():
429 prompt += ('Repo %s run the script:\n'
430 ' %s\n'
431 '\n'
432 'Do you want to allow this script to run '
433 '(yes/yes-never-ask-again/NO)? ') % (
434 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530435 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900436 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800437
438 # User is doing a one-time approval.
439 if response in ('y', 'yes'):
440 return True
441 elif response == 'yes-never-ask-again':
442 hooks_config.SetString(git_approval_key, new_hash)
443 return True
444
445 # For anything else, we'll assume no approval.
446 if self._abort_if_user_denies:
447 raise HookError('You must allow the %s hook or use --no-verify.' %
448 self._hook_type)
449
450 return False
451
452 def _ExecuteHook(self, **kwargs):
453 """Actually execute the given hook.
454
455 This will run the hook's 'main' function in our python interpreter.
456
457 Args:
458 kwargs: Keyword arguments to pass to the hook. These are often specific
459 to the hook type. For instance, pre-upload hooks will contain
460 a project_list.
461 """
462 # Keep sys.path and CWD stashed away so that we can always restore them
463 # upon function exit.
464 orig_path = os.getcwd()
465 orig_syspath = sys.path
466
467 try:
468 # Always run hooks with CWD as topdir.
469 os.chdir(self._topdir)
470
471 # Put the hook dir as the first item of sys.path so hooks can do
472 # relative imports. We want to replace the repo dir as [0] so
473 # hooks can't import repo files.
474 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
475
476 # Exec, storing global context in the context dict. We catch exceptions
477 # and convert to a HookError w/ just the failing traceback.
478 context = {}
479 try:
Anthony King70f68902014-05-05 21:15:34 +0100480 exec(compile(open(self._script_fullpath).read(),
481 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800482 except Exception:
483 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
484 traceback.format_exc(), self._hook_type))
485
486 # Running the script should have defined a main() function.
487 if 'main' not in context:
488 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
489
490
491 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
492 # We don't actually want hooks to define their main with this argument--
493 # it's there to remind them that their hook should always take **kwargs.
494 # For instance, a pre-upload hook should be defined like:
495 # def main(project_list, **kwargs):
496 #
497 # This allows us to later expand the API without breaking old hooks.
498 kwargs = kwargs.copy()
499 kwargs['hook_should_take_kwargs'] = True
500
501 # Call the main function in the hook. If the hook should cause the
502 # build to fail, it will raise an Exception. We'll catch that convert
503 # to a HookError w/ just the failing traceback.
504 try:
505 context['main'](**kwargs)
506 except Exception:
507 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
508 'above.' % (
509 traceback.format_exc(), self._hook_type))
510 finally:
511 # Restore sys.path and CWD.
512 sys.path = orig_syspath
513 os.chdir(orig_path)
514
515 def Run(self, user_allows_all_hooks, **kwargs):
516 """Run the hook.
517
518 If the hook doesn't exist (because there is no hooks project or because
519 this particular hook is not enabled), this is a no-op.
520
521 Args:
522 user_allows_all_hooks: If True, we will never prompt about running the
523 hook--we'll just assume it's OK to run it.
524 kwargs: Keyword arguments to pass to the hook. These are often specific
525 to the hook type. For instance, pre-upload hooks will contain
526 a project_list.
527
528 Raises:
529 HookError: If there was a problem finding the hook or the user declined
530 to run a required hook (from _CheckForHookApproval).
531 """
532 # No-op if there is no hooks project or if hook is disabled.
533 if ((not self._hooks_project) or
534 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
535 return
536
537 # Bail with a nice error if we can't find the hook.
538 if not os.path.isfile(self._script_fullpath):
539 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
540
541 # Make sure the user is OK with running the hook.
542 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
543 return
544
545 # Run the hook with the same version of python we're using.
546 self._ExecuteHook(**kwargs)
547
548
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700549class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600550 # These objects can be shared between several working trees.
551 shareable_files = ['description', 'info']
natalie.chene8996f92015-12-29 10:53:30 +0800552 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn', 'lfs']
Kevin Degi384b3c52014-10-16 16:02:58 -0600553 # 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,
natalie.chene8996f92015-12-29 10:53:30 +0800576 old_revision=None,
577 lfs_fetch=False):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800578 """Init a Project object.
579
580 Args:
581 manifest: The XmlManifest object.
582 name: The `name` attribute of manifest.xml's project element.
583 remote: RemoteSpec object specifying its remote's properties.
584 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700585 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800586 worktree: Absolute path of git working tree.
587 relpath: Relative path of git working tree to repo's top directory.
588 revisionExpr: The `revision` attribute of manifest.xml's project element.
589 revisionId: git commit id for checking out.
590 rebase: The `rebase` attribute of manifest.xml's project element.
591 groups: The `groups` attribute of manifest.xml's project element.
592 sync_c: The `sync-c` attribute of manifest.xml's project element.
593 sync_s: The `sync-s` attribute of manifest.xml's project element.
594 upstream: The `upstream` attribute of manifest.xml's project element.
595 parent: The parent Project object.
596 is_derived: False if the project was explicitly defined in the manifest;
597 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400598 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900599 optimized_fetch: If True, when a project is set to a sha1 revision, only
600 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700601 old_revision: saved git commit id for open GITC projects.
natalie.chene8996f92015-12-29 10:53:30 +0800602 lfs_fetch: git lfs fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800603 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700604 self.manifest = manifest
605 self.name = name
606 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800607 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700608 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800609 if worktree:
610 self.worktree = worktree.replace('\\', '/')
611 else:
612 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700613 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700614 self.revisionExpr = revisionExpr
615
616 if revisionId is None \
617 and revisionExpr \
618 and IsId(revisionExpr):
619 self.revisionId = revisionExpr
620 else:
621 self.revisionId = revisionId
622
Mike Pontillod3153822012-02-28 11:53:24 -0800623 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700624 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700625 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800626 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900627 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700628 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800629 self.parent = parent
630 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900631 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800632 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800633
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700634 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500636 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500637 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700638 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100639 gitdir=self.gitdir,
640 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700641
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800642 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700643 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800644 else:
645 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700646 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700647 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700648 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400649 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700650 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700651
Doug Anderson37282b42011-03-04 11:54:18 -0800652 # This will be filled in if a project is later identified to be the
653 # project containing repo hooks.
654 self.enabled_repo_hooks = []
natalie.chene8996f92015-12-29 10:53:30 +0800655 self.lfs_fetch = lfs_fetch
Doug Anderson37282b42011-03-04 11:54:18 -0800656
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700657 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800658 def Derived(self):
659 return self.is_derived
660
661 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700662 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600663 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700664
665 @property
666 def CurrentBranch(self):
667 """Obtain the name of the currently checked out branch.
668 The branch name omits the 'refs/heads/' prefix.
669 None is returned if the project is on a detached HEAD.
670 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700671 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700672 if b.startswith(R_HEADS):
673 return b[len(R_HEADS):]
674 return None
675
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700676 def IsRebaseInProgress(self):
677 w = self.worktree
678 g = os.path.join(w, '.git')
679 return os.path.exists(os.path.join(g, 'rebase-apply')) \
680 or os.path.exists(os.path.join(g, 'rebase-merge')) \
681 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200682
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700683 def IsDirty(self, consider_untracked=True):
684 """Is the working directory modified in some way?
685 """
686 self.work_git.update_index('-q',
687 '--unmerged',
688 '--ignore-missing',
689 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900690 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700691 return True
692 if self.work_git.DiffZ('diff-files'):
693 return True
694 if consider_untracked and self.work_git.LsOthers():
695 return True
696 return False
697
698 _userident_name = None
699 _userident_email = None
700
701 @property
702 def UserName(self):
703 """Obtain the user's personal name.
704 """
705 if self._userident_name is None:
706 self._LoadUserIdentity()
707 return self._userident_name
708
709 @property
710 def UserEmail(self):
711 """Obtain the user's email address. This is very likely
712 to be their Gerrit login.
713 """
714 if self._userident_email is None:
715 self._LoadUserIdentity()
716 return self._userident_email
717
718 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900719 u = self.bare_git.var('GIT_COMMITTER_IDENT')
720 m = re.compile("^(.*) <([^>]*)> ").match(u)
721 if m:
722 self._userident_name = m.group(1)
723 self._userident_email = m.group(2)
724 else:
725 self._userident_name = ''
726 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700727
728 def GetRemote(self, name):
729 """Get the configuration for a single remote.
730 """
731 return self.config.GetRemote(name)
732
733 def GetBranch(self, name):
734 """Get the configuration for a single branch.
735 """
736 return self.config.GetBranch(name)
737
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700738 def GetBranches(self):
739 """Get all existing local branches.
740 """
741 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900742 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700743 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700744
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530745 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700746 if name.startswith(R_HEADS):
747 name = name[len(R_HEADS):]
748 b = self.GetBranch(name)
749 b.current = name == current
750 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900751 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700752 heads[name] = b
753
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530754 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700755 if name.startswith(R_PUB):
756 name = name[len(R_PUB):]
757 b = heads.get(name)
758 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900759 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700760
761 return heads
762
Colin Cross5acde752012-03-28 20:15:45 -0700763 def MatchesGroups(self, manifest_groups):
764 """Returns true if the manifest groups specified at init should cause
765 this project to be synced.
766 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700767 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700768
Conley Owens971de8e2012-04-16 10:36:08 -0700769 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700770 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700771 manifest_groups: "-group1,group2"
772 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500773
774 The special manifest group "default" will match any project that
775 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700776 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500777 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700778 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500779 if not 'notdefault' in expanded_project_groups:
780 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700781
Conley Owens971de8e2012-04-16 10:36:08 -0700782 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700783 for group in expanded_manifest_groups:
784 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700785 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700786 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700787 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700788
Conley Owens971de8e2012-04-16 10:36:08 -0700789 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700790
791## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700792 def UncommitedFiles(self, get_all=True):
793 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700795 Args:
796 get_all: a boolean, if True - get information about all different
797 uncommitted files. If False - return as soon as any kind of
798 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500799 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700800 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500801 self.work_git.update_index('-q',
802 '--unmerged',
803 '--ignore-missing',
804 '--refresh')
805 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700806 details.append("rebase in progress")
807 if not get_all:
808 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500809
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700810 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
811 if changes:
812 details.extend(changes)
813 if not get_all:
814 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500815
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700816 changes = self.work_git.DiffZ('diff-files').keys()
817 if changes:
818 details.extend(changes)
819 if not get_all:
820 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500821
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700822 changes = self.work_git.LsOthers()
823 if changes:
824 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500825
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700826 return details
827
828 def HasChanges(self):
829 """Returns true if there are uncommitted changes.
830 """
831 if self.UncommitedFiles(get_all=False):
832 return True
833 else:
834 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500835
Terence Haddock4655e812011-03-31 12:33:34 +0200836 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700837 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200838
839 Args:
840 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841 """
842 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200843 if output_redir == None:
844 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700845 print(file=output_redir)
846 print('project %s/' % self.relpath, file=output_redir)
847 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848 return
849
850 self.work_git.update_index('-q',
851 '--unmerged',
852 '--ignore-missing',
853 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700854 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700855 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
856 df = self.work_git.DiffZ('diff-files')
857 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100858 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700859 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700860
861 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200862 if not output_redir == None:
863 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700864 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865
866 branch = self.CurrentBranch
867 if branch is None:
868 out.nobranch('(*** NO BRANCH ***)')
869 else:
870 out.branch('branch %s', branch)
871 out.nl()
872
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700873 if rb:
874 out.important('prior sync failed; rebase still in progress')
875 out.nl()
876
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877 paths = list()
878 paths.extend(di.keys())
879 paths.extend(df.keys())
880 paths.extend(do)
881
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530882 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900883 try:
884 i = di[p]
885 except KeyError:
886 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700887
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900888 try:
889 f = df[p]
890 except KeyError:
891 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200892
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900893 if i:
894 i_status = i.status.upper()
895 else:
896 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700897
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900898 if f:
899 f_status = f.status.lower()
900 else:
901 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902
903 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800904 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905 i.src_path, p, i.level)
906 else:
907 line = ' %s%s\t%s' % (i_status, f_status, p)
908
909 if i and not f:
910 out.added('%s', line)
911 elif (i and f) or (not i and f):
912 out.changed('%s', line)
913 elif not i and not f:
914 out.untracked('%s', line)
915 else:
916 out.write('%s', line)
917 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200918
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700919 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920
pelyad67872d2012-03-28 14:49:58 +0300921 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 """Prints the status of the repository to stdout.
923 """
924 out = DiffColoring(self.config)
925 cmd = ['diff']
926 if out.is_on:
927 cmd.append('--color')
928 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300929 if absolute_paths:
930 cmd.append('--src-prefix=a/%s/' % self.relpath)
931 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932 cmd.append('--')
933 p = GitCommand(self,
934 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100935 capture_stdout=True,
936 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937 has_diff = False
938 for line in p.process.stdout:
939 if not has_diff:
940 out.nl()
941 out.project('project %s/' % self.relpath)
942 out.nl()
943 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700944 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945 p.Wait()
946
947
948## Publish / Upload ##
949
David Pursehouse8a68ff92012-09-24 12:15:13 +0900950 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951 """Was the branch published (uploaded) for code review?
952 If so, returns the SHA-1 hash of the last published
953 state for the branch.
954 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700955 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900956 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700957 try:
958 return self.bare_git.rev_parse(key)
959 except GitError:
960 return None
961 else:
962 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900963 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700964 except KeyError:
965 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966
David Pursehouse8a68ff92012-09-24 12:15:13 +0900967 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968 """Prunes any stale published refs.
969 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900970 if all_refs is None:
971 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 heads = set()
973 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530974 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700975 if name.startswith(R_HEADS):
976 heads.add(name)
977 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900978 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530980 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 n = name[len(R_PUB):]
982 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900983 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700985 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 """List any branches which can be uploaded for review.
987 """
988 heads = {}
989 pubed = {}
990
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530991 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700992 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900993 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700994 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900995 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700996
997 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530998 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900999 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001001 if selected_branch and branch != selected_branch:
1002 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001004 rb = self.GetUploadableBranch(branch)
1005 if rb:
1006 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001007 return ready
1008
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001009 def GetUploadableBranch(self, branch_name):
1010 """Get a single uploadable branch, or None.
1011 """
1012 branch = self.GetBranch(branch_name)
1013 base = branch.LocalMerge
1014 if branch.LocalMerge:
1015 rb = ReviewableBranch(self, branch, base)
1016 if rb.commits:
1017 return rb
1018 return None
1019
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001020 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001021 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001022 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001023 draft=False,
1024 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001025 """Uploads the named branch for code review.
1026 """
1027 if branch is None:
1028 branch = self.CurrentBranch
1029 if branch is None:
1030 raise GitError('not currently on a branch')
1031
1032 branch = self.GetBranch(branch)
1033 if not branch.LocalMerge:
1034 raise GitError('branch %s does not track a remote' % branch.name)
1035 if not branch.remote.review:
1036 raise GitError('remote %s has no review url' % branch.remote.name)
1037
Bryan Jacobsf609f912013-05-06 13:36:24 -04001038 if dest_branch is None:
1039 dest_branch = self.dest_branch
1040 if dest_branch is None:
1041 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 if not dest_branch.startswith(R_HEADS):
1043 dest_branch = R_HEADS + dest_branch
1044
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001045 if not branch.remote.projectname:
1046 branch.remote.projectname = self.name
1047 branch.remote.Save()
1048
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001049 url = branch.remote.ReviewUrl(self.UserEmail)
1050 if url is None:
1051 raise UploadError('review not configured')
1052 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001053
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001054 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001055 rp = ['gerrit receive-pack']
1056 for e in people[0]:
1057 rp.append('--reviewer=%s' % sq(e))
1058 for e in people[1]:
1059 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001060 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001061
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001062 cmd.append(url)
1063
1064 if dest_branch.startswith(R_HEADS):
1065 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001066
1067 upload_type = 'for'
1068 if draft:
1069 upload_type = 'drafts'
1070
1071 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1072 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001073 if auto_topic:
1074 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001075 if not url.startswith('ssh://'):
1076 rp = ['r=%s' % p for p in people[0]] + \
1077 ['cc=%s' % p for p in people[1]]
1078 if rp:
1079 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001080 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001081
Anthony King7bdac712014-07-16 12:56:40 +01001082 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001083 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001084
1085 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1086 self.bare_git.UpdateRef(R_PUB + branch.name,
1087 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001088 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089
1090
1091## Sync ##
natalie.chene8996f92015-12-29 10:53:30 +08001092 def __FetchLfsObjects(self, name, refs):
1093 if 'refs/heads/*' in refs:
1094 refs = []
1095 for ref in GitRefs(self.objdir).all:
1096 if ref.startswith(R_HEADS):
1097 refs.append(ref)
1098 cmd = ['lfs', 'fetch']
1099 cmd.append(name)
1100 cmd.extend(refs)
1101 gitcmd = GitCommand(self, cmd, bare=True)
1102 ret = gitcmd.Wait()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001103
natalie.chen81826a52017-05-26 16:35:48 +08001104 def _CreateLocalMergeBranch(self, branch, revid):
1105 if not branch and not ID_RE.match(revid):
1106 return
1107 ref = os.path.join(self.gitdir, branch)
1108 if os.path.exists(ref): return
natalie.chenf453ba82017-04-21 16:46:39 +08001109 try:
natalie.chen81826a52017-05-26 16:35:48 +08001110 self.work_git.rev_parse(branch)
natalie.chenf453ba82017-04-21 16:46:39 +08001111 except GitError:
natalie.chenf453ba82017-04-21 16:46:39 +08001112 try:
1113 os.makedirs(os.path.dirname(ref))
1114 except OSError:
1115 pass
1116 _lwrite(ref, '%s\n' % revid)
1117
Julien Campergue335f5ef2013-10-16 11:02:35 +02001118 def _ExtractArchive(self, tarpath, path=None):
1119 """Extract the given tar on its current location
1120
1121 Args:
1122 - tarpath: The path to the actual tar file
1123
1124 """
1125 try:
1126 with tarfile.open(tarpath, 'r') as tar:
1127 tar.extractall(path=path)
1128 return True
1129 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001130 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001131 return False
1132
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001133 def Sync_NetworkHalf(self,
1134 quiet=False,
1135 is_new=None,
1136 current_branch_only=False,
Kevin Degiabaa7f32014-11-12 11:27:45 -07001137 force_sync=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001138 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001139 no_tags=False,
David Pursehouseb1553542014-09-04 21:28:09 +09001140 archive=False,
David Pursehouse74cfd272015-10-14 10:50:15 +09001141 optimized_fetch=False,
1142 prune=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001143 """Perform only the network IO portion of the sync process.
1144 Local working directory/branch state is not affected.
1145 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001146 if archive and not isinstance(self, MetaProject):
1147 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001148 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001149 return False
1150
1151 name = self.relpath.replace('\\', '/')
1152 name = name.replace('/', '_')
1153 tarpath = '%s.tar' % name
1154 topdir = self.manifest.topdir
1155
1156 try:
1157 self._FetchArchive(tarpath, cwd=topdir)
1158 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001159 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001160 return False
1161
1162 # From now on, we only need absolute tarpath
1163 tarpath = os.path.join(topdir, tarpath)
1164
1165 if not self._ExtractArchive(tarpath, path=topdir):
1166 return False
1167 try:
1168 os.remove(tarpath)
1169 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001170 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001171 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001172 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001173 if is_new is None:
1174 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001175 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001176 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001177 else:
1178 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001180
1181 if is_new:
1182 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1183 try:
1184 fd = open(alt, 'rb')
1185 try:
1186 alt_dir = fd.readline().rstrip()
1187 finally:
1188 fd.close()
1189 except IOError:
1190 alt_dir = None
1191 else:
1192 alt_dir = None
1193
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001194 if clone_bundle \
1195 and alt_dir is None \
1196 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001197 is_new = False
1198
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001199 if not current_branch_only:
1200 if self.sync_c:
1201 current_branch_only = True
1202 elif not self.manifest._loaded:
1203 # Manifest cannot check defaults until it syncs.
1204 current_branch_only = False
1205 elif self.manifest.default.sync_c:
1206 current_branch_only = True
1207
David Pursehouseb1553542014-09-04 21:28:09 +09001208 need_to_fetch = not (optimized_fetch and \
1209 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1210 if (need_to_fetch
Conley Owens666d5342014-05-01 13:09:57 -07001211 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1212 current_branch_only=current_branch_only,
David Pursehouse74cfd272015-10-14 10:50:15 +09001213 no_tags=no_tags, prune=prune)):
Anthony King7bdac712014-07-16 12:56:40 +01001214 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001215
1216 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001217 self._InitMRef()
1218 else:
1219 self._InitMirrorHead()
1220 try:
1221 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1222 except OSError:
1223 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001225
1226 def PostRepoUpgrade(self):
1227 self._InitHooks()
1228
natalie.chen0c367f52017-05-19 17:01:37 +08001229 def RemoveOldCopyAndLinkFiles(self, path = None):
1230 old_copylink = []
1231 file_path = os.path.join(self.gitdir if not path else path, '.repo_copylink')
1232 if os.path.exists(file_path):
1233 fd = open(file_path, 'r')
1234 try:
1235 old_copylink = fd.read().split('\n')
1236 finally:
1237 fd.close()
natalie.chenc56b94d2017-08-16 09:23:19 +08001238 for dest in old_copylink:
1239 if not dest: continue
1240 target = os.path.join(self.manifest.topdir, dest)
natalie.chen0c367f52017-05-19 17:01:37 +08001241 if os.path.exists(target):
1242 if IsTrace():
1243 Trace('rm %s', target)
1244 os.remove(target)
1245
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001246 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001247 if self.manifest.isGitcClient:
1248 return
natalie.chen0c367f52017-05-19 17:01:37 +08001249 self.RemoveOldCopyAndLinkFiles()
1250 new_copylink = []
David Pursehouse8a68ff92012-09-24 12:15:13 +09001251 for copyfile in self.copyfiles:
1252 copyfile._Copy()
natalie.chenc56b94d2017-08-16 09:23:19 +08001253 new_copylink.append(copyfile.dest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001254 for linkfile in self.linkfiles:
1255 linkfile._Link()
natalie.chenc56b94d2017-08-16 09:23:19 +08001256 new_copylink.append(linkfile.dest)
natalie.chen0c367f52017-05-19 17:01:37 +08001257 file_path = os.path.join(self.gitdir, '.repo_copylink')
1258 fd = open(file_path, 'w')
1259 try:
1260 fd.write('\n'.join(new_copylink))
1261 fd.write('\n')
1262 finally:
1263 fd.close()
1264
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001265
Julien Camperguedd654222014-01-09 16:21:37 +01001266 def GetCommitRevisionId(self):
1267 """Get revisionId of a commit.
1268
1269 Use this method instead of GetRevisionId to get the id of the commit rather
1270 than the id of the current git object (for example, a tag)
1271
1272 """
1273 if not self.revisionExpr.startswith(R_TAGS):
1274 return self.GetRevisionId(self._allrefs)
1275
1276 try:
1277 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1278 except GitError:
1279 raise ManifestInvalidRevisionError(
1280 'revision %s in %s not found' % (self.revisionExpr,
1281 self.name))
1282
David Pursehouse8a68ff92012-09-24 12:15:13 +09001283 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001284 if self.revisionId:
1285 return self.revisionId
1286
1287 rem = self.GetRemote(self.remote.name)
1288 rev = rem.ToLocal(self.revisionExpr)
1289
David Pursehouse8a68ff92012-09-24 12:15:13 +09001290 if all_refs is not None and rev in all_refs:
1291 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001292
1293 try:
1294 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1295 except GitError:
1296 raise ManifestInvalidRevisionError(
1297 'revision %s in %s not found' % (self.revisionExpr,
1298 self.name))
1299
Kevin Degiabaa7f32014-11-12 11:27:45 -07001300 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 """Perform only the local IO portion of the sync process.
1302 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001304 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001305 all_refs = self.bare_ref.all
1306 self.CleanPublishedCache(all_refs)
1307 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001308
David Pursehouse1d947b32012-10-25 12:23:11 +09001309 def _doff():
1310 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001311 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001312
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001313 head = self.work_git.GetHead()
1314 if head.startswith(R_HEADS):
1315 branch = head[len(R_HEADS):]
1316 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001317 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001318 except KeyError:
1319 head = None
1320 else:
1321 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001323 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 # Currently on a detached HEAD. The user is assumed to
1325 # not have any local modifications worth worrying about.
1326 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001327 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001328 syncbuf.fail(self, _PriorSyncFailedError())
1329 return
1330
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001331 if head == revid:
1332 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001333 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001334 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001335 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001336 # The copy/linkfile config may have changed.
1337 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001338 return
1339 else:
1340 lost = self._revlist(not_rev(revid), HEAD)
1341 if lost:
1342 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001343
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001345 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001346 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001347 syncbuf.fail(self, e)
1348 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001349 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001350 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001351
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001352 if head == revid:
1353 # No changes; don't do anything further.
1354 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001355 # The copy/linkfile config may have changed.
1356 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001357 return
1358
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001360
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001361 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001362 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001363 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001365 syncbuf.info(self,
1366 "leaving %s; does not track upstream",
1367 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001369 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001370 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001371 syncbuf.fail(self, e)
1372 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001373 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001374 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001375
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001376 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001377 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001378 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001379 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001380 if not_merged:
1381 if upstream_gain:
1382 # The user has published this branch and some of those
1383 # commits are not yet merged upstream. We do not want
1384 # to rewrite the published commits so we punt.
1385 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001386 syncbuf.fail(self,
1387 "branch %s is published (but not merged) and is now %d commits behind"
1388 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001389 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001390 elif pub == head:
1391 # All published commits are merged, and thus we are a
1392 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001393 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001394 syncbuf.later1(self, _doff)
1395 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001396
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001397 # Examine the local commits not in the remote. Find the
1398 # last one attributed to this user, if any.
1399 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001400 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001401 last_mine = None
1402 cnt_mine = 0
1403 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301404 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001405 if committer_email == self.UserEmail:
1406 last_mine = commit_id
1407 cnt_mine += 1
1408
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001409 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001410 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001411
1412 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001413 syncbuf.fail(self, _DirtyError())
1414 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001415
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001416 # If the upstream switched on us, warn the user.
1417 #
1418 if branch.merge != self.revisionExpr:
1419 if branch.merge and self.revisionExpr:
1420 syncbuf.info(self,
1421 'manifest switched %s...%s',
1422 branch.merge,
1423 self.revisionExpr)
1424 elif branch.merge:
1425 syncbuf.info(self,
1426 'manifest no longer tracks %s',
1427 branch.merge)
1428
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001429 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001430 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001431 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001432 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001433 syncbuf.info(self,
1434 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001435 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001436
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001437 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001438 if not ID_RE.match(self.revisionExpr):
1439 # in case of manifest sync the revisionExpr might be a SHA1
1440 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001441 if not branch.merge.startswith('refs/'):
1442 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001443 branch.Save()
1444
Mike Pontillod3153822012-02-28 11:53:24 -08001445 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001446 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001447 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001448 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001449 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001450 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001451 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001452 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001453 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001454 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001455 syncbuf.fail(self, e)
1456 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001457 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001458 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001459
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001460 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001461 # dest should already be an absolute path, but src is project relative
1462 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001463 abssrc = os.path.join(self.worktree, src)
1464 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001465
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001466 def AddLinkFile(self, src, dest, absdest):
1467 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001468 # make src relative path to dest
1469 absdestdir = os.path.dirname(absdest)
1470 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001471 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001472
James W. Mills24c13082012-04-12 15:04:13 -05001473 def AddAnnotation(self, name, value, keep):
1474 self.annotations.append(_Annotation(name, value, keep))
1475
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001476 def DownloadPatchSet(self, change_id, patch_id):
1477 """Download a single patch set of a single change to FETCH_HEAD.
1478 """
1479 remote = self.GetRemote(self.remote.name)
1480
1481 cmd = ['fetch', remote.name]
1482 cmd.append('refs/changes/%2.2d/%d/%d' \
1483 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001484 if GitCommand(self, cmd, bare=True).Wait() != 0:
1485 return None
1486 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001487 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001488 change_id,
1489 patch_id,
1490 self.bare_git.rev_parse('FETCH_HEAD'))
1491
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001492
1493## Branch Management ##
1494
Simran Basib9a1b732015-08-20 12:19:28 -07001495 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001496 """Create a new branch off the manifest's revision.
1497 """
Simran Basib9a1b732015-08-20 12:19:28 -07001498 if not branch_merge:
1499 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001500 head = self.work_git.GetHead()
1501 if head == (R_HEADS + name):
1502 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001503
David Pursehouse8a68ff92012-09-24 12:15:13 +09001504 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001505 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001506 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001507 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001508 capture_stdout=True,
1509 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001510
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001511 branch = self.GetBranch(name)
1512 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001513 branch.merge = branch_merge
1514 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1515 branch.merge = R_HEADS + branch_merge
natalie.chen81826a52017-05-26 16:35:48 +08001516 '''make sure local merge branch exists'''
1517 self._CreateLocalMergeBranch(branch.LocalMerge, self.revisionExpr)
1518
David Pursehouse8a68ff92012-09-24 12:15:13 +09001519 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001520
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001521 if head.startswith(R_HEADS):
1522 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001523 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001524 except KeyError:
1525 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001526 if revid and head and revid == head:
1527 ref = os.path.join(self.gitdir, R_HEADS + name)
1528 try:
1529 os.makedirs(os.path.dirname(ref))
1530 except OSError:
1531 pass
1532 _lwrite(ref, '%s\n' % revid)
1533 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1534 'ref: %s%s\n' % (R_HEADS, name))
1535 branch.Save()
1536 return True
1537
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001538 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001539 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001540 capture_stdout=True,
1541 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001542 branch.Save()
1543 return True
1544 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001545
Wink Saville02d79452009-04-10 13:01:24 -07001546 def CheckoutBranch(self, name):
1547 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001548
1549 Args:
1550 name: The name of the branch to checkout.
1551
1552 Returns:
1553 True if the checkout succeeded; False if it didn't; None if the branch
1554 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001555 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001556 rev = R_HEADS + name
1557 head = self.work_git.GetHead()
1558 if head == rev:
1559 # Already on the branch
1560 #
1561 return True
Wink Saville02d79452009-04-10 13:01:24 -07001562
David Pursehouse8a68ff92012-09-24 12:15:13 +09001563 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001564 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001565 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001566 except KeyError:
1567 # Branch does not exist in this project
1568 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001569 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001570
1571 if head.startswith(R_HEADS):
1572 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001573 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001574 except KeyError:
1575 head = None
1576
1577 if head == revid:
1578 # Same revision; just update HEAD to point to the new
1579 # target branch, but otherwise take no other action.
1580 #
1581 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1582 'ref: %s%s\n' % (R_HEADS, name))
1583 return True
Wink Saville02d79452009-04-10 13:01:24 -07001584
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001585 return GitCommand(self,
1586 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001587 capture_stdout=True,
1588 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001589
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001590 def AbandonBranch(self, name):
1591 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001592
1593 Args:
1594 name: The name of the branch to abandon.
1595
1596 Returns:
1597 True if the abandon succeeded; False if it didn't; None if the branch
1598 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001599 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001600 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001601 all_refs = self.bare_ref.all
1602 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001603 # Doesn't exist
1604 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001605
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001606 head = self.work_git.GetHead()
1607 if head == rev:
1608 # We can't destroy the branch while we are sitting
1609 # on it. Switch to a detached HEAD.
1610 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001611 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001612
David Pursehouse8a68ff92012-09-24 12:15:13 +09001613 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001614 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001615 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1616 '%s\n' % revid)
1617 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001618 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001619
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001620 return GitCommand(self,
1621 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001622 capture_stdout=True,
1623 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001624
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001625 def PruneHeads(self):
1626 """Prune any topic branches already merged into upstream.
1627 """
1628 cb = self.CurrentBranch
1629 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001630 left = self._allrefs
1631 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001632 if name.startswith(R_HEADS):
1633 name = name[len(R_HEADS):]
1634 if cb is None or name != cb:
1635 kill.append(name)
1636
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001637 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001638 if cb is not None \
1639 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001640 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001641 self.work_git.DetachHead(HEAD)
1642 kill.append(cb)
1643
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001644 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001645 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001646
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001647 try:
1648 self.bare_git.DetachHead(rev)
1649
1650 b = ['branch', '-d']
1651 b.extend(kill)
1652 b = GitCommand(self, b, bare=True,
1653 capture_stdout=True,
1654 capture_stderr=True)
1655 b.Wait()
1656 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001657 if ID_RE.match(old):
1658 self.bare_git.DetachHead(old)
1659 else:
1660 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001661 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001662
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001663 for branch in kill:
1664 if (R_HEADS + branch) not in left:
1665 self.CleanPublishedCache()
1666 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001667
1668 if cb and cb not in kill:
1669 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001670 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001671
1672 kept = []
1673 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001674 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001675 branch = self.GetBranch(branch)
1676 base = branch.LocalMerge
1677 if not base:
1678 base = rev
1679 kept.append(ReviewableBranch(self, branch, base))
1680 return kept
1681
1682
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001683## Submodule Management ##
1684
1685 def GetRegisteredSubprojects(self):
1686 result = []
1687 def rec(subprojects):
1688 if not subprojects:
1689 return
1690 result.extend(subprojects)
1691 for p in subprojects:
1692 rec(p.subprojects)
1693 rec(self.subprojects)
1694 return result
1695
1696 def _GetSubmodules(self):
1697 # Unfortunately we cannot call `git submodule status --recursive` here
1698 # because the working tree might not exist yet, and it cannot be used
1699 # without a working tree in its current implementation.
1700
1701 def get_submodules(gitdir, rev):
1702 # Parse .gitmodules for submodule sub_paths and sub_urls
1703 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1704 if not sub_paths:
1705 return []
1706 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1707 # revision of submodule repository
1708 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1709 submodules = []
1710 for sub_path, sub_url in zip(sub_paths, sub_urls):
1711 try:
1712 sub_rev = sub_revs[sub_path]
1713 except KeyError:
1714 # Ignore non-exist submodules
1715 continue
1716 submodules.append((sub_rev, sub_path, sub_url))
1717 return submodules
1718
1719 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1720 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1721 def parse_gitmodules(gitdir, rev):
1722 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1723 try:
Anthony King7bdac712014-07-16 12:56:40 +01001724 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1725 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001726 except GitError:
1727 return [], []
1728 if p.Wait() != 0:
1729 return [], []
1730
1731 gitmodules_lines = []
1732 fd, temp_gitmodules_path = tempfile.mkstemp()
1733 try:
1734 os.write(fd, p.stdout)
1735 os.close(fd)
1736 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001737 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1738 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001739 if p.Wait() != 0:
1740 return [], []
1741 gitmodules_lines = p.stdout.split('\n')
1742 except GitError:
1743 return [], []
1744 finally:
1745 os.remove(temp_gitmodules_path)
1746
1747 names = set()
1748 paths = {}
1749 urls = {}
1750 for line in gitmodules_lines:
1751 if not line:
1752 continue
1753 m = re_path.match(line)
1754 if m:
1755 names.add(m.group(1))
1756 paths[m.group(1)] = m.group(2)
1757 continue
1758 m = re_url.match(line)
1759 if m:
1760 names.add(m.group(1))
1761 urls[m.group(1)] = m.group(2)
1762 continue
1763 names = sorted(names)
1764 return ([paths.get(name, '') for name in names],
1765 [urls.get(name, '') for name in names])
1766
1767 def git_ls_tree(gitdir, rev, paths):
1768 cmd = ['ls-tree', rev, '--']
1769 cmd.extend(paths)
1770 try:
Anthony King7bdac712014-07-16 12:56:40 +01001771 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1772 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001773 except GitError:
1774 return []
1775 if p.Wait() != 0:
1776 return []
1777 objects = {}
1778 for line in p.stdout.split('\n'):
1779 if not line.strip():
1780 continue
1781 object_rev, object_path = line.split()[2:4]
1782 objects[object_path] = object_rev
1783 return objects
1784
1785 try:
1786 rev = self.GetRevisionId()
1787 except GitError:
1788 return []
1789 return get_submodules(self.gitdir, rev)
1790
1791 def GetDerivedSubprojects(self):
1792 result = []
1793 if not self.Exists:
1794 # If git repo does not exist yet, querying its submodules will
1795 # mess up its states; so return here.
1796 return result
1797 for rev, path, url in self._GetSubmodules():
1798 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001799 relpath, worktree, gitdir, objdir = \
1800 self.manifest.GetSubprojectPaths(self, name, path)
1801 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001802 if project:
1803 result.extend(project.GetDerivedSubprojects())
1804 continue
David James8d201162013-10-11 17:03:19 -07001805
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001806 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001807 url=url,
1808 review=self.remote.review,
1809 revision=self.remote.revision)
1810 subproject = Project(manifest=self.manifest,
1811 name=name,
1812 remote=remote,
1813 gitdir=gitdir,
1814 objdir=objdir,
1815 worktree=worktree,
1816 relpath=relpath,
1817 revisionExpr=self.revisionExpr,
1818 revisionId=rev,
1819 rebase=self.rebase,
1820 groups=self.groups,
1821 sync_c=self.sync_c,
1822 sync_s=self.sync_s,
1823 parent=self,
1824 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001825 result.append(subproject)
1826 result.extend(subproject.GetDerivedSubprojects())
1827 return result
1828
1829
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001830## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001831 def _CheckForSha1(self):
1832 try:
1833 # if revision (sha or tag) is not present then following function
1834 # throws an error.
1835 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1836 return True
1837 except GitError:
1838 # There is no such persistent revision. We have to fetch it.
1839 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001840
Julien Campergue335f5ef2013-10-16 11:02:35 +02001841 def _FetchArchive(self, tarpath, cwd=None):
1842 cmd = ['archive', '-v', '-o', tarpath]
1843 cmd.append('--remote=%s' % self.remote.url)
1844 cmd.append('--prefix=%s/' % self.relpath)
1845 cmd.append(self.revisionExpr)
1846
1847 command = GitCommand(self, cmd, cwd=cwd,
1848 capture_stdout=True,
1849 capture_stderr=True)
1850
1851 if command.Wait() != 0:
1852 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1853
Conley Owens80b87fe2014-05-09 17:13:44 -07001854
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001855 def _RemoteFetch(self, name=None,
1856 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001857 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001858 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001859 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001860 no_tags=False,
1861 prune=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001862
1863 is_sha1 = False
1864 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001865 depth = None
1866
1867 # The depth should not be used when fetching to a mirror because
1868 # it will result in a shallow repository that cannot be cloned or
1869 # fetched from.
1870 if not self.manifest.IsMirror:
1871 if self.clone_depth:
1872 depth = self.clone_depth
1873 else:
1874 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001875 # The repo project should never be synced with partial depth
1876 if self.relpath == '.repo/repo':
1877 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001878
Shawn Pearce69e04d82014-01-29 12:48:54 -08001879 if depth:
1880 current_branch_only = True
1881
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001882 if ID_RE.match(self.revisionExpr) is not None:
1883 is_sha1 = True
1884
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001885 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001886 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001887 # this is a tag and its sha1 value should never change
1888 tag_name = self.revisionExpr[len(R_TAGS):]
1889
1890 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001891 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001892 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001893 if is_sha1 and not depth:
1894 # When syncing a specific commit and --depth is not set:
1895 # * if upstream is explicitly specified and is not a sha1, fetch only
1896 # upstream as users expect only upstream to be fetch.
1897 # Note: The commit might not be in upstream in which case the sync
1898 # will fail.
1899 # * otherwise, fetch all branches to make sure we end up with the
1900 # specific commit.
1901 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001902
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001903 if not name:
1904 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001905
1906 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001907 remote = self.GetRemote(name)
1908 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001909 ssh_proxy = True
1910
Shawn O. Pearce88443382010-10-08 10:02:09 +02001911 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001912 if alt_dir and 'objects' == os.path.basename(alt_dir):
1913 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001914 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1915 remote = self.GetRemote(name)
1916
David Pursehouse8a68ff92012-09-24 12:15:13 +09001917 all_refs = self.bare_ref.all
1918 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001919 tmp = set()
1920
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301921 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001922 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001923 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001924 all_refs[r] = ref_id
1925 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001926 continue
1927
David Pursehouse8a68ff92012-09-24 12:15:13 +09001928 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001929 continue
1930
David Pursehouse8a68ff92012-09-24 12:15:13 +09001931 r = 'refs/_alt/%s' % ref_id
1932 all_refs[r] = ref_id
1933 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001934 tmp.add(r)
1935
Shawn O. Pearce88443382010-10-08 10:02:09 +02001936 tmp_packed = ''
1937 old_packed = ''
1938
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301939 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001940 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001941 tmp_packed += line
1942 if r not in tmp:
1943 old_packed += line
1944
1945 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001946 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001947 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001948
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001949 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001950
Conley Owensf97e8382015-01-21 11:12:46 -08001951 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001952 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07001953 else:
1954 # If this repo has shallow objects, then we don't know which refs have
1955 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
1956 # do this with projects that don't have shallow objects, since it is less
1957 # efficient.
1958 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
1959 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07001960
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001961 if quiet:
1962 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001963 if not self.worktree:
1964 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001965 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001966
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001967 # If using depth then we should not get all the tags since they may
1968 # be outside of the depth.
1969 if no_tags or depth:
1970 cmd.append('--no-tags')
1971 else:
1972 cmd.append('--tags')
1973
natalie.chene8996f92015-12-29 10:53:30 +08001974 refs = []
David Pursehouse74cfd272015-10-14 10:50:15 +09001975 if prune:
1976 cmd.append('--prune')
1977
Conley Owens80b87fe2014-05-09 17:13:44 -07001978 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001979 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001980 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001981 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
natalie.chene8996f92015-12-29 10:53:30 +08001982 if self.manifest.IsMirror: refs.append('refs/heads/*')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001983 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001984 spec.append('tag')
1985 spec.append(tag_name)
natalie.chene8996f92015-12-29 10:53:30 +08001986 refs.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001987
David Pursehouse403b64e2015-04-27 10:41:33 +09001988 if not self.manifest.IsMirror:
1989 branch = self.revisionExpr
natalie.chen0546a1b2016-04-09 12:17:37 +08001990 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001991 # Shallow checkout of a specific commit, fetch from that commit and not
1992 # the heads only as the commit might be deeper in the history.
natalie.chene8996f92015-12-29 10:53:30 +08001993 spec.append(branch)
David Pursehouse403b64e2015-04-27 10:41:33 +09001994 else:
1995 if is_sha1:
1996 branch = self.upstream
1997 if branch is not None and branch.strip():
1998 if not branch.startswith('refs/'):
1999 branch = R_HEADS + branch
2000 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
natalie.chene8996f92015-12-29 10:53:30 +08002001 refs.append(remote.ToLocal(branch))
Conley Owens80b87fe2014-05-09 17:13:44 -07002002 cmd.extend(spec)
2003
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002004 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002005 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002006 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002007 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002008 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002009 ok = True
2010 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002011 # If needed, run the 'git remote prune' the first time through the loop
2012 elif (not _i and
2013 "error:" in gitcmd.stderr and
2014 "git remote prune" in gitcmd.stderr):
2015 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002016 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002017 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002018 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002019 break
2020 continue
Brian Harring14a66742012-09-28 20:21:57 -07002021 elif current_branch_only and is_sha1 and ret == 128:
2022 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
2023 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
2024 # abort the optimization attempt and do a full sync.
2025 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002026 elif ret < 0:
2027 # Git died with a signal, exit immediately
2028 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002029 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002030
2031 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002032 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002033 if old_packed != '':
2034 _lwrite(packed_refs, old_packed)
2035 else:
2036 os.remove(packed_refs)
2037 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002038
2039 if is_sha1 and current_branch_only and self.upstream:
2040 # We just synced the upstream given branch; verify we
2041 # got what we wanted, else trigger a second run of all
2042 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002043 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06002044 if not depth:
2045 # Avoid infinite recursion when depth is True (since depth implies
2046 # current_branch_only)
2047 return self._RemoteFetch(name=name, current_branch_only=False,
2048 initial=False, quiet=quiet, alt_dir=alt_dir)
2049 if self.clone_depth:
2050 self.clone_depth = None
2051 return self._RemoteFetch(name=name, current_branch_only=current_branch_only,
2052 initial=False, quiet=quiet, alt_dir=alt_dir)
natalie.chene8996f92015-12-29 10:53:30 +08002053 if self.lfs_fetch:
2054 self.__FetchLfsObjects(name, refs)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002055 return ok
2056
2057 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09002058 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002059 return False
2060
2061 remote = self.GetRemote(self.remote.name)
2062 bundle_url = remote.url + '/clone.bundle'
2063 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002064 if GetSchemeFromUrl(bundle_url) not in (
2065 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002066 return False
2067
2068 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2069 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002070
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002071 exist_dst = os.path.exists(bundle_dst)
2072 exist_tmp = os.path.exists(bundle_tmp)
2073
2074 if not initial and not exist_dst and not exist_tmp:
2075 return False
2076
2077 if not exist_dst:
2078 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2079 if not exist_dst:
2080 return False
2081
2082 cmd = ['fetch']
2083 if quiet:
2084 cmd.append('--quiet')
2085 if not self.worktree:
2086 cmd.append('--update-head-ok')
2087 cmd.append(bundle_dst)
2088 for f in remote.fetch:
2089 cmd.append(str(f))
2090 cmd.append('refs/tags/*:refs/tags/*')
2091
2092 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002093 if os.path.exists(bundle_dst):
2094 os.remove(bundle_dst)
2095 if os.path.exists(bundle_tmp):
2096 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002097 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002098
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002099 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002100 if os.path.exists(dstPath):
2101 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07002102
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002103 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002104 if quiet:
2105 cmd += ['--silent']
2106 if os.path.exists(tmpPath):
2107 size = os.stat(tmpPath).st_size
2108 if size >= 1024:
2109 cmd += ['--continue-at', '%d' % (size,)]
2110 else:
2111 os.remove(tmpPath)
2112 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2113 cmd += ['--proxy', os.environ['http_proxy']]
Dan Willemsen0745bb22015-08-17 13:41:45 -07002114 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002115 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002116 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002117 if srcUrl.startswith('persistent-'):
2118 srcUrl = srcUrl[len('persistent-'):]
2119 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002120
Dave Borowitz137d0132015-01-02 11:12:54 -08002121 if IsTrace():
2122 Trace('%s', ' '.join(cmd))
2123 try:
2124 proc = subprocess.Popen(cmd)
2125 except OSError:
2126 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002127
Dave Borowitz137d0132015-01-02 11:12:54 -08002128 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002129
Dave Borowitz137d0132015-01-02 11:12:54 -08002130 if curlret == 22:
2131 # From curl man page:
2132 # 22: HTTP page not retrieved. The requested url was not found or
2133 # returned another error with the HTTP error code being 400 or above.
2134 # This return code only appears if -f, --fail is used.
2135 if not quiet:
2136 print("Server does not provide clone.bundle; ignoring.",
2137 file=sys.stderr)
2138 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002139
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002140 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002141 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002142 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002143 return True
2144 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002145 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002146 return False
2147 else:
2148 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002149
Kris Giesingc8d882a2014-12-23 13:02:32 -08002150 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002151 try:
2152 with open(path) as f:
2153 if f.read(16) == '# v2 git bundle\n':
2154 return True
2155 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002156 if not quiet:
2157 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002158 return False
2159 except OSError:
2160 return False
2161
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002162 def _Checkout(self, rev, quiet=False):
2163 cmd = ['checkout']
2164 if quiet:
2165 cmd.append('-q')
2166 cmd.append(rev)
2167 cmd.append('--')
2168 if GitCommand(self, cmd).Wait() != 0:
2169 if self._allrefs:
2170 raise GitError('%s checkout %s ' % (self.name, rev))
2171
Anthony King7bdac712014-07-16 12:56:40 +01002172 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002173 cmd = ['cherry-pick']
2174 cmd.append(rev)
2175 cmd.append('--')
2176 if GitCommand(self, cmd).Wait() != 0:
2177 if self._allrefs:
2178 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2179
Anthony King7bdac712014-07-16 12:56:40 +01002180 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002181 cmd = ['revert']
2182 cmd.append('--no-edit')
2183 cmd.append(rev)
2184 cmd.append('--')
2185 if GitCommand(self, cmd).Wait() != 0:
2186 if self._allrefs:
2187 raise GitError('%s revert %s ' % (self.name, rev))
2188
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002189 def _ResetHard(self, rev, quiet=True):
2190 cmd = ['reset', '--hard']
2191 if quiet:
2192 cmd.append('-q')
2193 cmd.append(rev)
2194 if GitCommand(self, cmd).Wait() != 0:
2195 raise GitError('%s reset --hard %s ' % (self.name, rev))
2196
Anthony King7bdac712014-07-16 12:56:40 +01002197 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002198 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002199 if onto is not None:
2200 cmd.extend(['--onto', onto])
2201 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002202 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002203 raise GitError('%s rebase %s ' % (self.name, upstream))
2204
Pierre Tardy3d125942012-05-04 12:18:12 +02002205 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002206 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002207 if ffonly:
2208 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002209 if GitCommand(self, cmd).Wait() != 0:
2210 raise GitError('%s merge %s ' % (self.name, head))
2211
Kevin Degiabaa7f32014-11-12 11:27:45 -07002212 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002213 init_git_dir = not os.path.exists(self.gitdir)
2214 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002215 try:
2216 # Initialize the bare repository, which contains all of the objects.
2217 if init_obj_dir:
2218 os.makedirs(self.objdir)
2219 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002220
Kevin Degib1a07b82015-07-27 13:33:43 -06002221 # If we have a separate directory to hold refs, initialize it as well.
2222 if self.objdir != self.gitdir:
2223 if init_git_dir:
2224 os.makedirs(self.gitdir)
Kevin Degi384b3c52014-10-16 16:02:58 -06002225
Kevin Degib1a07b82015-07-27 13:33:43 -06002226 if init_obj_dir or init_git_dir:
2227 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2228 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002229 try:
2230 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2231 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002232 if force_sync:
David Pursehouse25857b82015-08-19 18:06:22 +09002233 print("Retrying clone after deleting %s" % self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002234 try:
2235 shutil.rmtree(os.path.realpath(self.gitdir))
2236 if self.worktree and os.path.exists(
2237 os.path.realpath(self.worktree)):
2238 shutil.rmtree(os.path.realpath(self.worktree))
2239 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2240 except:
2241 raise e
2242 raise e
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002243
Kevin Degib1a07b82015-07-27 13:33:43 -06002244 if init_git_dir:
2245 mp = self.manifest.manifestProject
2246 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002247
Kevin Degib1a07b82015-07-27 13:33:43 -06002248 if ref_dir or mirror_git:
2249 if not mirror_git:
2250 mirror_git = os.path.join(ref_dir, self.name + '.git')
2251 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2252 self.relpath + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002253
Kevin Degib1a07b82015-07-27 13:33:43 -06002254 if os.path.exists(mirror_git):
2255 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002256
Kevin Degib1a07b82015-07-27 13:33:43 -06002257 elif os.path.exists(repo_git):
2258 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002259
Kevin Degib1a07b82015-07-27 13:33:43 -06002260 else:
2261 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002262
Kevin Degib1a07b82015-07-27 13:33:43 -06002263 if ref_dir:
2264 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2265 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002266
Kevin Degib1a07b82015-07-27 13:33:43 -06002267 self._UpdateHooks()
Jimmie Westera0444582012-10-24 13:44:42 +02002268
Kevin Degib1a07b82015-07-27 13:33:43 -06002269 m = self.manifest.manifestProject.config
2270 for key in ['user.name', 'user.email']:
2271 if m.Has(key, include_defaults=False):
2272 self.config.SetString(key, m.GetString(key))
2273 if self.manifest.IsMirror:
2274 self.config.SetString('core.bare', 'true')
2275 else:
2276 self.config.SetString('core.bare', None)
natalie.chene8996f92015-12-29 10:53:30 +08002277
natalie.chen4e5600a2016-01-13 11:51:40 +08002278 if self.lfs_fetch:
2279 self.config.SetString('filter.lfs.clean', 'git-lfs clean %f')
2280 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge %f')
2281 self.config.SetString('filter.lfs.required', 'true')
natalie.chene8996f92015-12-29 10:53:30 +08002282
Kevin Degib1a07b82015-07-27 13:33:43 -06002283 except Exception:
2284 if init_obj_dir and os.path.exists(self.objdir):
2285 shutil.rmtree(self.objdir)
2286 if init_git_dir and os.path.exists(self.gitdir):
2287 shutil.rmtree(self.gitdir)
2288 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002289
Jimmie Westera0444582012-10-24 13:44:42 +02002290 def _UpdateHooks(self):
2291 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002292 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002293
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002294 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002295 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002296 if not os.path.exists(hooks):
2297 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002298 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002299 name = os.path.basename(stock_hook)
2300
Victor Boivie65e0f352011-04-18 11:23:29 +02002301 if name in ('commit-msg',) and not self.remote.review \
2302 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002303 # Don't install a Gerrit Code Review hook if this
2304 # project does not appear to use it for reviews.
2305 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002306 # Since the manifest project is one of those, but also
2307 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002308 continue
2309
2310 dst = os.path.join(hooks, name)
2311 if os.path.islink(dst):
2312 continue
2313 if os.path.exists(dst):
2314 if filecmp.cmp(stock_hook, dst, shallow=False):
2315 os.remove(dst)
2316 else:
David Pursehousedc2545c2015-08-24 14:43:45 +09002317 _warn("%s: Not replacing locally modified %s hook", self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002318 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002319 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002320 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002321 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002322 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002323 raise GitError('filesystem must support symlinks')
2324 else:
2325 raise
2326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002327 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002328 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002329 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002330 remote.url = self.remote.url
2331 remote.review = self.remote.review
2332 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002333
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002334 if self.worktree:
2335 remote.ResetFetch(mirror=False)
2336 else:
2337 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002338 remote.Save()
2339
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002340 def _InitMRef(self):
2341 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002342 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002343
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002344 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002345 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002346
2347 def _InitAnyMRef(self, ref):
2348 cur = self.bare_ref.symref(ref)
2349
2350 if self.revisionId:
2351 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2352 msg = 'manifest set to %s' % self.revisionId
2353 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002354 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002355 else:
2356 remote = self.GetRemote(self.remote.name)
2357 dst = remote.ToLocal(self.revisionExpr)
2358 if cur != dst:
2359 msg = 'manifest set to %s' % self.revisionExpr
2360 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002361
Kevin Degi384b3c52014-10-16 16:02:58 -06002362 def _CheckDirReference(self, srcdir, destdir, share_refs):
2363 symlink_files = self.shareable_files
2364 symlink_dirs = self.shareable_dirs
2365 if share_refs:
2366 symlink_files += self.working_tree_files
2367 symlink_dirs += self.working_tree_dirs
2368 to_symlink = symlink_files + symlink_dirs
2369 for name in set(to_symlink):
2370 dst = os.path.realpath(os.path.join(destdir, name))
2371 if os.path.lexists(dst):
2372 src = os.path.realpath(os.path.join(srcdir, name))
2373 # Fail if the links are pointing to the wrong place
2374 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002375 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002376 'work tree. If you\'re comfortable with the '
2377 'possibility of losing the work tree\'s git metadata,'
2378 ' use `repo sync --force-sync {0}` to '
2379 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002380
David James8d201162013-10-11 17:03:19 -07002381 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2382 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2383
2384 Args:
2385 gitdir: The bare git repository. Must already be initialized.
2386 dotgit: The repository you would like to initialize.
2387 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2388 Only one work tree can store refs under a given |gitdir|.
2389 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2390 This saves you the effort of initializing |dotgit| yourself.
2391 """
Kevin Degi384b3c52014-10-16 16:02:58 -06002392 symlink_files = self.shareable_files
2393 symlink_dirs = self.shareable_dirs
David James8d201162013-10-11 17:03:19 -07002394 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002395 symlink_files += self.working_tree_files
2396 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002397 to_symlink = symlink_files + symlink_dirs
2398
2399 to_copy = []
2400 if copy_all:
2401 to_copy = os.listdir(gitdir)
2402
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002403 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002404 for name in set(to_copy).union(to_symlink):
2405 try:
2406 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002407 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002408
Kevin Degi384b3c52014-10-16 16:02:58 -06002409 if os.path.lexists(dst):
2410 continue
David James8d201162013-10-11 17:03:19 -07002411
2412 # If the source dir doesn't exist, create an empty dir.
2413 if name in symlink_dirs and not os.path.lexists(src):
2414 os.makedirs(src)
2415
Conley Owens80b87fe2014-05-09 17:13:44 -07002416 # If the source file doesn't exist, ensure the destination
2417 # file doesn't either.
2418 if name in symlink_files and not os.path.lexists(src):
2419 try:
2420 os.remove(dst)
2421 except OSError:
2422 pass
2423
David James8d201162013-10-11 17:03:19 -07002424 if name in to_symlink:
2425 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2426 elif copy_all and not os.path.islink(dst):
2427 if os.path.isdir(src):
2428 shutil.copytree(src, dst)
2429 elif os.path.isfile(src):
2430 shutil.copy(src, dst)
2431 except OSError as e:
2432 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002433 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002434 else:
2435 raise
2436
Kevin Degiabaa7f32014-11-12 11:27:45 -07002437 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002438 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002439 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002440 try:
2441 if init_dotgit:
2442 os.makedirs(dotgit)
2443 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2444 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002445
Kevin Degiabaa7f32014-11-12 11:27:45 -07002446 try:
2447 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2448 except GitError as e:
2449 if force_sync:
2450 try:
2451 shutil.rmtree(dotgit)
2452 return self._InitWorkTree(force_sync=False)
2453 except:
2454 raise e
2455 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002456
Kevin Degib1a07b82015-07-27 13:33:43 -06002457 if init_dotgit:
2458 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002459
Kevin Degib1a07b82015-07-27 13:33:43 -06002460 cmd = ['read-tree', '--reset', '-u']
2461 cmd.append('-v')
2462 cmd.append(HEAD)
2463 if GitCommand(self, cmd).Wait() != 0:
2464 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002465
Kevin Degib1a07b82015-07-27 13:33:43 -06002466 self._CopyAndLinkFiles()
2467 except Exception:
2468 if init_dotgit:
2469 shutil.rmtree(dotgit)
2470 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002471
2472 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002473 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002474
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002475 def _revlist(self, *args, **kw):
2476 a = []
2477 a.extend(args)
2478 a.append('--')
2479 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002480
2481 @property
2482 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002483 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002484
Julien Camperguedd654222014-01-09 16:21:37 +01002485 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2486 """Get logs between two revisions of this project."""
2487 comp = '..'
2488 if rev1:
2489 revs = [rev1]
2490 if rev2:
2491 revs.extend([comp, rev2])
2492 cmd = ['log', ''.join(revs)]
2493 out = DiffColoring(self.config)
2494 if out.is_on and color:
2495 cmd.append('--color')
2496 if oneline:
2497 cmd.append('--oneline')
2498
2499 try:
2500 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2501 if log.Wait() == 0:
2502 return log.stdout
2503 except GitError:
2504 # worktree may not exist if groups changed for example. In that case,
2505 # try in gitdir instead.
2506 if not os.path.exists(self.worktree):
2507 return self.bare_git.log(*cmd[1:])
2508 else:
2509 raise
2510 return None
2511
2512 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2513 """Get the list of logs from this revision to given revisionId"""
2514 logs = {}
2515 selfId = self.GetRevisionId(self._allrefs)
2516 toId = toProject.GetRevisionId(toProject._allrefs)
2517
2518 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2519 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2520 return logs
2521
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002522 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002523 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002524 self._project = project
2525 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002526 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002527
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002528 def LsOthers(self):
2529 p = GitCommand(self._project,
2530 ['ls-files',
2531 '-z',
2532 '--others',
2533 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002534 bare=False,
David James8d201162013-10-11 17:03:19 -07002535 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002536 capture_stdout=True,
2537 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002538 if p.Wait() == 0:
2539 out = p.stdout
2540 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002541 return out[:-1].split('\0') # pylint: disable=W1401
2542 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002543 return []
2544
2545 def DiffZ(self, name, *args):
2546 cmd = [name]
2547 cmd.append('-z')
2548 cmd.extend(args)
2549 p = GitCommand(self._project,
2550 cmd,
David James8d201162013-10-11 17:03:19 -07002551 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002552 bare=False,
2553 capture_stdout=True,
2554 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002555 try:
2556 out = p.process.stdout.read()
2557 r = {}
2558 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002559 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002560 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002561 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002562 info = next(out)
2563 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002564 except StopIteration:
2565 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002566
2567 class _Info(object):
2568 def __init__(self, path, omode, nmode, oid, nid, state):
2569 self.path = path
2570 self.src_path = None
2571 self.old_mode = omode
2572 self.new_mode = nmode
2573 self.old_id = oid
2574 self.new_id = nid
2575
2576 if len(state) == 1:
2577 self.status = state
2578 self.level = None
2579 else:
2580 self.status = state[:1]
2581 self.level = state[1:]
2582 while self.level.startswith('0'):
2583 self.level = self.level[1:]
2584
2585 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002586 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002587 if info.status in ('R', 'C'):
2588 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002589 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002590 r[info.path] = info
2591 return r
2592 finally:
2593 p.Wait()
2594
2595 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002596 if self._bare:
2597 path = os.path.join(self._project.gitdir, HEAD)
2598 else:
2599 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002600 try:
2601 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002602 except IOError as e:
2603 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002604 try:
2605 line = fd.read()
2606 finally:
2607 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302608 try:
2609 line = line.decode()
2610 except AttributeError:
2611 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002612 if line.startswith('ref: '):
2613 return line[5:-1]
2614 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002615
2616 def SetHead(self, ref, message=None):
2617 cmdv = []
2618 if message is not None:
2619 cmdv.extend(['-m', message])
2620 cmdv.append(HEAD)
2621 cmdv.append(ref)
2622 self.symbolic_ref(*cmdv)
2623
2624 def DetachHead(self, new, message=None):
2625 cmdv = ['--no-deref']
2626 if message is not None:
2627 cmdv.extend(['-m', message])
2628 cmdv.append(HEAD)
2629 cmdv.append(new)
2630 self.update_ref(*cmdv)
2631
2632 def UpdateRef(self, name, new, old=None,
2633 message=None,
2634 detach=False):
2635 cmdv = []
2636 if message is not None:
2637 cmdv.extend(['-m', message])
2638 if detach:
2639 cmdv.append('--no-deref')
2640 cmdv.append(name)
2641 cmdv.append(new)
2642 if old is not None:
2643 cmdv.append(old)
2644 self.update_ref(*cmdv)
2645
2646 def DeleteRef(self, name, old=None):
2647 if not old:
2648 old = self.rev_parse(name)
2649 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002650 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002651
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002652 def rev_list(self, *args, **kw):
2653 if 'format' in kw:
2654 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2655 else:
2656 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002657 cmdv.extend(args)
2658 p = GitCommand(self._project,
2659 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002660 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002661 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002662 capture_stdout=True,
2663 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002664 r = []
2665 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002666 if line[-1] == '\n':
2667 line = line[:-1]
2668 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002669 if p.Wait() != 0:
2670 raise GitError('%s rev-list %s: %s' % (
2671 self._project.name,
2672 str(args),
2673 p.stderr))
2674 return r
2675
2676 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002677 """Allow arbitrary git commands using pythonic syntax.
2678
2679 This allows you to do things like:
2680 git_obj.rev_parse('HEAD')
2681
2682 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2683 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002684 Any other positional arguments will be passed to the git command, and the
2685 following keyword arguments are supported:
2686 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002687
2688 Args:
2689 name: The name of the git command to call. Any '_' characters will
2690 be replaced with '-'.
2691
2692 Returns:
2693 A callable object that will try to call git with the named command.
2694 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002695 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002696 def runner(*args, **kwargs):
2697 cmdv = []
2698 config = kwargs.pop('config', None)
2699 for k in kwargs:
2700 raise TypeError('%s() got an unexpected keyword argument %r'
2701 % (name, k))
2702 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002703 if not git_require((1, 7, 2)):
2704 raise ValueError('cannot set config on command line for %s()'
2705 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302706 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002707 cmdv.append('-c')
2708 cmdv.append('%s=%s' % (k, v))
2709 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002710 cmdv.extend(args)
2711 p = GitCommand(self._project,
2712 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002713 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002714 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002715 capture_stdout=True,
2716 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002717 if p.Wait() != 0:
2718 raise GitError('%s %s: %s' % (
2719 self._project.name,
2720 name,
2721 p.stderr))
2722 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302723 try:
Conley Owensedd01512013-09-26 12:59:58 -07002724 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302725 except AttributeError:
2726 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002727 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2728 return r[:-1]
2729 return r
2730 return runner
2731
2732
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002733class _PriorSyncFailedError(Exception):
2734 def __str__(self):
2735 return 'prior sync failed; rebase still in progress'
2736
2737class _DirtyError(Exception):
2738 def __str__(self):
2739 return 'contains uncommitted changes'
2740
2741class _InfoMessage(object):
2742 def __init__(self, project, text):
2743 self.project = project
2744 self.text = text
2745
2746 def Print(self, syncbuf):
2747 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2748 syncbuf.out.nl()
2749
2750class _Failure(object):
2751 def __init__(self, project, why):
2752 self.project = project
2753 self.why = why
2754
2755 def Print(self, syncbuf):
2756 syncbuf.out.fail('error: %s/: %s',
2757 self.project.relpath,
2758 str(self.why))
2759 syncbuf.out.nl()
2760
2761class _Later(object):
2762 def __init__(self, project, action):
2763 self.project = project
2764 self.action = action
2765
2766 def Run(self, syncbuf):
2767 out = syncbuf.out
2768 out.project('project %s/', self.project.relpath)
2769 out.nl()
2770 try:
2771 self.action()
2772 out.nl()
2773 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002774 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002775 out.nl()
2776 return False
2777
2778class _SyncColoring(Coloring):
2779 def __init__(self, config):
2780 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002781 self.project = self.printer('header', attr='bold')
2782 self.info = self.printer('info')
2783 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002784
2785class SyncBuffer(object):
2786 def __init__(self, config, detach_head=False):
2787 self._messages = []
2788 self._failures = []
2789 self._later_queue1 = []
2790 self._later_queue2 = []
2791
2792 self.out = _SyncColoring(config)
2793 self.out.redirect(sys.stderr)
2794
2795 self.detach_head = detach_head
2796 self.clean = True
2797
2798 def info(self, project, fmt, *args):
2799 self._messages.append(_InfoMessage(project, fmt % args))
2800
2801 def fail(self, project, err=None):
2802 self._failures.append(_Failure(project, err))
2803 self.clean = False
2804
2805 def later1(self, project, what):
2806 self._later_queue1.append(_Later(project, what))
2807
2808 def later2(self, project, what):
2809 self._later_queue2.append(_Later(project, what))
2810
2811 def Finish(self):
2812 self._PrintMessages()
2813 self._RunLater()
2814 self._PrintMessages()
2815 return self.clean
2816
2817 def _RunLater(self):
2818 for q in ['_later_queue1', '_later_queue2']:
2819 if not self._RunQueue(q):
2820 return
2821
2822 def _RunQueue(self, queue):
2823 for m in getattr(self, queue):
2824 if not m.Run(self):
2825 self.clean = False
2826 return False
2827 setattr(self, queue, [])
2828 return True
2829
2830 def _PrintMessages(self):
2831 for m in self._messages:
2832 m.Print(self)
2833 for m in self._failures:
2834 m.Print(self)
2835
2836 self._messages = []
2837 self._failures = []
2838
2839
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002840class MetaProject(Project):
2841 """A special project housed under .repo.
2842 """
2843 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002844 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002845 manifest=manifest,
2846 name=name,
2847 gitdir=gitdir,
2848 objdir=gitdir,
2849 worktree=worktree,
2850 remote=RemoteSpec('origin'),
2851 relpath='.repo/%s' % name,
2852 revisionExpr='refs/heads/master',
2853 revisionId=None,
2854 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002855
2856 def PreSync(self):
2857 if self.Exists:
2858 cb = self.CurrentBranch
2859 if cb:
2860 base = self.GetBranch(cb).merge
2861 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002862 self.revisionExpr = base
2863 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002864
Anthony King7bdac712014-07-16 12:56:40 +01002865 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002866 """ Prepare MetaProject for manifest branch switch
2867 """
2868
2869 # detach and delete manifest branch, allowing a new
2870 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002871 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002872 self.Sync_LocalHalf(syncbuf)
2873 syncbuf.Finish()
2874
2875 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002876 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002877 capture_stdout=True,
2878 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002879
2880
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002881 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002882 def LastFetch(self):
2883 try:
2884 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2885 return os.path.getmtime(fh)
2886 except OSError:
2887 return 0
2888
2889 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002890 def HasChanges(self):
2891 """Has the remote received new commits not yet checked out?
2892 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002893 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002894 return False
2895
David Pursehouse8a68ff92012-09-24 12:15:13 +09002896 all_refs = self.bare_ref.all
2897 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002898 head = self.work_git.GetHead()
2899 if head.startswith(R_HEADS):
2900 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002901 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002902 except KeyError:
2903 head = None
2904
2905 if revid == head:
2906 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002907 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002908 return True
2909 return False