blob: 9cb7542e933f3fc0c6c8b3c679be990ad2c9cf0f [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
Dave Borowitz137d0132015-01-02 11:12:54 -080016import contextlib
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080017import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070019import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070021import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import re
23import shutil
24import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070025import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070026import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020027import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080028import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070029import time
Dave Borowitz137d0132015-01-02 11:12:54 -080030import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070031
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070033from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070034from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070035from error import GitError, HookError, UploadError, DownloadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080036from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080037from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070038from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
Shawn O. Pearced237b692009-04-17 18:49:50 -070040from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
David Pursehouse59bbb582013-05-17 10:49:33 +090042from pyversion import is_python3
43if not is_python3():
44 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053045 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090046 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053047
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070048def _lwrite(path, content):
49 lock = '%s.lock' % path
50
Chirayu Desai303a82f2014-08-19 22:57:17 +053051 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070052 try:
53 fd.write(content)
54 finally:
55 fd.close()
56
57 try:
58 os.rename(lock, path)
59 except OSError:
60 os.remove(lock)
61 raise
62
Shawn O. Pearce48244782009-04-16 08:25:57 -070063def _error(fmt, *args):
64 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070065 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070066
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070067def not_rev(r):
68 return '^' + r
69
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080070def sq(r):
71 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080072
Jonathan Nieder93719792015-03-17 11:29:58 -070073_project_hook_list = None
74def _ProjectHooks():
75 """List the hooks present in the 'hooks' directory.
76
77 These hooks are project hooks and are copied to the '.git/hooks' directory
78 of all subprojects.
79
80 This function caches the list of hooks (based on the contents of the
81 'repo/hooks' directory) on the first call.
82
83 Returns:
84 A list of absolute paths to all of the files in the hooks directory.
85 """
86 global _project_hook_list
87 if _project_hook_list is None:
88 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
89 d = os.path.join(d, 'hooks')
90 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
91 return _project_hook_list
92
93
Shawn O. Pearce632768b2008-10-23 11:58:52 -070094class DownloadedChange(object):
95 _commit_cache = None
96
97 def __init__(self, project, base, change_id, ps_id, commit):
98 self.project = project
99 self.base = base
100 self.change_id = change_id
101 self.ps_id = ps_id
102 self.commit = commit
103
104 @property
105 def commits(self):
106 if self._commit_cache is None:
107 self._commit_cache = self.project.bare_git.rev_list(
108 '--abbrev=8',
109 '--abbrev-commit',
110 '--pretty=oneline',
111 '--reverse',
112 '--date-order',
113 not_rev(self.base),
114 self.commit,
115 '--')
116 return self._commit_cache
117
118
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700119class ReviewableBranch(object):
120 _commit_cache = None
121
122 def __init__(self, project, branch, base):
123 self.project = project
124 self.branch = branch
125 self.base = base
126
127 @property
128 def name(self):
129 return self.branch.name
130
131 @property
132 def commits(self):
133 if self._commit_cache is None:
134 self._commit_cache = self.project.bare_git.rev_list(
135 '--abbrev=8',
136 '--abbrev-commit',
137 '--pretty=oneline',
138 '--reverse',
139 '--date-order',
140 not_rev(self.base),
141 R_HEADS + self.name,
142 '--')
143 return self._commit_cache
144
145 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800146 def unabbrev_commits(self):
147 r = dict()
148 for commit in self.project.bare_git.rev_list(
149 not_rev(self.base),
150 R_HEADS + self.name,
151 '--'):
152 r[commit[0:8]] = commit
153 return r
154
155 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700156 def date(self):
157 return self.project.bare_git.log(
158 '--pretty=format:%cd',
159 '-n', '1',
160 R_HEADS + self.name,
161 '--')
162
Bryan Jacobsf609f912013-05-06 13:36:24 -0400163 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800164 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700165 people,
Brian Harring435370c2012-07-28 15:37:04 -0700166 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400167 draft=draft,
168 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700170 def GetPublishedRefs(self):
171 refs = {}
172 output = self.project.bare_git.ls_remote(
173 self.branch.remote.SshReviewUrl(self.project.UserEmail),
174 'refs/changes/*')
175 for line in output.split('\n'):
176 try:
177 (sha, ref) = line.split()
178 refs[sha] = ref
179 except ValueError:
180 pass
181
182 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700183
184class StatusColoring(Coloring):
185 def __init__(self, config):
186 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100187 self.project = self.printer('header', attr='bold')
188 self.branch = self.printer('header', attr='bold')
189 self.nobranch = self.printer('nobranch', fg='red')
190 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700191
Anthony King7bdac712014-07-16 12:56:40 +0100192 self.added = self.printer('added', fg='green')
193 self.changed = self.printer('changed', fg='red')
194 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195
196
197class DiffColoring(Coloring):
198 def __init__(self, config):
199 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100200 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700201
Anthony King7bdac712014-07-16 12:56:40 +0100202class _Annotation(object):
James W. Mills24c13082012-04-12 15:04:13 -0500203 def __init__(self, name, value, keep):
204 self.name = name
205 self.value = value
206 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207
Anthony King7bdac712014-07-16 12:56:40 +0100208class _CopyFile(object):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800209 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210 self.src = src
211 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800212 self.abs_src = abssrc
213 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214
215 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800216 src = self.abs_src
217 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218 # copy file if it does not exist or is out of date
219 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
220 try:
221 # remove existing file first, since it might be read-only
222 if os.path.exists(dest):
223 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400224 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200225 dest_dir = os.path.dirname(dest)
226 if not os.path.isdir(dest_dir):
227 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228 shutil.copy(src, dest)
229 # make the file read-only
230 mode = os.stat(dest)[stat.ST_MODE]
231 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
232 os.chmod(dest, mode)
233 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700234 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235
Anthony King7bdac712014-07-16 12:56:40 +0100236class _LinkFile(object):
Wink Saville4c426ef2015-06-03 08:05:17 -0700237 def __init__(self, git_worktree, src, dest, relsrc, absdest):
238 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500239 self.src = src
240 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700241 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500242 self.abs_dest = absdest
243
Wink Saville4c426ef2015-06-03 08:05:17 -0700244 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500245 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700246 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500247 try:
248 # remove existing file first, since it might be read-only
Wink Saville4c426ef2015-06-03 08:05:17 -0700249 if os.path.exists(absDest):
250 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500251 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700252 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500253 if not os.path.isdir(dest_dir):
254 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700255 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500256 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700257 _error('Cannot link file %s to %s', relSrc, absDest)
258
259 def _Link(self):
260 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
261 on the src linking all of the files in the source in to the destination
262 directory.
263 """
264 # We use the absSrc to handle the situation where the current directory
265 # is not the root of the repo
266 absSrc = os.path.join(self.git_worktree, self.src)
267 if os.path.exists(absSrc):
268 # Entity exists so just a simple one to one link operation
269 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
270 else:
271 # Entity doesn't exist assume there is a wild card
272 absDestDir = self.abs_dest
273 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
274 _error('Link error: src with wildcard, %s must be a directory',
275 absDestDir)
276 else:
277 absSrcFiles = glob.glob(absSrc)
278 for absSrcFile in absSrcFiles:
279 # Create a releative path from source dir to destination dir
280 absSrcDir = os.path.dirname(absSrcFile)
281 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
282
283 # Get the source file name
284 srcFile = os.path.basename(absSrcFile)
285
286 # Now form the final full paths to srcFile. They will be
287 # absolute for the desintaiton and relative for the srouce.
288 absDest = os.path.join(absDestDir, srcFile)
289 relSrc = os.path.join(relSrcDir, srcFile)
290 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500291
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700292class RemoteSpec(object):
293 def __init__(self,
294 name,
Anthony King7bdac712014-07-16 12:56:40 +0100295 url=None,
296 review=None,
297 revision=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700298 self.name = name
299 self.url = url
300 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100301 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700302
Doug Anderson37282b42011-03-04 11:54:18 -0800303class RepoHook(object):
304 """A RepoHook contains information about a script to run as a hook.
305
306 Hooks are used to run a python script before running an upload (for instance,
307 to run presubmit checks). Eventually, we may have hooks for other actions.
308
309 This shouldn't be confused with files in the 'repo/hooks' directory. Those
310 files are copied into each '.git/hooks' folder for each project. Repo-level
311 hooks are associated instead with repo actions.
312
313 Hooks are always python. When a hook is run, we will load the hook into the
314 interpreter and execute its main() function.
315 """
316 def __init__(self,
317 hook_type,
318 hooks_project,
319 topdir,
320 abort_if_user_denies=False):
321 """RepoHook constructor.
322
323 Params:
324 hook_type: A string representing the type of hook. This is also used
325 to figure out the name of the file containing the hook. For
326 example: 'pre-upload'.
327 hooks_project: The project containing the repo hooks. If you have a
328 manifest, this is manifest.repo_hooks_project. OK if this is None,
329 which will make the hook a no-op.
330 topdir: Repo's top directory (the one containing the .repo directory).
331 Scripts will run with CWD as this directory. If you have a manifest,
332 this is manifest.topdir
333 abort_if_user_denies: If True, we'll throw a HookError() if the user
334 doesn't allow us to run the hook.
335 """
336 self._hook_type = hook_type
337 self._hooks_project = hooks_project
338 self._topdir = topdir
339 self._abort_if_user_denies = abort_if_user_denies
340
341 # Store the full path to the script for convenience.
342 if self._hooks_project:
343 self._script_fullpath = os.path.join(self._hooks_project.worktree,
344 self._hook_type + '.py')
345 else:
346 self._script_fullpath = None
347
348 def _GetHash(self):
349 """Return a hash of the contents of the hooks directory.
350
351 We'll just use git to do this. This hash has the property that if anything
352 changes in the directory we will return a different has.
353
354 SECURITY CONSIDERATION:
355 This hash only represents the contents of files in the hook directory, not
356 any other files imported or called by hooks. Changes to imported files
357 can change the script behavior without affecting the hash.
358
359 Returns:
360 A string representing the hash. This will always be ASCII so that it can
361 be printed to the user easily.
362 """
363 assert self._hooks_project, "Must have hooks to calculate their hash."
364
365 # We will use the work_git object rather than just calling GetRevisionId().
366 # That gives us a hash of the latest checked in version of the files that
367 # the user will actually be executing. Specifically, GetRevisionId()
368 # doesn't appear to change even if a user checks out a different version
369 # of the hooks repo (via git checkout) nor if a user commits their own revs.
370 #
371 # NOTE: Local (non-committed) changes will not be factored into this hash.
372 # I think this is OK, since we're really only worried about warning the user
373 # about upstream changes.
374 return self._hooks_project.work_git.rev_parse('HEAD')
375
376 def _GetMustVerb(self):
377 """Return 'must' if the hook is required; 'should' if not."""
378 if self._abort_if_user_denies:
379 return 'must'
380 else:
381 return 'should'
382
383 def _CheckForHookApproval(self):
384 """Check to see whether this hook has been approved.
385
386 We'll look at the hash of all of the hooks. If this matches the hash that
387 the user last approved, we're done. If it doesn't, we'll ask the user
388 about approval.
389
390 Note that we ask permission for each individual hook even though we use
391 the hash of all hooks when detecting changes. We'd like the user to be
392 able to approve / deny each hook individually. We only use the hash of all
393 hooks because there is no other easy way to detect changes to local imports.
394
395 Returns:
396 True if this hook is approved to run; False otherwise.
397
398 Raises:
399 HookError: Raised if the user doesn't approve and abort_if_user_denies
400 was passed to the consturctor.
401 """
Doug Anderson37282b42011-03-04 11:54:18 -0800402 hooks_config = self._hooks_project.config
403 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
404
405 # Get the last hash that the user approved for this hook; may be None.
406 old_hash = hooks_config.GetString(git_approval_key)
407
408 # Get the current hash so we can tell if scripts changed since approval.
409 new_hash = self._GetHash()
410
411 if old_hash is not None:
412 # User previously approved hook and asked not to be prompted again.
413 if new_hash == old_hash:
414 # Approval matched. We're done.
415 return True
416 else:
417 # Give the user a reason why we're prompting, since they last told
418 # us to "never ask again".
419 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
420 self._hook_type)
421 else:
422 prompt = ''
423
424 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
425 if sys.stdout.isatty():
426 prompt += ('Repo %s run the script:\n'
427 ' %s\n'
428 '\n'
429 'Do you want to allow this script to run '
430 '(yes/yes-never-ask-again/NO)? ') % (
431 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530432 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900433 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800434
435 # User is doing a one-time approval.
436 if response in ('y', 'yes'):
437 return True
438 elif response == 'yes-never-ask-again':
439 hooks_config.SetString(git_approval_key, new_hash)
440 return True
441
442 # For anything else, we'll assume no approval.
443 if self._abort_if_user_denies:
444 raise HookError('You must allow the %s hook or use --no-verify.' %
445 self._hook_type)
446
447 return False
448
449 def _ExecuteHook(self, **kwargs):
450 """Actually execute the given hook.
451
452 This will run the hook's 'main' function in our python interpreter.
453
454 Args:
455 kwargs: Keyword arguments to pass to the hook. These are often specific
456 to the hook type. For instance, pre-upload hooks will contain
457 a project_list.
458 """
459 # Keep sys.path and CWD stashed away so that we can always restore them
460 # upon function exit.
461 orig_path = os.getcwd()
462 orig_syspath = sys.path
463
464 try:
465 # Always run hooks with CWD as topdir.
466 os.chdir(self._topdir)
467
468 # Put the hook dir as the first item of sys.path so hooks can do
469 # relative imports. We want to replace the repo dir as [0] so
470 # hooks can't import repo files.
471 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
472
473 # Exec, storing global context in the context dict. We catch exceptions
474 # and convert to a HookError w/ just the failing traceback.
475 context = {}
476 try:
Anthony King70f68902014-05-05 21:15:34 +0100477 exec(compile(open(self._script_fullpath).read(),
478 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800479 except Exception:
480 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
481 traceback.format_exc(), self._hook_type))
482
483 # Running the script should have defined a main() function.
484 if 'main' not in context:
485 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
486
487
488 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
489 # We don't actually want hooks to define their main with this argument--
490 # it's there to remind them that their hook should always take **kwargs.
491 # For instance, a pre-upload hook should be defined like:
492 # def main(project_list, **kwargs):
493 #
494 # This allows us to later expand the API without breaking old hooks.
495 kwargs = kwargs.copy()
496 kwargs['hook_should_take_kwargs'] = True
497
498 # Call the main function in the hook. If the hook should cause the
499 # build to fail, it will raise an Exception. We'll catch that convert
500 # to a HookError w/ just the failing traceback.
501 try:
502 context['main'](**kwargs)
503 except Exception:
504 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
505 'above.' % (
506 traceback.format_exc(), self._hook_type))
507 finally:
508 # Restore sys.path and CWD.
509 sys.path = orig_syspath
510 os.chdir(orig_path)
511
512 def Run(self, user_allows_all_hooks, **kwargs):
513 """Run the hook.
514
515 If the hook doesn't exist (because there is no hooks project or because
516 this particular hook is not enabled), this is a no-op.
517
518 Args:
519 user_allows_all_hooks: If True, we will never prompt about running the
520 hook--we'll just assume it's OK to run it.
521 kwargs: Keyword arguments to pass to the hook. These are often specific
522 to the hook type. For instance, pre-upload hooks will contain
523 a project_list.
524
525 Raises:
526 HookError: If there was a problem finding the hook or the user declined
527 to run a required hook (from _CheckForHookApproval).
528 """
529 # No-op if there is no hooks project or if hook is disabled.
530 if ((not self._hooks_project) or
531 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
532 return
533
534 # Bail with a nice error if we can't find the hook.
535 if not os.path.isfile(self._script_fullpath):
536 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
537
538 # Make sure the user is OK with running the hook.
539 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
540 return
541
542 # Run the hook with the same version of python we're using.
543 self._ExecuteHook(**kwargs)
544
545
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700546class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600547 # These objects can be shared between several working trees.
548 shareable_files = ['description', 'info']
549 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
550 # These objects can only be used by a single working tree.
551 working_tree_files = ['config', 'packed-refs', 'shallow']
552 working_tree_dirs = ['logs', 'refs']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700553 def __init__(self,
554 manifest,
555 name,
556 remote,
557 gitdir,
David James8d201162013-10-11 17:03:19 -0700558 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700559 worktree,
560 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700561 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800562 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100563 rebase=True,
564 groups=None,
565 sync_c=False,
566 sync_s=False,
567 clone_depth=None,
568 upstream=None,
569 parent=None,
570 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900571 dest_branch=None,
572 optimized_fetch=False):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800573 """Init a Project object.
574
575 Args:
576 manifest: The XmlManifest object.
577 name: The `name` attribute of manifest.xml's project element.
578 remote: RemoteSpec object specifying its remote's properties.
579 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700580 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800581 worktree: Absolute path of git working tree.
582 relpath: Relative path of git working tree to repo's top directory.
583 revisionExpr: The `revision` attribute of manifest.xml's project element.
584 revisionId: git commit id for checking out.
585 rebase: The `rebase` attribute of manifest.xml's project element.
586 groups: The `groups` attribute of manifest.xml's project element.
587 sync_c: The `sync-c` attribute of manifest.xml's project element.
588 sync_s: The `sync-s` attribute of manifest.xml's project element.
589 upstream: The `upstream` attribute of manifest.xml's project element.
590 parent: The parent Project object.
591 is_derived: False if the project was explicitly defined in the manifest;
592 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400593 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900594 optimized_fetch: If True, when a project is set to a sha1 revision, only
595 fetch from the remote if the sha1 is not present locally.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800596 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700597 self.manifest = manifest
598 self.name = name
599 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800600 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700601 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800602 if worktree:
603 self.worktree = worktree.replace('\\', '/')
604 else:
605 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700606 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700607 self.revisionExpr = revisionExpr
608
609 if revisionId is None \
610 and revisionExpr \
611 and IsId(revisionExpr):
612 self.revisionId = revisionExpr
613 else:
614 self.revisionId = revisionId
615
Mike Pontillod3153822012-02-28 11:53:24 -0800616 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700617 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700618 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800619 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900620 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700621 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800622 self.parent = parent
623 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900624 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800625 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800626
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700627 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700628 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500629 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500630 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700631 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100632 gitdir=self.gitdir,
633 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700634
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800635 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700636 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800637 else:
638 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700639 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700640 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700641 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400642 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700643
Doug Anderson37282b42011-03-04 11:54:18 -0800644 # This will be filled in if a project is later identified to be the
645 # project containing repo hooks.
646 self.enabled_repo_hooks = []
647
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700648 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800649 def Derived(self):
650 return self.is_derived
651
652 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600654 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700655
656 @property
657 def CurrentBranch(self):
658 """Obtain the name of the currently checked out branch.
659 The branch name omits the 'refs/heads/' prefix.
660 None is returned if the project is on a detached HEAD.
661 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700662 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700663 if b.startswith(R_HEADS):
664 return b[len(R_HEADS):]
665 return None
666
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700667 def IsRebaseInProgress(self):
668 w = self.worktree
669 g = os.path.join(w, '.git')
670 return os.path.exists(os.path.join(g, 'rebase-apply')) \
671 or os.path.exists(os.path.join(g, 'rebase-merge')) \
672 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200673
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700674 def IsDirty(self, consider_untracked=True):
675 """Is the working directory modified in some way?
676 """
677 self.work_git.update_index('-q',
678 '--unmerged',
679 '--ignore-missing',
680 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900681 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700682 return True
683 if self.work_git.DiffZ('diff-files'):
684 return True
685 if consider_untracked and self.work_git.LsOthers():
686 return True
687 return False
688
689 _userident_name = None
690 _userident_email = None
691
692 @property
693 def UserName(self):
694 """Obtain the user's personal name.
695 """
696 if self._userident_name is None:
697 self._LoadUserIdentity()
698 return self._userident_name
699
700 @property
701 def UserEmail(self):
702 """Obtain the user's email address. This is very likely
703 to be their Gerrit login.
704 """
705 if self._userident_email is None:
706 self._LoadUserIdentity()
707 return self._userident_email
708
709 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900710 u = self.bare_git.var('GIT_COMMITTER_IDENT')
711 m = re.compile("^(.*) <([^>]*)> ").match(u)
712 if m:
713 self._userident_name = m.group(1)
714 self._userident_email = m.group(2)
715 else:
716 self._userident_name = ''
717 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700718
719 def GetRemote(self, name):
720 """Get the configuration for a single remote.
721 """
722 return self.config.GetRemote(name)
723
724 def GetBranch(self, name):
725 """Get the configuration for a single branch.
726 """
727 return self.config.GetBranch(name)
728
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700729 def GetBranches(self):
730 """Get all existing local branches.
731 """
732 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900733 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700734 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700735
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530736 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700737 if name.startswith(R_HEADS):
738 name = name[len(R_HEADS):]
739 b = self.GetBranch(name)
740 b.current = name == current
741 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900742 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700743 heads[name] = b
744
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_PUB):
747 name = name[len(R_PUB):]
748 b = heads.get(name)
749 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900750 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700751
752 return heads
753
Colin Cross5acde752012-03-28 20:15:45 -0700754 def MatchesGroups(self, manifest_groups):
755 """Returns true if the manifest groups specified at init should cause
756 this project to be synced.
757 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700758 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700759
760 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700761 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700762 manifest_groups: "-group1,group2"
763 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500764
765 The special manifest group "default" will match any project that
766 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700767 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500768 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700769 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500770 if not 'notdefault' in expanded_project_groups:
771 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700772
Conley Owens971de8e2012-04-16 10:36:08 -0700773 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700774 for group in expanded_manifest_groups:
775 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700776 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700777 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700778 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700779
Conley Owens971de8e2012-04-16 10:36:08 -0700780 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700781
782## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700783 def UncommitedFiles(self, get_all=True):
784 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700785
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700786 Args:
787 get_all: a boolean, if True - get information about all different
788 uncommitted files. If False - return as soon as any kind of
789 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500790 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700791 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500792 self.work_git.update_index('-q',
793 '--unmerged',
794 '--ignore-missing',
795 '--refresh')
796 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700797 details.append("rebase in progress")
798 if not get_all:
799 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500800
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700801 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
802 if changes:
803 details.extend(changes)
804 if not get_all:
805 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500806
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700807 changes = self.work_git.DiffZ('diff-files').keys()
808 if changes:
809 details.extend(changes)
810 if not get_all:
811 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500812
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700813 changes = self.work_git.LsOthers()
814 if changes:
815 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500816
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700817 return details
818
819 def HasChanges(self):
820 """Returns true if there are uncommitted changes.
821 """
822 if self.UncommitedFiles(get_all=False):
823 return True
824 else:
825 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500826
Terence Haddock4655e812011-03-31 12:33:34 +0200827 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200829
830 Args:
831 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 """
833 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200834 if output_redir == None:
835 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700836 print(file=output_redir)
837 print('project %s/' % self.relpath, file=output_redir)
838 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839 return
840
841 self.work_git.update_index('-q',
842 '--unmerged',
843 '--ignore-missing',
844 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700845 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
847 df = self.work_git.DiffZ('diff-files')
848 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100849 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700850 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700851
852 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200853 if not output_redir == None:
854 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700855 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700856
857 branch = self.CurrentBranch
858 if branch is None:
859 out.nobranch('(*** NO BRANCH ***)')
860 else:
861 out.branch('branch %s', branch)
862 out.nl()
863
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700864 if rb:
865 out.important('prior sync failed; rebase still in progress')
866 out.nl()
867
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868 paths = list()
869 paths.extend(di.keys())
870 paths.extend(df.keys())
871 paths.extend(do)
872
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530873 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900874 try:
875 i = di[p]
876 except KeyError:
877 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900879 try:
880 f = df[p]
881 except KeyError:
882 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200883
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900884 if i:
885 i_status = i.status.upper()
886 else:
887 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900889 if f:
890 f_status = f.status.lower()
891 else:
892 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893
894 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800895 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896 i.src_path, p, i.level)
897 else:
898 line = ' %s%s\t%s' % (i_status, f_status, p)
899
900 if i and not f:
901 out.added('%s', line)
902 elif (i and f) or (not i and f):
903 out.changed('%s', line)
904 elif not i and not f:
905 out.untracked('%s', line)
906 else:
907 out.write('%s', line)
908 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200909
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700910 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911
pelyad67872d2012-03-28 14:49:58 +0300912 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913 """Prints the status of the repository to stdout.
914 """
915 out = DiffColoring(self.config)
916 cmd = ['diff']
917 if out.is_on:
918 cmd.append('--color')
919 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300920 if absolute_paths:
921 cmd.append('--src-prefix=a/%s/' % self.relpath)
922 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923 cmd.append('--')
924 p = GitCommand(self,
925 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100926 capture_stdout=True,
927 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928 has_diff = False
929 for line in p.process.stdout:
930 if not has_diff:
931 out.nl()
932 out.project('project %s/' % self.relpath)
933 out.nl()
934 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700935 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936 p.Wait()
937
938
939## Publish / Upload ##
940
David Pursehouse8a68ff92012-09-24 12:15:13 +0900941 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 """Was the branch published (uploaded) for code review?
943 If so, returns the SHA-1 hash of the last published
944 state for the branch.
945 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700946 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900947 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700948 try:
949 return self.bare_git.rev_parse(key)
950 except GitError:
951 return None
952 else:
953 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900954 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700955 except KeyError:
956 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957
David Pursehouse8a68ff92012-09-24 12:15:13 +0900958 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700959 """Prunes any stale published refs.
960 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900961 if all_refs is None:
962 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963 heads = set()
964 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530965 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966 if name.startswith(R_HEADS):
967 heads.add(name)
968 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900969 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530971 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 n = name[len(R_PUB):]
973 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900974 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700975
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700976 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700977 """List any branches which can be uploaded for review.
978 """
979 heads = {}
980 pubed = {}
981
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530982 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900984 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700985 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900986 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987
988 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530989 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900990 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700992 if selected_branch and branch != selected_branch:
993 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700994
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800995 rb = self.GetUploadableBranch(branch)
996 if rb:
997 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998 return ready
999
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001000 def GetUploadableBranch(self, branch_name):
1001 """Get a single uploadable branch, or None.
1002 """
1003 branch = self.GetBranch(branch_name)
1004 base = branch.LocalMerge
1005 if branch.LocalMerge:
1006 rb = ReviewableBranch(self, branch, base)
1007 if rb.commits:
1008 return rb
1009 return None
1010
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001011 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001012 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001013 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001014 draft=False,
1015 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016 """Uploads the named branch for code review.
1017 """
1018 if branch is None:
1019 branch = self.CurrentBranch
1020 if branch is None:
1021 raise GitError('not currently on a branch')
1022
1023 branch = self.GetBranch(branch)
1024 if not branch.LocalMerge:
1025 raise GitError('branch %s does not track a remote' % branch.name)
1026 if not branch.remote.review:
1027 raise GitError('remote %s has no review url' % branch.remote.name)
1028
Bryan Jacobsf609f912013-05-06 13:36:24 -04001029 if dest_branch is None:
1030 dest_branch = self.dest_branch
1031 if dest_branch is None:
1032 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001033 if not dest_branch.startswith(R_HEADS):
1034 dest_branch = R_HEADS + dest_branch
1035
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001036 if not branch.remote.projectname:
1037 branch.remote.projectname = self.name
1038 branch.remote.Save()
1039
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001040 url = branch.remote.ReviewUrl(self.UserEmail)
1041 if url is None:
1042 raise UploadError('review not configured')
1043 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001044
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001045 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001046 rp = ['gerrit receive-pack']
1047 for e in people[0]:
1048 rp.append('--reviewer=%s' % sq(e))
1049 for e in people[1]:
1050 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001051 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001052
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001053 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001054
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001055 if dest_branch.startswith(R_HEADS):
1056 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001057
1058 upload_type = 'for'
1059 if draft:
1060 upload_type = 'drafts'
1061
1062 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1063 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001064 if auto_topic:
1065 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001066 if not url.startswith('ssh://'):
1067 rp = ['r=%s' % p for p in people[0]] + \
1068 ['cc=%s' % p for p in people[1]]
1069 if rp:
1070 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001071 cmd.append(ref_spec)
1072
Anthony King7bdac712014-07-16 12:56:40 +01001073 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001074 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075
1076 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1077 self.bare_git.UpdateRef(R_PUB + branch.name,
1078 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001079 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080
1081
1082## Sync ##
1083
Julien Campergue335f5ef2013-10-16 11:02:35 +02001084 def _ExtractArchive(self, tarpath, path=None):
1085 """Extract the given tar on its current location
1086
1087 Args:
1088 - tarpath: The path to the actual tar file
1089
1090 """
1091 try:
1092 with tarfile.open(tarpath, 'r') as tar:
1093 tar.extractall(path=path)
1094 return True
1095 except (IOError, tarfile.TarError) as e:
1096 print("error: Cannot extract archive %s: "
1097 "%s" % (tarpath, str(e)), file=sys.stderr)
1098 return False
1099
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001100 def Sync_NetworkHalf(self,
1101 quiet=False,
1102 is_new=None,
1103 current_branch_only=False,
Kevin Degiabaa7f32014-11-12 11:27:45 -07001104 force_sync=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001105 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001106 no_tags=False,
David Pursehouseb1553542014-09-04 21:28:09 +09001107 archive=False,
1108 optimized_fetch=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001109 """Perform only the network IO portion of the sync process.
1110 Local working directory/branch state is not affected.
1111 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001112 if archive and not isinstance(self, MetaProject):
1113 if self.remote.url.startswith(('http://', 'https://')):
1114 print("error: %s: Cannot fetch archives from http/https "
1115 "remotes." % self.name, file=sys.stderr)
1116 return False
1117
1118 name = self.relpath.replace('\\', '/')
1119 name = name.replace('/', '_')
1120 tarpath = '%s.tar' % name
1121 topdir = self.manifest.topdir
1122
1123 try:
1124 self._FetchArchive(tarpath, cwd=topdir)
1125 except GitError as e:
1126 print('error: %s' % str(e), file=sys.stderr)
1127 return False
1128
1129 # From now on, we only need absolute tarpath
1130 tarpath = os.path.join(topdir, tarpath)
1131
1132 if not self._ExtractArchive(tarpath, path=topdir):
1133 return False
1134 try:
1135 os.remove(tarpath)
1136 except OSError as e:
1137 print("warn: Cannot remove archive %s: "
1138 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001139 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001140 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001141 if is_new is None:
1142 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001143 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001144 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001145 else:
1146 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001147 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001148
1149 if is_new:
1150 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1151 try:
1152 fd = open(alt, 'rb')
1153 try:
1154 alt_dir = fd.readline().rstrip()
1155 finally:
1156 fd.close()
1157 except IOError:
1158 alt_dir = None
1159 else:
1160 alt_dir = None
1161
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001162 if clone_bundle \
1163 and alt_dir is None \
1164 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001165 is_new = False
1166
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001167 if not current_branch_only:
1168 if self.sync_c:
1169 current_branch_only = True
1170 elif not self.manifest._loaded:
1171 # Manifest cannot check defaults until it syncs.
1172 current_branch_only = False
1173 elif self.manifest.default.sync_c:
1174 current_branch_only = True
1175
David Pursehouseb1553542014-09-04 21:28:09 +09001176 need_to_fetch = not (optimized_fetch and \
1177 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1178 if (need_to_fetch
Conley Owens666d5342014-05-01 13:09:57 -07001179 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1180 current_branch_only=current_branch_only,
1181 no_tags=no_tags)):
Anthony King7bdac712014-07-16 12:56:40 +01001182 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001183
1184 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001185 self._InitMRef()
1186 else:
1187 self._InitMirrorHead()
1188 try:
1189 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1190 except OSError:
1191 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001193
1194 def PostRepoUpgrade(self):
1195 self._InitHooks()
1196
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001197 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001198 for copyfile in self.copyfiles:
1199 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001200 for linkfile in self.linkfiles:
1201 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001202
Julien Camperguedd654222014-01-09 16:21:37 +01001203 def GetCommitRevisionId(self):
1204 """Get revisionId of a commit.
1205
1206 Use this method instead of GetRevisionId to get the id of the commit rather
1207 than the id of the current git object (for example, a tag)
1208
1209 """
1210 if not self.revisionExpr.startswith(R_TAGS):
1211 return self.GetRevisionId(self._allrefs)
1212
1213 try:
1214 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1215 except GitError:
1216 raise ManifestInvalidRevisionError(
1217 'revision %s in %s not found' % (self.revisionExpr,
1218 self.name))
1219
David Pursehouse8a68ff92012-09-24 12:15:13 +09001220 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001221 if self.revisionId:
1222 return self.revisionId
1223
1224 rem = self.GetRemote(self.remote.name)
1225 rev = rem.ToLocal(self.revisionExpr)
1226
David Pursehouse8a68ff92012-09-24 12:15:13 +09001227 if all_refs is not None and rev in all_refs:
1228 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001229
1230 try:
1231 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1232 except GitError:
1233 raise ManifestInvalidRevisionError(
1234 'revision %s in %s not found' % (self.revisionExpr,
1235 self.name))
1236
Kevin Degiabaa7f32014-11-12 11:27:45 -07001237 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001238 """Perform only the local IO portion of the sync process.
1239 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001240 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001241 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001242 all_refs = self.bare_ref.all
1243 self.CleanPublishedCache(all_refs)
1244 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001245
David Pursehouse1d947b32012-10-25 12:23:11 +09001246 def _doff():
1247 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001248 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001249
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001250 head = self.work_git.GetHead()
1251 if head.startswith(R_HEADS):
1252 branch = head[len(R_HEADS):]
1253 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001254 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001255 except KeyError:
1256 head = None
1257 else:
1258 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001259
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001260 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001261 # Currently on a detached HEAD. The user is assumed to
1262 # not have any local modifications worth worrying about.
1263 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001264 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001265 syncbuf.fail(self, _PriorSyncFailedError())
1266 return
1267
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001268 if head == revid:
1269 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001270 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001271 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001272 if not syncbuf.detach_head:
1273 return
1274 else:
1275 lost = self._revlist(not_rev(revid), HEAD)
1276 if lost:
1277 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001278
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001280 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001281 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001282 syncbuf.fail(self, e)
1283 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001284 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001285 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001287 if head == revid:
1288 # No changes; don't do anything further.
1289 #
1290 return
1291
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001294 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001295 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001296 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001298 syncbuf.info(self,
1299 "leaving %s; does not track upstream",
1300 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001302 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001303 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001304 syncbuf.fail(self, e)
1305 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001306 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001307 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001309 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001310 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001312 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001313 if not_merged:
1314 if upstream_gain:
1315 # The user has published this branch and some of those
1316 # commits are not yet merged upstream. We do not want
1317 # to rewrite the published commits so we punt.
1318 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001319 syncbuf.fail(self,
1320 "branch %s is published (but not merged) and is now %d commits behind"
1321 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001322 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001323 elif pub == head:
1324 # All published commits are merged, and thus we are a
1325 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001326 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001327 syncbuf.later1(self, _doff)
1328 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001330 # Examine the local commits not in the remote. Find the
1331 # last one attributed to this user, if any.
1332 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001333 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001334 last_mine = None
1335 cnt_mine = 0
1336 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301337 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001338 if committer_email == self.UserEmail:
1339 last_mine = commit_id
1340 cnt_mine += 1
1341
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001342 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001343 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344
1345 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001346 syncbuf.fail(self, _DirtyError())
1347 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001348
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001349 # If the upstream switched on us, warn the user.
1350 #
1351 if branch.merge != self.revisionExpr:
1352 if branch.merge and self.revisionExpr:
1353 syncbuf.info(self,
1354 'manifest switched %s...%s',
1355 branch.merge,
1356 self.revisionExpr)
1357 elif branch.merge:
1358 syncbuf.info(self,
1359 'manifest no longer tracks %s',
1360 branch.merge)
1361
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001362 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001364 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001366 syncbuf.info(self,
1367 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001368 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001369
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001370 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001371 if not ID_RE.match(self.revisionExpr):
1372 # in case of manifest sync the revisionExpr might be a SHA1
1373 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001374 if not branch.merge.startswith('refs/'):
1375 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376 branch.Save()
1377
Mike Pontillod3153822012-02-28 11:53:24 -08001378 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001379 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001380 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001381 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001382 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001383 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001384 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001385 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001386 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001387 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001388 syncbuf.fail(self, e)
1389 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001390 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001391 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001392
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001393 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394 # dest should already be an absolute path, but src is project relative
1395 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001396 abssrc = os.path.join(self.worktree, src)
1397 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001399 def AddLinkFile(self, src, dest, absdest):
1400 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001401 # make src relative path to dest
1402 absdestdir = os.path.dirname(absdest)
1403 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001404 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001405
James W. Mills24c13082012-04-12 15:04:13 -05001406 def AddAnnotation(self, name, value, keep):
1407 self.annotations.append(_Annotation(name, value, keep))
1408
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001409 def DownloadPatchSet(self, change_id, patch_id):
1410 """Download a single patch set of a single change to FETCH_HEAD.
1411 """
1412 remote = self.GetRemote(self.remote.name)
1413
1414 cmd = ['fetch', remote.name]
1415 cmd.append('refs/changes/%2.2d/%d/%d' \
1416 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001417 if GitCommand(self, cmd, bare=True).Wait() != 0:
1418 return None
1419 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001420 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001421 change_id,
1422 patch_id,
1423 self.bare_git.rev_parse('FETCH_HEAD'))
1424
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001425
1426## Branch Management ##
1427
1428 def StartBranch(self, name):
1429 """Create a new branch off the manifest's revision.
1430 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001431 head = self.work_git.GetHead()
1432 if head == (R_HEADS + name):
1433 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001434
David Pursehouse8a68ff92012-09-24 12:15:13 +09001435 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001436 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001437 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001438 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001439 capture_stdout=True,
1440 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001441
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001442 branch = self.GetBranch(name)
1443 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001444 branch.merge = self.revisionExpr
Alexandre Boeglin38258272015-04-30 14:50:33 +02001445 if not branch.merge.startswith('refs/') and not ID_RE.match(self.revisionExpr):
Conley Owens04f2f0e2014-10-01 17:22:46 -07001446 branch.merge = R_HEADS + self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001447 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001448
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001449 if head.startswith(R_HEADS):
1450 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001451 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001452 except KeyError:
1453 head = None
1454
1455 if revid and head and revid == head:
1456 ref = os.path.join(self.gitdir, R_HEADS + name)
1457 try:
1458 os.makedirs(os.path.dirname(ref))
1459 except OSError:
1460 pass
1461 _lwrite(ref, '%s\n' % revid)
1462 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1463 'ref: %s%s\n' % (R_HEADS, name))
1464 branch.Save()
1465 return True
1466
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001467 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001468 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001469 capture_stdout=True,
1470 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001471 branch.Save()
1472 return True
1473 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001474
Wink Saville02d79452009-04-10 13:01:24 -07001475 def CheckoutBranch(self, name):
1476 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001477
1478 Args:
1479 name: The name of the branch to checkout.
1480
1481 Returns:
1482 True if the checkout succeeded; False if it didn't; None if the branch
1483 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001484 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001485 rev = R_HEADS + name
1486 head = self.work_git.GetHead()
1487 if head == rev:
1488 # Already on the branch
1489 #
1490 return True
Wink Saville02d79452009-04-10 13:01:24 -07001491
David Pursehouse8a68ff92012-09-24 12:15:13 +09001492 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001493 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001494 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001495 except KeyError:
1496 # Branch does not exist in this project
1497 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001498 return None
Wink Saville02d79452009-04-10 13:01:24 -07001499
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001500 if head.startswith(R_HEADS):
1501 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001502 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001503 except KeyError:
1504 head = None
1505
1506 if head == revid:
1507 # Same revision; just update HEAD to point to the new
1508 # target branch, but otherwise take no other action.
1509 #
1510 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1511 'ref: %s%s\n' % (R_HEADS, name))
1512 return True
1513
1514 return GitCommand(self,
1515 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001516 capture_stdout=True,
1517 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001518
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001519 def AbandonBranch(self, name):
1520 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001521
1522 Args:
1523 name: The name of the branch to abandon.
1524
1525 Returns:
1526 True if the abandon succeeded; False if it didn't; None if the branch
1527 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001528 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001529 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001530 all_refs = self.bare_ref.all
1531 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001532 # Doesn't exist
1533 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001534
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001535 head = self.work_git.GetHead()
1536 if head == rev:
1537 # We can't destroy the branch while we are sitting
1538 # on it. Switch to a detached HEAD.
1539 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001540 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001541
David Pursehouse8a68ff92012-09-24 12:15:13 +09001542 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001543 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001544 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1545 '%s\n' % revid)
1546 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001547 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001548
1549 return GitCommand(self,
1550 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001551 capture_stdout=True,
1552 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001553
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001554 def PruneHeads(self):
1555 """Prune any topic branches already merged into upstream.
1556 """
1557 cb = self.CurrentBranch
1558 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001559 left = self._allrefs
1560 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001561 if name.startswith(R_HEADS):
1562 name = name[len(R_HEADS):]
1563 if cb is None or name != cb:
1564 kill.append(name)
1565
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001566 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001567 if cb is not None \
1568 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001569 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001570 self.work_git.DetachHead(HEAD)
1571 kill.append(cb)
1572
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001573 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001574 old = self.bare_git.GetHead()
1575 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001576 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1577
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001578 try:
1579 self.bare_git.DetachHead(rev)
1580
1581 b = ['branch', '-d']
1582 b.extend(kill)
1583 b = GitCommand(self, b, bare=True,
1584 capture_stdout=True,
1585 capture_stderr=True)
1586 b.Wait()
1587 finally:
1588 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001589 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001590
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001591 for branch in kill:
1592 if (R_HEADS + branch) not in left:
1593 self.CleanPublishedCache()
1594 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001595
1596 if cb and cb not in kill:
1597 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001598 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001599
1600 kept = []
1601 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001602 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001603 branch = self.GetBranch(branch)
1604 base = branch.LocalMerge
1605 if not base:
1606 base = rev
1607 kept.append(ReviewableBranch(self, branch, base))
1608 return kept
1609
1610
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001611## Submodule Management ##
1612
1613 def GetRegisteredSubprojects(self):
1614 result = []
1615 def rec(subprojects):
1616 if not subprojects:
1617 return
1618 result.extend(subprojects)
1619 for p in subprojects:
1620 rec(p.subprojects)
1621 rec(self.subprojects)
1622 return result
1623
1624 def _GetSubmodules(self):
1625 # Unfortunately we cannot call `git submodule status --recursive` here
1626 # because the working tree might not exist yet, and it cannot be used
1627 # without a working tree in its current implementation.
1628
1629 def get_submodules(gitdir, rev):
1630 # Parse .gitmodules for submodule sub_paths and sub_urls
1631 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1632 if not sub_paths:
1633 return []
1634 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1635 # revision of submodule repository
1636 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1637 submodules = []
1638 for sub_path, sub_url in zip(sub_paths, sub_urls):
1639 try:
1640 sub_rev = sub_revs[sub_path]
1641 except KeyError:
1642 # Ignore non-exist submodules
1643 continue
1644 submodules.append((sub_rev, sub_path, sub_url))
1645 return submodules
1646
1647 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1648 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1649 def parse_gitmodules(gitdir, rev):
1650 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1651 try:
Anthony King7bdac712014-07-16 12:56:40 +01001652 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1653 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001654 except GitError:
1655 return [], []
1656 if p.Wait() != 0:
1657 return [], []
1658
1659 gitmodules_lines = []
1660 fd, temp_gitmodules_path = tempfile.mkstemp()
1661 try:
1662 os.write(fd, p.stdout)
1663 os.close(fd)
1664 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001665 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1666 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001667 if p.Wait() != 0:
1668 return [], []
1669 gitmodules_lines = p.stdout.split('\n')
1670 except GitError:
1671 return [], []
1672 finally:
1673 os.remove(temp_gitmodules_path)
1674
1675 names = set()
1676 paths = {}
1677 urls = {}
1678 for line in gitmodules_lines:
1679 if not line:
1680 continue
1681 m = re_path.match(line)
1682 if m:
1683 names.add(m.group(1))
1684 paths[m.group(1)] = m.group(2)
1685 continue
1686 m = re_url.match(line)
1687 if m:
1688 names.add(m.group(1))
1689 urls[m.group(1)] = m.group(2)
1690 continue
1691 names = sorted(names)
1692 return ([paths.get(name, '') for name in names],
1693 [urls.get(name, '') for name in names])
1694
1695 def git_ls_tree(gitdir, rev, paths):
1696 cmd = ['ls-tree', rev, '--']
1697 cmd.extend(paths)
1698 try:
Anthony King7bdac712014-07-16 12:56:40 +01001699 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1700 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001701 except GitError:
1702 return []
1703 if p.Wait() != 0:
1704 return []
1705 objects = {}
1706 for line in p.stdout.split('\n'):
1707 if not line.strip():
1708 continue
1709 object_rev, object_path = line.split()[2:4]
1710 objects[object_path] = object_rev
1711 return objects
1712
1713 try:
1714 rev = self.GetRevisionId()
1715 except GitError:
1716 return []
1717 return get_submodules(self.gitdir, rev)
1718
1719 def GetDerivedSubprojects(self):
1720 result = []
1721 if not self.Exists:
1722 # If git repo does not exist yet, querying its submodules will
1723 # mess up its states; so return here.
1724 return result
1725 for rev, path, url in self._GetSubmodules():
1726 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001727 relpath, worktree, gitdir, objdir = \
1728 self.manifest.GetSubprojectPaths(self, name, path)
1729 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001730 if project:
1731 result.extend(project.GetDerivedSubprojects())
1732 continue
David James8d201162013-10-11 17:03:19 -07001733
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001734 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001735 url=url,
1736 review=self.remote.review,
1737 revision=self.remote.revision)
1738 subproject = Project(manifest=self.manifest,
1739 name=name,
1740 remote=remote,
1741 gitdir=gitdir,
1742 objdir=objdir,
1743 worktree=worktree,
1744 relpath=relpath,
1745 revisionExpr=self.revisionExpr,
1746 revisionId=rev,
1747 rebase=self.rebase,
1748 groups=self.groups,
1749 sync_c=self.sync_c,
1750 sync_s=self.sync_s,
1751 parent=self,
1752 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001753 result.append(subproject)
1754 result.extend(subproject.GetDerivedSubprojects())
1755 return result
1756
1757
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001759 def _CheckForSha1(self):
1760 try:
1761 # if revision (sha or tag) is not present then following function
1762 # throws an error.
1763 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1764 return True
1765 except GitError:
1766 # There is no such persistent revision. We have to fetch it.
1767 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768
Julien Campergue335f5ef2013-10-16 11:02:35 +02001769 def _FetchArchive(self, tarpath, cwd=None):
1770 cmd = ['archive', '-v', '-o', tarpath]
1771 cmd.append('--remote=%s' % self.remote.url)
1772 cmd.append('--prefix=%s/' % self.relpath)
1773 cmd.append(self.revisionExpr)
1774
1775 command = GitCommand(self, cmd, cwd=cwd,
1776 capture_stdout=True,
1777 capture_stderr=True)
1778
1779 if command.Wait() != 0:
1780 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1781
Conley Owens80b87fe2014-05-09 17:13:44 -07001782
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001783 def _RemoteFetch(self, name=None,
1784 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001785 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001786 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001787 alt_dir=None,
1788 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001789
1790 is_sha1 = False
1791 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001792 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001793
David Pursehouse9bc422f2014-04-15 10:28:56 +09001794 # The depth should not be used when fetching to a mirror because
1795 # it will result in a shallow repository that cannot be cloned or
1796 # fetched from.
1797 if not self.manifest.IsMirror:
1798 if self.clone_depth:
1799 depth = self.clone_depth
1800 else:
1801 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001802 # The repo project should never be synced with partial depth
1803 if self.relpath == '.repo/repo':
1804 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001805
Shawn Pearce69e04d82014-01-29 12:48:54 -08001806 if depth:
1807 current_branch_only = True
1808
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001809 if ID_RE.match(self.revisionExpr) is not None:
1810 is_sha1 = True
1811
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001812 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001813 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001814 # this is a tag and its sha1 value should never change
1815 tag_name = self.revisionExpr[len(R_TAGS):]
1816
1817 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001818 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001819 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001820 if is_sha1 and not depth:
1821 # When syncing a specific commit and --depth is not set:
1822 # * if upstream is explicitly specified and is not a sha1, fetch only
1823 # upstream as users expect only upstream to be fetch.
1824 # Note: The commit might not be in upstream in which case the sync
1825 # will fail.
1826 # * otherwise, fetch all branches to make sure we end up with the
1827 # specific commit.
1828 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001829
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001830 if not name:
1831 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001832
1833 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001834 remote = self.GetRemote(name)
1835 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001836 ssh_proxy = True
1837
Shawn O. Pearce88443382010-10-08 10:02:09 +02001838 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001839 if alt_dir and 'objects' == os.path.basename(alt_dir):
1840 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001841 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1842 remote = self.GetRemote(name)
1843
David Pursehouse8a68ff92012-09-24 12:15:13 +09001844 all_refs = self.bare_ref.all
1845 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001846 tmp = set()
1847
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301848 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001849 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001850 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001851 all_refs[r] = ref_id
1852 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001853 continue
1854
David Pursehouse8a68ff92012-09-24 12:15:13 +09001855 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001856 continue
1857
David Pursehouse8a68ff92012-09-24 12:15:13 +09001858 r = 'refs/_alt/%s' % ref_id
1859 all_refs[r] = ref_id
1860 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001861 tmp.add(r)
1862
Shawn O. Pearce88443382010-10-08 10:02:09 +02001863 tmp_packed = ''
1864 old_packed = ''
1865
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301866 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001867 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001868 tmp_packed += line
1869 if r not in tmp:
1870 old_packed += line
1871
1872 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001873 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001874 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001875
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001876 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001877
Conley Owensf97e8382015-01-21 11:12:46 -08001878 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001879 cmd.append('--depth=%s' % depth)
1880
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001881 if quiet:
1882 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001883 if not self.worktree:
1884 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001885 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001886
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001887 # If using depth then we should not get all the tags since they may
1888 # be outside of the depth.
1889 if no_tags or depth:
1890 cmd.append('--no-tags')
1891 else:
1892 cmd.append('--tags')
1893
Conley Owens80b87fe2014-05-09 17:13:44 -07001894 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001895 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001896 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001897 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001898 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001899 spec.append('tag')
1900 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001901
David Pursehouse403b64e2015-04-27 10:41:33 +09001902 if not self.manifest.IsMirror:
1903 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06001904 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001905 # Shallow checkout of a specific commit, fetch from that commit and not
1906 # the heads only as the commit might be deeper in the history.
1907 spec.append(branch)
1908 else:
1909 if is_sha1:
1910 branch = self.upstream
1911 if branch is not None and branch.strip():
1912 if not branch.startswith('refs/'):
1913 branch = R_HEADS + branch
1914 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001915 cmd.extend(spec)
1916
1917 shallowfetch = self.config.GetString('repo.shallowfetch')
1918 if shallowfetch and shallowfetch != ' '.join(spec):
Anthony King23ff7df2015-03-28 19:42:39 +00001919 GitCommand(self, ['fetch', '--depth=2147483647', name]
1920 + shallowfetch.split(),
Conley Owens80b87fe2014-05-09 17:13:44 -07001921 bare=True, ssh_proxy=ssh_proxy).Wait()
1922 if depth:
Anthony King7bdac712014-07-16 12:56:40 +01001923 self.config.SetString('repo.shallowfetch', ' '.join(spec))
Conley Owens80b87fe2014-05-09 17:13:44 -07001924 else:
Anthony King7bdac712014-07-16 12:56:40 +01001925 self.config.SetString('repo.shallowfetch', None)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001926
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001927 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001928 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001929 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001930 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001931 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001932 ok = True
1933 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001934 # If needed, run the 'git remote prune' the first time through the loop
1935 elif (not _i and
1936 "error:" in gitcmd.stderr and
1937 "git remote prune" in gitcmd.stderr):
1938 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001939 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001940 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001941 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001942 break
1943 continue
Brian Harring14a66742012-09-28 20:21:57 -07001944 elif current_branch_only and is_sha1 and ret == 128:
1945 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1946 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1947 # abort the optimization attempt and do a full sync.
1948 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001949 elif ret < 0:
1950 # Git died with a signal, exit immediately
1951 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001952 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001953
1954 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001955 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001956 if old_packed != '':
1957 _lwrite(packed_refs, old_packed)
1958 else:
1959 os.remove(packed_refs)
1960 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001961
1962 if is_sha1 and current_branch_only and self.upstream:
1963 # We just synced the upstream given branch; verify we
1964 # got what we wanted, else trigger a second run of all
1965 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001966 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06001967 if not depth:
1968 # Avoid infinite recursion when depth is True (since depth implies
1969 # current_branch_only)
1970 return self._RemoteFetch(name=name, current_branch_only=False,
1971 initial=False, quiet=quiet, alt_dir=alt_dir)
1972 if self.clone_depth:
1973 self.clone_depth = None
1974 return self._RemoteFetch(name=name, current_branch_only=current_branch_only,
1975 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07001976
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001977 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001978
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001979 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001980 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001981 return False
1982
1983 remote = self.GetRemote(self.remote.name)
1984 bundle_url = remote.url + '/clone.bundle'
1985 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001986 if GetSchemeFromUrl(bundle_url) not in (
1987 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001988 return False
1989
1990 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1991 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1992
1993 exist_dst = os.path.exists(bundle_dst)
1994 exist_tmp = os.path.exists(bundle_tmp)
1995
1996 if not initial and not exist_dst and not exist_tmp:
1997 return False
1998
1999 if not exist_dst:
2000 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2001 if not exist_dst:
2002 return False
2003
2004 cmd = ['fetch']
2005 if quiet:
2006 cmd.append('--quiet')
2007 if not self.worktree:
2008 cmd.append('--update-head-ok')
2009 cmd.append(bundle_dst)
2010 for f in remote.fetch:
2011 cmd.append(str(f))
2012 cmd.append('refs/tags/*:refs/tags/*')
2013
2014 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002015 if os.path.exists(bundle_dst):
2016 os.remove(bundle_dst)
2017 if os.path.exists(bundle_tmp):
2018 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002019 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002020
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002021 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002022 if os.path.exists(dstPath):
2023 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002024
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002025 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002026 if quiet:
2027 cmd += ['--silent']
2028 if os.path.exists(tmpPath):
2029 size = os.stat(tmpPath).st_size
2030 if size >= 1024:
2031 cmd += ['--continue-at', '%d' % (size,)]
2032 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002033 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002034 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2035 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz497bde42015-01-02 13:58:05 -08002036 with self._GetBundleCookieFile(srcUrl, quiet) as cookiefile:
Dave Borowitz137d0132015-01-02 11:12:54 -08002037 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002038 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002039 if srcUrl.startswith('persistent-'):
2040 srcUrl = srcUrl[len('persistent-'):]
2041 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002042
Dave Borowitz137d0132015-01-02 11:12:54 -08002043 if IsTrace():
2044 Trace('%s', ' '.join(cmd))
2045 try:
2046 proc = subprocess.Popen(cmd)
2047 except OSError:
2048 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002049
Dave Borowitz137d0132015-01-02 11:12:54 -08002050 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002051
Dave Borowitz137d0132015-01-02 11:12:54 -08002052 if curlret == 22:
2053 # From curl man page:
2054 # 22: HTTP page not retrieved. The requested url was not found or
2055 # returned another error with the HTTP error code being 400 or above.
2056 # This return code only appears if -f, --fail is used.
2057 if not quiet:
2058 print("Server does not provide clone.bundle; ignoring.",
2059 file=sys.stderr)
2060 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002061
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002062 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002063 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002064 os.rename(tmpPath, dstPath)
2065 return True
2066 else:
2067 os.remove(tmpPath)
2068 return False
2069 else:
2070 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002071
Kris Giesingc8d882a2014-12-23 13:02:32 -08002072 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002073 try:
2074 with open(path) as f:
2075 if f.read(16) == '# v2 git bundle\n':
2076 return True
2077 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002078 if not quiet:
2079 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002080 return False
2081 except OSError:
2082 return False
2083
Dave Borowitz137d0132015-01-02 11:12:54 -08002084 @contextlib.contextmanager
Dave Borowitz497bde42015-01-02 13:58:05 -08002085 def _GetBundleCookieFile(self, url, quiet):
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002086 if url.startswith('persistent-'):
2087 try:
2088 p = subprocess.Popen(
2089 ['git-remote-persistent-https', '-print_config', url],
2090 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2091 stderr=subprocess.PIPE)
Dave Borowitz137d0132015-01-02 11:12:54 -08002092 try:
2093 prefix = 'http.cookiefile='
2094 cookiefile = None
2095 for line in p.stdout:
2096 line = line.strip()
2097 if line.startswith(prefix):
2098 cookiefile = line[len(prefix):]
2099 break
2100 # Leave subprocess open, as cookie file may be transient.
2101 if cookiefile:
2102 yield cookiefile
2103 return
2104 finally:
2105 p.stdin.close()
2106 if p.wait():
2107 err_msg = p.stderr.read()
2108 if ' -print_config' in err_msg:
2109 pass # Persistent proxy doesn't support -print_config.
Dave Borowitz497bde42015-01-02 13:58:05 -08002110 elif not quiet:
Dave Borowitz137d0132015-01-02 11:12:54 -08002111 print(err_msg, file=sys.stderr)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002112 except OSError as e:
2113 if e.errno == errno.ENOENT:
2114 pass # No persistent proxy.
2115 raise
Dave Borowitz137d0132015-01-02 11:12:54 -08002116 yield GitConfig.ForUser().GetString('http.cookiefile')
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002117
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002118 def _Checkout(self, rev, quiet=False):
2119 cmd = ['checkout']
2120 if quiet:
2121 cmd.append('-q')
2122 cmd.append(rev)
2123 cmd.append('--')
2124 if GitCommand(self, cmd).Wait() != 0:
2125 if self._allrefs:
2126 raise GitError('%s checkout %s ' % (self.name, rev))
2127
Anthony King7bdac712014-07-16 12:56:40 +01002128 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002129 cmd = ['cherry-pick']
2130 cmd.append(rev)
2131 cmd.append('--')
2132 if GitCommand(self, cmd).Wait() != 0:
2133 if self._allrefs:
2134 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2135
Anthony King7bdac712014-07-16 12:56:40 +01002136 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002137 cmd = ['revert']
2138 cmd.append('--no-edit')
2139 cmd.append(rev)
2140 cmd.append('--')
2141 if GitCommand(self, cmd).Wait() != 0:
2142 if self._allrefs:
2143 raise GitError('%s revert %s ' % (self.name, rev))
2144
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002145 def _ResetHard(self, rev, quiet=True):
2146 cmd = ['reset', '--hard']
2147 if quiet:
2148 cmd.append('-q')
2149 cmd.append(rev)
2150 if GitCommand(self, cmd).Wait() != 0:
2151 raise GitError('%s reset --hard %s ' % (self.name, rev))
2152
Anthony King7bdac712014-07-16 12:56:40 +01002153 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002154 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002155 if onto is not None:
2156 cmd.extend(['--onto', onto])
2157 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002158 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002159 raise GitError('%s rebase %s ' % (self.name, upstream))
2160
Pierre Tardy3d125942012-05-04 12:18:12 +02002161 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002162 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002163 if ffonly:
2164 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002165 if GitCommand(self, cmd).Wait() != 0:
2166 raise GitError('%s merge %s ' % (self.name, head))
2167
Kevin Degiabaa7f32014-11-12 11:27:45 -07002168 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002169 init_git_dir = not os.path.exists(self.gitdir)
2170 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002171 try:
2172 # Initialize the bare repository, which contains all of the objects.
2173 if init_obj_dir:
2174 os.makedirs(self.objdir)
2175 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002176
Kevin Degib1a07b82015-07-27 13:33:43 -06002177 # If we have a separate directory to hold refs, initialize it as well.
2178 if self.objdir != self.gitdir:
2179 if init_git_dir:
2180 os.makedirs(self.gitdir)
2181
2182 if init_obj_dir or init_git_dir:
2183 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2184 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002185 try:
2186 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2187 except GitError as e:
2188 print("Retrying clone after deleting %s" % force_sync, file=sys.stderr)
2189 if force_sync:
2190 try:
2191 shutil.rmtree(os.path.realpath(self.gitdir))
2192 if self.worktree and os.path.exists(
2193 os.path.realpath(self.worktree)):
2194 shutil.rmtree(os.path.realpath(self.worktree))
2195 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2196 except:
2197 raise e
2198 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002199
Kevin Degi384b3c52014-10-16 16:02:58 -06002200 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002201 mp = self.manifest.manifestProject
2202 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002203
Kevin Degib1a07b82015-07-27 13:33:43 -06002204 if ref_dir or mirror_git:
2205 if not mirror_git:
2206 mirror_git = os.path.join(ref_dir, self.name + '.git')
2207 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2208 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002209
Kevin Degib1a07b82015-07-27 13:33:43 -06002210 if os.path.exists(mirror_git):
2211 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002212
Kevin Degib1a07b82015-07-27 13:33:43 -06002213 elif os.path.exists(repo_git):
2214 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002215
Kevin Degib1a07b82015-07-27 13:33:43 -06002216 else:
2217 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002218
Kevin Degib1a07b82015-07-27 13:33:43 -06002219 if ref_dir:
2220 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2221 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002222
Kevin Degib1a07b82015-07-27 13:33:43 -06002223 self._UpdateHooks()
2224
2225 m = self.manifest.manifestProject.config
2226 for key in ['user.name', 'user.email']:
2227 if m.Has(key, include_defaults=False):
2228 self.config.SetString(key, m.GetString(key))
2229 if self.manifest.IsMirror:
2230 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002231 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002232 self.config.SetString('core.bare', None)
2233 except Exception:
2234 if init_obj_dir and os.path.exists(self.objdir):
2235 shutil.rmtree(self.objdir)
2236 if init_git_dir and os.path.exists(self.gitdir):
2237 shutil.rmtree(self.gitdir)
2238 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002239
Jimmie Westera0444582012-10-24 13:44:42 +02002240 def _UpdateHooks(self):
2241 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002242 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002243
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002244 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002245 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002246 if not os.path.exists(hooks):
2247 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002248 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002249 name = os.path.basename(stock_hook)
2250
Victor Boivie65e0f352011-04-18 11:23:29 +02002251 if name in ('commit-msg',) and not self.remote.review \
2252 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002253 # Don't install a Gerrit Code Review hook if this
2254 # project does not appear to use it for reviews.
2255 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002256 # Since the manifest project is one of those, but also
2257 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002258 continue
2259
2260 dst = os.path.join(hooks, name)
2261 if os.path.islink(dst):
2262 continue
2263 if os.path.exists(dst):
2264 if filecmp.cmp(stock_hook, dst, shallow=False):
2265 os.remove(dst)
2266 else:
2267 _error("%s: Not replacing %s hook", self.relpath, name)
2268 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002269 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002270 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002271 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002272 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002273 raise GitError('filesystem must support symlinks')
2274 else:
2275 raise
2276
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002277 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002278 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002279 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002280 remote.url = self.remote.url
2281 remote.review = self.remote.review
2282 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002283
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002284 if self.worktree:
2285 remote.ResetFetch(mirror=False)
2286 else:
2287 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288 remote.Save()
2289
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002290 def _InitMRef(self):
2291 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002292 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002293
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002294 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002295 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002296
2297 def _InitAnyMRef(self, ref):
2298 cur = self.bare_ref.symref(ref)
2299
2300 if self.revisionId:
2301 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2302 msg = 'manifest set to %s' % self.revisionId
2303 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002304 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002305 else:
2306 remote = self.GetRemote(self.remote.name)
2307 dst = remote.ToLocal(self.revisionExpr)
2308 if cur != dst:
2309 msg = 'manifest set to %s' % self.revisionExpr
2310 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002311
Kevin Degi384b3c52014-10-16 16:02:58 -06002312 def _CheckDirReference(self, srcdir, destdir, share_refs):
2313 symlink_files = self.shareable_files
2314 symlink_dirs = self.shareable_dirs
2315 if share_refs:
2316 symlink_files += self.working_tree_files
2317 symlink_dirs += self.working_tree_dirs
2318 to_symlink = symlink_files + symlink_dirs
2319 for name in set(to_symlink):
2320 dst = os.path.realpath(os.path.join(destdir, name))
2321 if os.path.lexists(dst):
2322 src = os.path.realpath(os.path.join(srcdir, name))
2323 # Fail if the links are pointing to the wrong place
2324 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002325 raise GitError('--force-sync not enabled; cannot overwrite a local '
2326 'work tree')
Kevin Degi384b3c52014-10-16 16:02:58 -06002327
David James8d201162013-10-11 17:03:19 -07002328 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2329 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2330
2331 Args:
2332 gitdir: The bare git repository. Must already be initialized.
2333 dotgit: The repository you would like to initialize.
2334 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2335 Only one work tree can store refs under a given |gitdir|.
2336 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2337 This saves you the effort of initializing |dotgit| yourself.
2338 """
Kevin Degi384b3c52014-10-16 16:02:58 -06002339 symlink_files = self.shareable_files
2340 symlink_dirs = self.shareable_dirs
David James8d201162013-10-11 17:03:19 -07002341 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002342 symlink_files += self.working_tree_files
2343 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002344 to_symlink = symlink_files + symlink_dirs
2345
2346 to_copy = []
2347 if copy_all:
2348 to_copy = os.listdir(gitdir)
2349
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002350 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002351 for name in set(to_copy).union(to_symlink):
2352 try:
2353 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002354 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002355
Kevin Degi384b3c52014-10-16 16:02:58 -06002356 if os.path.lexists(dst):
2357 continue
David James8d201162013-10-11 17:03:19 -07002358
2359 # If the source dir doesn't exist, create an empty dir.
2360 if name in symlink_dirs and not os.path.lexists(src):
2361 os.makedirs(src)
2362
Conley Owens80b87fe2014-05-09 17:13:44 -07002363 # If the source file doesn't exist, ensure the destination
2364 # file doesn't either.
2365 if name in symlink_files and not os.path.lexists(src):
2366 try:
2367 os.remove(dst)
2368 except OSError:
2369 pass
2370
David James8d201162013-10-11 17:03:19 -07002371 if name in to_symlink:
2372 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2373 elif copy_all and not os.path.islink(dst):
2374 if os.path.isdir(src):
2375 shutil.copytree(src, dst)
2376 elif os.path.isfile(src):
2377 shutil.copy(src, dst)
2378 except OSError as e:
2379 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002380 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002381 else:
2382 raise
2383
Kevin Degiabaa7f32014-11-12 11:27:45 -07002384 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002385 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002386 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002387 try:
2388 if init_dotgit:
2389 os.makedirs(dotgit)
2390 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2391 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002392
Kevin Degiabaa7f32014-11-12 11:27:45 -07002393 try:
2394 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2395 except GitError as e:
2396 if force_sync:
2397 try:
2398 shutil.rmtree(dotgit)
2399 return self._InitWorkTree(force_sync=False)
2400 except:
2401 raise e
2402 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002403
Kevin Degib1a07b82015-07-27 13:33:43 -06002404 if init_dotgit:
2405 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002406
Kevin Degib1a07b82015-07-27 13:33:43 -06002407 cmd = ['read-tree', '--reset', '-u']
2408 cmd.append('-v')
2409 cmd.append(HEAD)
2410 if GitCommand(self, cmd).Wait() != 0:
2411 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002412
Kevin Degib1a07b82015-07-27 13:33:43 -06002413 self._CopyAndLinkFiles()
2414 except Exception:
2415 if init_dotgit:
2416 shutil.rmtree(dotgit)
2417 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002418
2419 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002420 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002421
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002422 def _revlist(self, *args, **kw):
2423 a = []
2424 a.extend(args)
2425 a.append('--')
2426 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002427
2428 @property
2429 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002430 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002431
Julien Camperguedd654222014-01-09 16:21:37 +01002432 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2433 """Get logs between two revisions of this project."""
2434 comp = '..'
2435 if rev1:
2436 revs = [rev1]
2437 if rev2:
2438 revs.extend([comp, rev2])
2439 cmd = ['log', ''.join(revs)]
2440 out = DiffColoring(self.config)
2441 if out.is_on and color:
2442 cmd.append('--color')
2443 if oneline:
2444 cmd.append('--oneline')
2445
2446 try:
2447 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2448 if log.Wait() == 0:
2449 return log.stdout
2450 except GitError:
2451 # worktree may not exist if groups changed for example. In that case,
2452 # try in gitdir instead.
2453 if not os.path.exists(self.worktree):
2454 return self.bare_git.log(*cmd[1:])
2455 else:
2456 raise
2457 return None
2458
2459 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2460 """Get the list of logs from this revision to given revisionId"""
2461 logs = {}
2462 selfId = self.GetRevisionId(self._allrefs)
2463 toId = toProject.GetRevisionId(toProject._allrefs)
2464
2465 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2466 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2467 return logs
2468
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002469 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002470 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002471 self._project = project
2472 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002473 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002474
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002475 def LsOthers(self):
2476 p = GitCommand(self._project,
2477 ['ls-files',
2478 '-z',
2479 '--others',
2480 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002481 bare=False,
David James8d201162013-10-11 17:03:19 -07002482 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002483 capture_stdout=True,
2484 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002485 if p.Wait() == 0:
2486 out = p.stdout
2487 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002488 return out[:-1].split('\0') # pylint: disable=W1401
2489 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002490 return []
2491
2492 def DiffZ(self, name, *args):
2493 cmd = [name]
2494 cmd.append('-z')
2495 cmd.extend(args)
2496 p = GitCommand(self._project,
2497 cmd,
David James8d201162013-10-11 17:03:19 -07002498 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002499 bare=False,
2500 capture_stdout=True,
2501 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002502 try:
2503 out = p.process.stdout.read()
2504 r = {}
2505 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002506 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002507 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002508 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002509 info = next(out)
2510 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002511 except StopIteration:
2512 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002513
2514 class _Info(object):
2515 def __init__(self, path, omode, nmode, oid, nid, state):
2516 self.path = path
2517 self.src_path = None
2518 self.old_mode = omode
2519 self.new_mode = nmode
2520 self.old_id = oid
2521 self.new_id = nid
2522
2523 if len(state) == 1:
2524 self.status = state
2525 self.level = None
2526 else:
2527 self.status = state[:1]
2528 self.level = state[1:]
2529 while self.level.startswith('0'):
2530 self.level = self.level[1:]
2531
2532 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002533 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002534 if info.status in ('R', 'C'):
2535 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002536 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002537 r[info.path] = info
2538 return r
2539 finally:
2540 p.Wait()
2541
2542 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002543 if self._bare:
2544 path = os.path.join(self._project.gitdir, HEAD)
2545 else:
2546 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002547 try:
2548 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002549 except IOError as e:
2550 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002551 try:
2552 line = fd.read()
2553 finally:
2554 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302555 try:
2556 line = line.decode()
2557 except AttributeError:
2558 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002559 if line.startswith('ref: '):
2560 return line[5:-1]
2561 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002562
2563 def SetHead(self, ref, message=None):
2564 cmdv = []
2565 if message is not None:
2566 cmdv.extend(['-m', message])
2567 cmdv.append(HEAD)
2568 cmdv.append(ref)
2569 self.symbolic_ref(*cmdv)
2570
2571 def DetachHead(self, new, message=None):
2572 cmdv = ['--no-deref']
2573 if message is not None:
2574 cmdv.extend(['-m', message])
2575 cmdv.append(HEAD)
2576 cmdv.append(new)
2577 self.update_ref(*cmdv)
2578
2579 def UpdateRef(self, name, new, old=None,
2580 message=None,
2581 detach=False):
2582 cmdv = []
2583 if message is not None:
2584 cmdv.extend(['-m', message])
2585 if detach:
2586 cmdv.append('--no-deref')
2587 cmdv.append(name)
2588 cmdv.append(new)
2589 if old is not None:
2590 cmdv.append(old)
2591 self.update_ref(*cmdv)
2592
2593 def DeleteRef(self, name, old=None):
2594 if not old:
2595 old = self.rev_parse(name)
2596 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002597 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002598
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002599 def rev_list(self, *args, **kw):
2600 if 'format' in kw:
2601 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2602 else:
2603 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002604 cmdv.extend(args)
2605 p = GitCommand(self._project,
2606 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002607 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002608 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002609 capture_stdout=True,
2610 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002611 r = []
2612 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002613 if line[-1] == '\n':
2614 line = line[:-1]
2615 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002616 if p.Wait() != 0:
2617 raise GitError('%s rev-list %s: %s' % (
2618 self._project.name,
2619 str(args),
2620 p.stderr))
2621 return r
2622
2623 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002624 """Allow arbitrary git commands using pythonic syntax.
2625
2626 This allows you to do things like:
2627 git_obj.rev_parse('HEAD')
2628
2629 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2630 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002631 Any other positional arguments will be passed to the git command, and the
2632 following keyword arguments are supported:
2633 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002634
2635 Args:
2636 name: The name of the git command to call. Any '_' characters will
2637 be replaced with '-'.
2638
2639 Returns:
2640 A callable object that will try to call git with the named command.
2641 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002642 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002643 def runner(*args, **kwargs):
2644 cmdv = []
2645 config = kwargs.pop('config', None)
2646 for k in kwargs:
2647 raise TypeError('%s() got an unexpected keyword argument %r'
2648 % (name, k))
2649 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002650 if not git_require((1, 7, 2)):
2651 raise ValueError('cannot set config on command line for %s()'
2652 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302653 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002654 cmdv.append('-c')
2655 cmdv.append('%s=%s' % (k, v))
2656 cmdv.append(name)
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 if p.Wait() != 0:
2665 raise GitError('%s %s: %s' % (
2666 self._project.name,
2667 name,
2668 p.stderr))
2669 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302670 try:
Conley Owensedd01512013-09-26 12:59:58 -07002671 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302672 except AttributeError:
2673 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002674 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2675 return r[:-1]
2676 return r
2677 return runner
2678
2679
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002680class _PriorSyncFailedError(Exception):
2681 def __str__(self):
2682 return 'prior sync failed; rebase still in progress'
2683
2684class _DirtyError(Exception):
2685 def __str__(self):
2686 return 'contains uncommitted changes'
2687
2688class _InfoMessage(object):
2689 def __init__(self, project, text):
2690 self.project = project
2691 self.text = text
2692
2693 def Print(self, syncbuf):
2694 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2695 syncbuf.out.nl()
2696
2697class _Failure(object):
2698 def __init__(self, project, why):
2699 self.project = project
2700 self.why = why
2701
2702 def Print(self, syncbuf):
2703 syncbuf.out.fail('error: %s/: %s',
2704 self.project.relpath,
2705 str(self.why))
2706 syncbuf.out.nl()
2707
2708class _Later(object):
2709 def __init__(self, project, action):
2710 self.project = project
2711 self.action = action
2712
2713 def Run(self, syncbuf):
2714 out = syncbuf.out
2715 out.project('project %s/', self.project.relpath)
2716 out.nl()
2717 try:
2718 self.action()
2719 out.nl()
2720 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002721 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002722 out.nl()
2723 return False
2724
2725class _SyncColoring(Coloring):
2726 def __init__(self, config):
2727 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002728 self.project = self.printer('header', attr='bold')
2729 self.info = self.printer('info')
2730 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002731
2732class SyncBuffer(object):
2733 def __init__(self, config, detach_head=False):
2734 self._messages = []
2735 self._failures = []
2736 self._later_queue1 = []
2737 self._later_queue2 = []
2738
2739 self.out = _SyncColoring(config)
2740 self.out.redirect(sys.stderr)
2741
2742 self.detach_head = detach_head
2743 self.clean = True
2744
2745 def info(self, project, fmt, *args):
2746 self._messages.append(_InfoMessage(project, fmt % args))
2747
2748 def fail(self, project, err=None):
2749 self._failures.append(_Failure(project, err))
2750 self.clean = False
2751
2752 def later1(self, project, what):
2753 self._later_queue1.append(_Later(project, what))
2754
2755 def later2(self, project, what):
2756 self._later_queue2.append(_Later(project, what))
2757
2758 def Finish(self):
2759 self._PrintMessages()
2760 self._RunLater()
2761 self._PrintMessages()
2762 return self.clean
2763
2764 def _RunLater(self):
2765 for q in ['_later_queue1', '_later_queue2']:
2766 if not self._RunQueue(q):
2767 return
2768
2769 def _RunQueue(self, queue):
2770 for m in getattr(self, queue):
2771 if not m.Run(self):
2772 self.clean = False
2773 return False
2774 setattr(self, queue, [])
2775 return True
2776
2777 def _PrintMessages(self):
2778 for m in self._messages:
2779 m.Print(self)
2780 for m in self._failures:
2781 m.Print(self)
2782
2783 self._messages = []
2784 self._failures = []
2785
2786
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002787class MetaProject(Project):
2788 """A special project housed under .repo.
2789 """
2790 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002791 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002792 manifest=manifest,
2793 name=name,
2794 gitdir=gitdir,
2795 objdir=gitdir,
2796 worktree=worktree,
2797 remote=RemoteSpec('origin'),
2798 relpath='.repo/%s' % name,
2799 revisionExpr='refs/heads/master',
2800 revisionId=None,
2801 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002802
2803 def PreSync(self):
2804 if self.Exists:
2805 cb = self.CurrentBranch
2806 if cb:
2807 base = self.GetBranch(cb).merge
2808 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002809 self.revisionExpr = base
2810 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002811
Anthony King7bdac712014-07-16 12:56:40 +01002812 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002813 """ Prepare MetaProject for manifest branch switch
2814 """
2815
2816 # detach and delete manifest branch, allowing a new
2817 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002818 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002819 self.Sync_LocalHalf(syncbuf)
2820 syncbuf.Finish()
2821
2822 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002823 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002824 capture_stdout=True,
2825 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002826
2827
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002828 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002829 def LastFetch(self):
2830 try:
2831 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2832 return os.path.getmtime(fh)
2833 except OSError:
2834 return 0
2835
2836 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002837 def HasChanges(self):
2838 """Has the remote received new commits not yet checked out?
2839 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002840 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002841 return False
2842
David Pursehouse8a68ff92012-09-24 12:15:13 +09002843 all_refs = self.bare_ref.all
2844 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002845 head = self.work_git.GetHead()
2846 if head.startswith(R_HEADS):
2847 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002848 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002849 except KeyError:
2850 head = None
2851
2852 if revid == head:
2853 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002854 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002855 return True
2856 return False