blob: 1e3ab6b9090b0ef2baad4a92eed8aef54faee4c2 [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
David Pursehousee15c65a2012-08-22 10:46:11 +090035from error import GitError, HookError, UploadError
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,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001104 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001105 no_tags=False,
David Pursehouseb1553542014-09-04 21:28:09 +09001106 archive=False,
1107 optimized_fetch=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108 """Perform only the network IO portion of the sync process.
1109 Local working directory/branch state is not affected.
1110 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001111 if archive and not isinstance(self, MetaProject):
1112 if self.remote.url.startswith(('http://', 'https://')):
1113 print("error: %s: Cannot fetch archives from http/https "
1114 "remotes." % self.name, file=sys.stderr)
1115 return False
1116
1117 name = self.relpath.replace('\\', '/')
1118 name = name.replace('/', '_')
1119 tarpath = '%s.tar' % name
1120 topdir = self.manifest.topdir
1121
1122 try:
1123 self._FetchArchive(tarpath, cwd=topdir)
1124 except GitError as e:
1125 print('error: %s' % str(e), file=sys.stderr)
1126 return False
1127
1128 # From now on, we only need absolute tarpath
1129 tarpath = os.path.join(topdir, tarpath)
1130
1131 if not self._ExtractArchive(tarpath, path=topdir):
1132 return False
1133 try:
1134 os.remove(tarpath)
1135 except OSError as e:
1136 print("warn: Cannot remove archive %s: "
1137 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001138 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001139 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001140 if is_new is None:
1141 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001142 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001143 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001144 else:
1145 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001147
1148 if is_new:
1149 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1150 try:
1151 fd = open(alt, 'rb')
1152 try:
1153 alt_dir = fd.readline().rstrip()
1154 finally:
1155 fd.close()
1156 except IOError:
1157 alt_dir = None
1158 else:
1159 alt_dir = None
1160
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001161 if clone_bundle \
1162 and alt_dir is None \
1163 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001164 is_new = False
1165
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001166 if not current_branch_only:
1167 if self.sync_c:
1168 current_branch_only = True
1169 elif not self.manifest._loaded:
1170 # Manifest cannot check defaults until it syncs.
1171 current_branch_only = False
1172 elif self.manifest.default.sync_c:
1173 current_branch_only = True
1174
David Pursehouseb1553542014-09-04 21:28:09 +09001175 need_to_fetch = not (optimized_fetch and \
1176 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1177 if (need_to_fetch
Conley Owens666d5342014-05-01 13:09:57 -07001178 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1179 current_branch_only=current_branch_only,
1180 no_tags=no_tags)):
Anthony King7bdac712014-07-16 12:56:40 +01001181 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001182
1183 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001184 self._InitMRef()
1185 else:
1186 self._InitMirrorHead()
1187 try:
1188 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1189 except OSError:
1190 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001191 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001192
1193 def PostRepoUpgrade(self):
1194 self._InitHooks()
1195
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001196 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001197 for copyfile in self.copyfiles:
1198 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001199 for linkfile in self.linkfiles:
1200 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201
Julien Camperguedd654222014-01-09 16:21:37 +01001202 def GetCommitRevisionId(self):
1203 """Get revisionId of a commit.
1204
1205 Use this method instead of GetRevisionId to get the id of the commit rather
1206 than the id of the current git object (for example, a tag)
1207
1208 """
1209 if not self.revisionExpr.startswith(R_TAGS):
1210 return self.GetRevisionId(self._allrefs)
1211
1212 try:
1213 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1214 except GitError:
1215 raise ManifestInvalidRevisionError(
1216 'revision %s in %s not found' % (self.revisionExpr,
1217 self.name))
1218
David Pursehouse8a68ff92012-09-24 12:15:13 +09001219 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001220 if self.revisionId:
1221 return self.revisionId
1222
1223 rem = self.GetRemote(self.remote.name)
1224 rev = rem.ToLocal(self.revisionExpr)
1225
David Pursehouse8a68ff92012-09-24 12:15:13 +09001226 if all_refs is not None and rev in all_refs:
1227 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001228
1229 try:
1230 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1231 except GitError:
1232 raise ManifestInvalidRevisionError(
1233 'revision %s in %s not found' % (self.revisionExpr,
1234 self.name))
1235
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001236 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001237 """Perform only the local IO portion of the sync process.
1238 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001239 """
David James8d201162013-10-11 17:03:19 -07001240 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001241 all_refs = self.bare_ref.all
1242 self.CleanPublishedCache(all_refs)
1243 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001244
David Pursehouse1d947b32012-10-25 12:23:11 +09001245 def _doff():
1246 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001247 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001248
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001249 head = self.work_git.GetHead()
1250 if head.startswith(R_HEADS):
1251 branch = head[len(R_HEADS):]
1252 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001253 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001254 except KeyError:
1255 head = None
1256 else:
1257 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001259 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001260 # Currently on a detached HEAD. The user is assumed to
1261 # not have any local modifications worth worrying about.
1262 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001263 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001264 syncbuf.fail(self, _PriorSyncFailedError())
1265 return
1266
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001267 if head == revid:
1268 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001269 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001270 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001271 if not syncbuf.detach_head:
1272 return
1273 else:
1274 lost = self._revlist(not_rev(revid), HEAD)
1275 if lost:
1276 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001277
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001278 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001279 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001280 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001281 syncbuf.fail(self, e)
1282 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001283 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001284 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001285
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001286 if head == revid:
1287 # No changes; don't do anything further.
1288 #
1289 return
1290
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001291 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001293 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001295 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001297 syncbuf.info(self,
1298 "leaving %s; does not track upstream",
1299 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001301 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001302 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001303 syncbuf.fail(self, e)
1304 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001305 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001306 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001308 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001309 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001311 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001312 if not_merged:
1313 if upstream_gain:
1314 # The user has published this branch and some of those
1315 # commits are not yet merged upstream. We do not want
1316 # to rewrite the published commits so we punt.
1317 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001318 syncbuf.fail(self,
1319 "branch %s is published (but not merged) and is now %d commits behind"
1320 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001321 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001322 elif pub == head:
1323 # All published commits are merged, and thus we are a
1324 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001325 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001326 syncbuf.later1(self, _doff)
1327 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001329 # Examine the local commits not in the remote. Find the
1330 # last one attributed to this user, if any.
1331 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001332 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001333 last_mine = None
1334 cnt_mine = 0
1335 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301336 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001337 if committer_email == self.UserEmail:
1338 last_mine = commit_id
1339 cnt_mine += 1
1340
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001341 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001342 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001343
1344 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001345 syncbuf.fail(self, _DirtyError())
1346 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001347
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001348 # If the upstream switched on us, warn the user.
1349 #
1350 if branch.merge != self.revisionExpr:
1351 if branch.merge and self.revisionExpr:
1352 syncbuf.info(self,
1353 'manifest switched %s...%s',
1354 branch.merge,
1355 self.revisionExpr)
1356 elif branch.merge:
1357 syncbuf.info(self,
1358 'manifest no longer tracks %s',
1359 branch.merge)
1360
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001361 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001362 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001363 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001365 syncbuf.info(self,
1366 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001367 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001369 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001370 if not ID_RE.match(self.revisionExpr):
1371 # in case of manifest sync the revisionExpr might be a SHA1
1372 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001373 if not branch.merge.startswith('refs/'):
1374 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001375 branch.Save()
1376
Mike Pontillod3153822012-02-28 11:53:24 -08001377 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001378 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001379 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001380 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001381 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001382 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001383 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001384 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001385 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001386 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001387 syncbuf.fail(self, e)
1388 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001390 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001392 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393 # dest should already be an absolute path, but src is project relative
1394 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001395 abssrc = os.path.join(self.worktree, src)
1396 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001397
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001398 def AddLinkFile(self, src, dest, absdest):
1399 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001400 # make src relative path to dest
1401 absdestdir = os.path.dirname(absdest)
1402 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001403 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001404
James W. Mills24c13082012-04-12 15:04:13 -05001405 def AddAnnotation(self, name, value, keep):
1406 self.annotations.append(_Annotation(name, value, keep))
1407
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001408 def DownloadPatchSet(self, change_id, patch_id):
1409 """Download a single patch set of a single change to FETCH_HEAD.
1410 """
1411 remote = self.GetRemote(self.remote.name)
1412
1413 cmd = ['fetch', remote.name]
1414 cmd.append('refs/changes/%2.2d/%d/%d' \
1415 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001416 if GitCommand(self, cmd, bare=True).Wait() != 0:
1417 return None
1418 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001419 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001420 change_id,
1421 patch_id,
1422 self.bare_git.rev_parse('FETCH_HEAD'))
1423
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001424
1425## Branch Management ##
1426
1427 def StartBranch(self, name):
1428 """Create a new branch off the manifest's revision.
1429 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001430 head = self.work_git.GetHead()
1431 if head == (R_HEADS + name):
1432 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001433
David Pursehouse8a68ff92012-09-24 12:15:13 +09001434 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001435 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001436 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001437 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001438 capture_stdout=True,
1439 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001440
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001441 branch = self.GetBranch(name)
1442 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001443 branch.merge = self.revisionExpr
Alexandre Boeglin38258272015-04-30 14:50:33 +02001444 if not branch.merge.startswith('refs/') and not ID_RE.match(self.revisionExpr):
Conley Owens04f2f0e2014-10-01 17:22:46 -07001445 branch.merge = R_HEADS + self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001446 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001447
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001448 if head.startswith(R_HEADS):
1449 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001450 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001451 except KeyError:
1452 head = None
1453
1454 if revid and head and revid == head:
1455 ref = os.path.join(self.gitdir, R_HEADS + name)
1456 try:
1457 os.makedirs(os.path.dirname(ref))
1458 except OSError:
1459 pass
1460 _lwrite(ref, '%s\n' % revid)
1461 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1462 'ref: %s%s\n' % (R_HEADS, name))
1463 branch.Save()
1464 return True
1465
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001466 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001467 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001468 capture_stdout=True,
1469 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001470 branch.Save()
1471 return True
1472 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001473
Wink Saville02d79452009-04-10 13:01:24 -07001474 def CheckoutBranch(self, name):
1475 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001476
1477 Args:
1478 name: The name of the branch to checkout.
1479
1480 Returns:
1481 True if the checkout succeeded; False if it didn't; None if the branch
1482 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001483 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001484 rev = R_HEADS + name
1485 head = self.work_git.GetHead()
1486 if head == rev:
1487 # Already on the branch
1488 #
1489 return True
Wink Saville02d79452009-04-10 13:01:24 -07001490
David Pursehouse8a68ff92012-09-24 12:15:13 +09001491 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001492 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001493 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001494 except KeyError:
1495 # Branch does not exist in this project
1496 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001497 return None
Wink Saville02d79452009-04-10 13:01:24 -07001498
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001499 if head.startswith(R_HEADS):
1500 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001501 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001502 except KeyError:
1503 head = None
1504
1505 if head == revid:
1506 # Same revision; just update HEAD to point to the new
1507 # target branch, but otherwise take no other action.
1508 #
1509 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1510 'ref: %s%s\n' % (R_HEADS, name))
1511 return True
1512
1513 return GitCommand(self,
1514 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001515 capture_stdout=True,
1516 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001517
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001518 def AbandonBranch(self, name):
1519 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001520
1521 Args:
1522 name: The name of the branch to abandon.
1523
1524 Returns:
1525 True if the abandon succeeded; False if it didn't; None if the branch
1526 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001527 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001528 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001529 all_refs = self.bare_ref.all
1530 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001531 # Doesn't exist
1532 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001533
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001534 head = self.work_git.GetHead()
1535 if head == rev:
1536 # We can't destroy the branch while we are sitting
1537 # on it. Switch to a detached HEAD.
1538 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001539 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001540
David Pursehouse8a68ff92012-09-24 12:15:13 +09001541 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001542 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001543 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1544 '%s\n' % revid)
1545 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001546 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001547
1548 return GitCommand(self,
1549 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001550 capture_stdout=True,
1551 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001552
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001553 def PruneHeads(self):
1554 """Prune any topic branches already merged into upstream.
1555 """
1556 cb = self.CurrentBranch
1557 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001558 left = self._allrefs
1559 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001560 if name.startswith(R_HEADS):
1561 name = name[len(R_HEADS):]
1562 if cb is None or name != cb:
1563 kill.append(name)
1564
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001565 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001566 if cb is not None \
1567 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001568 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001569 self.work_git.DetachHead(HEAD)
1570 kill.append(cb)
1571
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001572 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001573 old = self.bare_git.GetHead()
1574 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001575 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1576
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001577 try:
1578 self.bare_git.DetachHead(rev)
1579
1580 b = ['branch', '-d']
1581 b.extend(kill)
1582 b = GitCommand(self, b, bare=True,
1583 capture_stdout=True,
1584 capture_stderr=True)
1585 b.Wait()
1586 finally:
1587 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001588 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001589
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001590 for branch in kill:
1591 if (R_HEADS + branch) not in left:
1592 self.CleanPublishedCache()
1593 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001594
1595 if cb and cb not in kill:
1596 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001597 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001598
1599 kept = []
1600 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001601 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001602 branch = self.GetBranch(branch)
1603 base = branch.LocalMerge
1604 if not base:
1605 base = rev
1606 kept.append(ReviewableBranch(self, branch, base))
1607 return kept
1608
1609
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001610## Submodule Management ##
1611
1612 def GetRegisteredSubprojects(self):
1613 result = []
1614 def rec(subprojects):
1615 if not subprojects:
1616 return
1617 result.extend(subprojects)
1618 for p in subprojects:
1619 rec(p.subprojects)
1620 rec(self.subprojects)
1621 return result
1622
1623 def _GetSubmodules(self):
1624 # Unfortunately we cannot call `git submodule status --recursive` here
1625 # because the working tree might not exist yet, and it cannot be used
1626 # without a working tree in its current implementation.
1627
1628 def get_submodules(gitdir, rev):
1629 # Parse .gitmodules for submodule sub_paths and sub_urls
1630 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1631 if not sub_paths:
1632 return []
1633 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1634 # revision of submodule repository
1635 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1636 submodules = []
1637 for sub_path, sub_url in zip(sub_paths, sub_urls):
1638 try:
1639 sub_rev = sub_revs[sub_path]
1640 except KeyError:
1641 # Ignore non-exist submodules
1642 continue
1643 submodules.append((sub_rev, sub_path, sub_url))
1644 return submodules
1645
1646 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1647 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1648 def parse_gitmodules(gitdir, rev):
1649 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1650 try:
Anthony King7bdac712014-07-16 12:56:40 +01001651 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1652 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001653 except GitError:
1654 return [], []
1655 if p.Wait() != 0:
1656 return [], []
1657
1658 gitmodules_lines = []
1659 fd, temp_gitmodules_path = tempfile.mkstemp()
1660 try:
1661 os.write(fd, p.stdout)
1662 os.close(fd)
1663 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001664 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1665 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001666 if p.Wait() != 0:
1667 return [], []
1668 gitmodules_lines = p.stdout.split('\n')
1669 except GitError:
1670 return [], []
1671 finally:
1672 os.remove(temp_gitmodules_path)
1673
1674 names = set()
1675 paths = {}
1676 urls = {}
1677 for line in gitmodules_lines:
1678 if not line:
1679 continue
1680 m = re_path.match(line)
1681 if m:
1682 names.add(m.group(1))
1683 paths[m.group(1)] = m.group(2)
1684 continue
1685 m = re_url.match(line)
1686 if m:
1687 names.add(m.group(1))
1688 urls[m.group(1)] = m.group(2)
1689 continue
1690 names = sorted(names)
1691 return ([paths.get(name, '') for name in names],
1692 [urls.get(name, '') for name in names])
1693
1694 def git_ls_tree(gitdir, rev, paths):
1695 cmd = ['ls-tree', rev, '--']
1696 cmd.extend(paths)
1697 try:
Anthony King7bdac712014-07-16 12:56:40 +01001698 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1699 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001700 except GitError:
1701 return []
1702 if p.Wait() != 0:
1703 return []
1704 objects = {}
1705 for line in p.stdout.split('\n'):
1706 if not line.strip():
1707 continue
1708 object_rev, object_path = line.split()[2:4]
1709 objects[object_path] = object_rev
1710 return objects
1711
1712 try:
1713 rev = self.GetRevisionId()
1714 except GitError:
1715 return []
1716 return get_submodules(self.gitdir, rev)
1717
1718 def GetDerivedSubprojects(self):
1719 result = []
1720 if not self.Exists:
1721 # If git repo does not exist yet, querying its submodules will
1722 # mess up its states; so return here.
1723 return result
1724 for rev, path, url in self._GetSubmodules():
1725 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001726 relpath, worktree, gitdir, objdir = \
1727 self.manifest.GetSubprojectPaths(self, name, path)
1728 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001729 if project:
1730 result.extend(project.GetDerivedSubprojects())
1731 continue
David James8d201162013-10-11 17:03:19 -07001732
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001733 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001734 url=url,
1735 review=self.remote.review,
1736 revision=self.remote.revision)
1737 subproject = Project(manifest=self.manifest,
1738 name=name,
1739 remote=remote,
1740 gitdir=gitdir,
1741 objdir=objdir,
1742 worktree=worktree,
1743 relpath=relpath,
1744 revisionExpr=self.revisionExpr,
1745 revisionId=rev,
1746 rebase=self.rebase,
1747 groups=self.groups,
1748 sync_c=self.sync_c,
1749 sync_s=self.sync_s,
1750 parent=self,
1751 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001752 result.append(subproject)
1753 result.extend(subproject.GetDerivedSubprojects())
1754 return result
1755
1756
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001757## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001758 def _CheckForSha1(self):
1759 try:
1760 # if revision (sha or tag) is not present then following function
1761 # throws an error.
1762 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1763 return True
1764 except GitError:
1765 # There is no such persistent revision. We have to fetch it.
1766 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001767
Julien Campergue335f5ef2013-10-16 11:02:35 +02001768 def _FetchArchive(self, tarpath, cwd=None):
1769 cmd = ['archive', '-v', '-o', tarpath]
1770 cmd.append('--remote=%s' % self.remote.url)
1771 cmd.append('--prefix=%s/' % self.relpath)
1772 cmd.append(self.revisionExpr)
1773
1774 command = GitCommand(self, cmd, cwd=cwd,
1775 capture_stdout=True,
1776 capture_stderr=True)
1777
1778 if command.Wait() != 0:
1779 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1780
Conley Owens80b87fe2014-05-09 17:13:44 -07001781
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001782 def _RemoteFetch(self, name=None,
1783 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001784 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001785 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001786 alt_dir=None,
1787 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001788
1789 is_sha1 = False
1790 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001791 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001792
David Pursehouse9bc422f2014-04-15 10:28:56 +09001793 # The depth should not be used when fetching to a mirror because
1794 # it will result in a shallow repository that cannot be cloned or
1795 # fetched from.
1796 if not self.manifest.IsMirror:
1797 if self.clone_depth:
1798 depth = self.clone_depth
1799 else:
1800 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001801 # The repo project should never be synced with partial depth
1802 if self.relpath == '.repo/repo':
1803 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001804
Shawn Pearce69e04d82014-01-29 12:48:54 -08001805 if depth:
1806 current_branch_only = True
1807
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001808 if ID_RE.match(self.revisionExpr) is not None:
1809 is_sha1 = True
1810
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001811 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001812 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001813 # this is a tag and its sha1 value should never change
1814 tag_name = self.revisionExpr[len(R_TAGS):]
1815
1816 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001817 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001818 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001819 if is_sha1 and not depth:
1820 # When syncing a specific commit and --depth is not set:
1821 # * if upstream is explicitly specified and is not a sha1, fetch only
1822 # upstream as users expect only upstream to be fetch.
1823 # Note: The commit might not be in upstream in which case the sync
1824 # will fail.
1825 # * otherwise, fetch all branches to make sure we end up with the
1826 # specific commit.
1827 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001828
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001829 if not name:
1830 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001831
1832 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001833 remote = self.GetRemote(name)
1834 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001835 ssh_proxy = True
1836
Shawn O. Pearce88443382010-10-08 10:02:09 +02001837 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001838 if alt_dir and 'objects' == os.path.basename(alt_dir):
1839 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001840 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1841 remote = self.GetRemote(name)
1842
David Pursehouse8a68ff92012-09-24 12:15:13 +09001843 all_refs = self.bare_ref.all
1844 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001845 tmp = set()
1846
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301847 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001848 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001849 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001850 all_refs[r] = ref_id
1851 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001852 continue
1853
David Pursehouse8a68ff92012-09-24 12:15:13 +09001854 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001855 continue
1856
David Pursehouse8a68ff92012-09-24 12:15:13 +09001857 r = 'refs/_alt/%s' % ref_id
1858 all_refs[r] = ref_id
1859 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001860 tmp.add(r)
1861
Shawn O. Pearce88443382010-10-08 10:02:09 +02001862 tmp_packed = ''
1863 old_packed = ''
1864
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301865 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001866 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001867 tmp_packed += line
1868 if r not in tmp:
1869 old_packed += line
1870
1871 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001872 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001873 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001874
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001875 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001876
Conley Owensf97e8382015-01-21 11:12:46 -08001877 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001878 cmd.append('--depth=%s' % depth)
1879
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001880 if quiet:
1881 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001882 if not self.worktree:
1883 cmd.append('--update-head-ok')
David Pursehouseb4d43b92015-04-28 18:28:12 +09001884 if self.manifest.IsMirror:
1885 cmd.append('--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001886 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001887
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001888 # If using depth then we should not get all the tags since they may
1889 # be outside of the depth.
1890 if no_tags or depth:
1891 cmd.append('--no-tags')
1892 else:
1893 cmd.append('--tags')
1894
Conley Owens80b87fe2014-05-09 17:13:44 -07001895 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001896 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001897 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001898 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001899 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001900 spec.append('tag')
1901 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001902
David Pursehouse403b64e2015-04-27 10:41:33 +09001903 if not self.manifest.IsMirror:
1904 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06001905 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001906 # Shallow checkout of a specific commit, fetch from that commit and not
1907 # the heads only as the commit might be deeper in the history.
1908 spec.append(branch)
1909 else:
1910 if is_sha1:
1911 branch = self.upstream
1912 if branch is not None and branch.strip():
1913 if not branch.startswith('refs/'):
1914 branch = R_HEADS + branch
1915 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001916 cmd.extend(spec)
1917
1918 shallowfetch = self.config.GetString('repo.shallowfetch')
1919 if shallowfetch and shallowfetch != ' '.join(spec):
Anthony King23ff7df2015-03-28 19:42:39 +00001920 GitCommand(self, ['fetch', '--depth=2147483647', name]
1921 + shallowfetch.split(),
Conley Owens80b87fe2014-05-09 17:13:44 -07001922 bare=True, ssh_proxy=ssh_proxy).Wait()
1923 if depth:
Anthony King7bdac712014-07-16 12:56:40 +01001924 self.config.SetString('repo.shallowfetch', ' '.join(spec))
Conley Owens80b87fe2014-05-09 17:13:44 -07001925 else:
Anthony King7bdac712014-07-16 12:56:40 +01001926 self.config.SetString('repo.shallowfetch', None)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001927
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001928 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001929 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001930 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001931 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001932 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001933 ok = True
1934 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001935 # If needed, run the 'git remote prune' the first time through the loop
1936 elif (not _i and
1937 "error:" in gitcmd.stderr and
1938 "git remote prune" in gitcmd.stderr):
1939 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001940 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001941 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001942 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001943 break
1944 continue
Brian Harring14a66742012-09-28 20:21:57 -07001945 elif current_branch_only and is_sha1 and ret == 128:
1946 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1947 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1948 # abort the optimization attempt and do a full sync.
1949 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001950 elif ret < 0:
1951 # Git died with a signal, exit immediately
1952 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001953 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001954
1955 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001956 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001957 if old_packed != '':
1958 _lwrite(packed_refs, old_packed)
1959 else:
1960 os.remove(packed_refs)
1961 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001962
1963 if is_sha1 and current_branch_only and self.upstream:
1964 # We just synced the upstream given branch; verify we
1965 # got what we wanted, else trigger a second run of all
1966 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001967 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06001968 if not depth:
1969 # Avoid infinite recursion when depth is True (since depth implies
1970 # current_branch_only)
1971 return self._RemoteFetch(name=name, current_branch_only=False,
1972 initial=False, quiet=quiet, alt_dir=alt_dir)
1973 if self.clone_depth:
1974 self.clone_depth = None
1975 return self._RemoteFetch(name=name, current_branch_only=current_branch_only,
1976 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07001977
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001978 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001979
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001980 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001981 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001982 return False
1983
1984 remote = self.GetRemote(self.remote.name)
1985 bundle_url = remote.url + '/clone.bundle'
1986 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001987 if GetSchemeFromUrl(bundle_url) not in (
1988 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001989 return False
1990
1991 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1992 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1993
1994 exist_dst = os.path.exists(bundle_dst)
1995 exist_tmp = os.path.exists(bundle_tmp)
1996
1997 if not initial and not exist_dst and not exist_tmp:
1998 return False
1999
2000 if not exist_dst:
2001 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2002 if not exist_dst:
2003 return False
2004
2005 cmd = ['fetch']
2006 if quiet:
2007 cmd.append('--quiet')
2008 if not self.worktree:
2009 cmd.append('--update-head-ok')
2010 cmd.append(bundle_dst)
2011 for f in remote.fetch:
2012 cmd.append(str(f))
2013 cmd.append('refs/tags/*:refs/tags/*')
2014
2015 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002016 if os.path.exists(bundle_dst):
2017 os.remove(bundle_dst)
2018 if os.path.exists(bundle_tmp):
2019 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002020 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002021
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002022 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002023 if os.path.exists(dstPath):
2024 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002025
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002026 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002027 if quiet:
2028 cmd += ['--silent']
2029 if os.path.exists(tmpPath):
2030 size = os.stat(tmpPath).st_size
2031 if size >= 1024:
2032 cmd += ['--continue-at', '%d' % (size,)]
2033 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002034 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002035 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2036 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz497bde42015-01-02 13:58:05 -08002037 with self._GetBundleCookieFile(srcUrl, quiet) as cookiefile:
Dave Borowitz137d0132015-01-02 11:12:54 -08002038 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002039 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002040 if srcUrl.startswith('persistent-'):
2041 srcUrl = srcUrl[len('persistent-'):]
2042 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002043
Dave Borowitz137d0132015-01-02 11:12:54 -08002044 if IsTrace():
2045 Trace('%s', ' '.join(cmd))
2046 try:
2047 proc = subprocess.Popen(cmd)
2048 except OSError:
2049 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002050
Dave Borowitz137d0132015-01-02 11:12:54 -08002051 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002052
Dave Borowitz137d0132015-01-02 11:12:54 -08002053 if curlret == 22:
2054 # From curl man page:
2055 # 22: HTTP page not retrieved. The requested url was not found or
2056 # returned another error with the HTTP error code being 400 or above.
2057 # This return code only appears if -f, --fail is used.
2058 if not quiet:
2059 print("Server does not provide clone.bundle; ignoring.",
2060 file=sys.stderr)
2061 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002062
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002063 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002064 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002065 os.rename(tmpPath, dstPath)
2066 return True
2067 else:
2068 os.remove(tmpPath)
2069 return False
2070 else:
2071 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002072
Kris Giesingc8d882a2014-12-23 13:02:32 -08002073 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002074 try:
2075 with open(path) as f:
2076 if f.read(16) == '# v2 git bundle\n':
2077 return True
2078 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002079 if not quiet:
2080 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002081 return False
2082 except OSError:
2083 return False
2084
Dave Borowitz137d0132015-01-02 11:12:54 -08002085 @contextlib.contextmanager
Dave Borowitz497bde42015-01-02 13:58:05 -08002086 def _GetBundleCookieFile(self, url, quiet):
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002087 if url.startswith('persistent-'):
2088 try:
2089 p = subprocess.Popen(
2090 ['git-remote-persistent-https', '-print_config', url],
2091 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2092 stderr=subprocess.PIPE)
Dave Borowitz137d0132015-01-02 11:12:54 -08002093 try:
2094 prefix = 'http.cookiefile='
2095 cookiefile = None
2096 for line in p.stdout:
2097 line = line.strip()
2098 if line.startswith(prefix):
2099 cookiefile = line[len(prefix):]
2100 break
2101 # Leave subprocess open, as cookie file may be transient.
2102 if cookiefile:
2103 yield cookiefile
2104 return
2105 finally:
2106 p.stdin.close()
2107 if p.wait():
2108 err_msg = p.stderr.read()
2109 if ' -print_config' in err_msg:
2110 pass # Persistent proxy doesn't support -print_config.
Dave Borowitz497bde42015-01-02 13:58:05 -08002111 elif not quiet:
Dave Borowitz137d0132015-01-02 11:12:54 -08002112 print(err_msg, file=sys.stderr)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002113 except OSError as e:
2114 if e.errno == errno.ENOENT:
2115 pass # No persistent proxy.
2116 raise
Dave Borowitz137d0132015-01-02 11:12:54 -08002117 yield GitConfig.ForUser().GetString('http.cookiefile')
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002118
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002119 def _Checkout(self, rev, quiet=False):
2120 cmd = ['checkout']
2121 if quiet:
2122 cmd.append('-q')
2123 cmd.append(rev)
2124 cmd.append('--')
2125 if GitCommand(self, cmd).Wait() != 0:
2126 if self._allrefs:
2127 raise GitError('%s checkout %s ' % (self.name, rev))
2128
Anthony King7bdac712014-07-16 12:56:40 +01002129 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002130 cmd = ['cherry-pick']
2131 cmd.append(rev)
2132 cmd.append('--')
2133 if GitCommand(self, cmd).Wait() != 0:
2134 if self._allrefs:
2135 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2136
Anthony King7bdac712014-07-16 12:56:40 +01002137 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002138 cmd = ['revert']
2139 cmd.append('--no-edit')
2140 cmd.append(rev)
2141 cmd.append('--')
2142 if GitCommand(self, cmd).Wait() != 0:
2143 if self._allrefs:
2144 raise GitError('%s revert %s ' % (self.name, rev))
2145
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002146 def _ResetHard(self, rev, quiet=True):
2147 cmd = ['reset', '--hard']
2148 if quiet:
2149 cmd.append('-q')
2150 cmd.append(rev)
2151 if GitCommand(self, cmd).Wait() != 0:
2152 raise GitError('%s reset --hard %s ' % (self.name, rev))
2153
Anthony King7bdac712014-07-16 12:56:40 +01002154 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002155 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002156 if onto is not None:
2157 cmd.extend(['--onto', onto])
2158 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002159 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002160 raise GitError('%s rebase %s ' % (self.name, upstream))
2161
Pierre Tardy3d125942012-05-04 12:18:12 +02002162 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002163 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002164 if ffonly:
2165 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002166 if GitCommand(self, cmd).Wait() != 0:
2167 raise GitError('%s merge %s ' % (self.name, head))
2168
Jonathan Nieder93719792015-03-17 11:29:58 -07002169 def _InitGitDir(self, mirror_git=None):
Kevin Degi384b3c52014-10-16 16:02:58 -06002170 init_git_dir = not os.path.exists(self.gitdir)
2171 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002172 try:
2173 # Initialize the bare repository, which contains all of the objects.
2174 if init_obj_dir:
2175 os.makedirs(self.objdir)
2176 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002177
Kevin Degib1a07b82015-07-27 13:33:43 -06002178 # If we have a separate directory to hold refs, initialize it as well.
2179 if self.objdir != self.gitdir:
2180 if init_git_dir:
2181 os.makedirs(self.gitdir)
2182
2183 if init_obj_dir or init_git_dir:
2184 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2185 copy_all=True)
2186 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2187
Kevin Degi384b3c52014-10-16 16:02:58 -06002188 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002189 mp = self.manifest.manifestProject
2190 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002191
Kevin Degib1a07b82015-07-27 13:33:43 -06002192 if ref_dir or mirror_git:
2193 if not mirror_git:
2194 mirror_git = os.path.join(ref_dir, self.name + '.git')
2195 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2196 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002197
Kevin Degib1a07b82015-07-27 13:33:43 -06002198 if os.path.exists(mirror_git):
2199 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002200
Kevin Degib1a07b82015-07-27 13:33:43 -06002201 elif os.path.exists(repo_git):
2202 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002203
Kevin Degib1a07b82015-07-27 13:33:43 -06002204 else:
2205 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002206
Kevin Degib1a07b82015-07-27 13:33:43 -06002207 if ref_dir:
2208 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2209 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002210
Kevin Degib1a07b82015-07-27 13:33:43 -06002211 self._UpdateHooks()
2212
2213 m = self.manifest.manifestProject.config
2214 for key in ['user.name', 'user.email']:
2215 if m.Has(key, include_defaults=False):
2216 self.config.SetString(key, m.GetString(key))
2217 if self.manifest.IsMirror:
2218 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002219 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002220 self.config.SetString('core.bare', None)
2221 except Exception:
2222 if init_obj_dir and os.path.exists(self.objdir):
2223 shutil.rmtree(self.objdir)
2224 if init_git_dir and os.path.exists(self.gitdir):
2225 shutil.rmtree(self.gitdir)
2226 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002227
Jimmie Westera0444582012-10-24 13:44:42 +02002228 def _UpdateHooks(self):
2229 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002230 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002231
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002232 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002233 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002234 if not os.path.exists(hooks):
2235 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002236 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002237 name = os.path.basename(stock_hook)
2238
Victor Boivie65e0f352011-04-18 11:23:29 +02002239 if name in ('commit-msg',) and not self.remote.review \
2240 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002241 # Don't install a Gerrit Code Review hook if this
2242 # project does not appear to use it for reviews.
2243 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002244 # Since the manifest project is one of those, but also
2245 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002246 continue
2247
2248 dst = os.path.join(hooks, name)
2249 if os.path.islink(dst):
2250 continue
2251 if os.path.exists(dst):
2252 if filecmp.cmp(stock_hook, dst, shallow=False):
2253 os.remove(dst)
2254 else:
2255 _error("%s: Not replacing %s hook", self.relpath, name)
2256 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002257 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002258 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002259 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002260 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002261 raise GitError('filesystem must support symlinks')
2262 else:
2263 raise
2264
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002265 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002266 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002267 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002268 remote.url = self.remote.url
2269 remote.review = self.remote.review
2270 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002271
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002272 if self.worktree:
2273 remote.ResetFetch(mirror=False)
2274 else:
2275 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002276 remote.Save()
2277
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002278 def _InitMRef(self):
2279 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002280 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002281
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002282 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002283 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002284
2285 def _InitAnyMRef(self, ref):
2286 cur = self.bare_ref.symref(ref)
2287
2288 if self.revisionId:
2289 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2290 msg = 'manifest set to %s' % self.revisionId
2291 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002292 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002293 else:
2294 remote = self.GetRemote(self.remote.name)
2295 dst = remote.ToLocal(self.revisionExpr)
2296 if cur != dst:
2297 msg = 'manifest set to %s' % self.revisionExpr
2298 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002299
Kevin Degi384b3c52014-10-16 16:02:58 -06002300 def _CheckDirReference(self, srcdir, destdir, share_refs):
2301 symlink_files = self.shareable_files
2302 symlink_dirs = self.shareable_dirs
2303 if share_refs:
2304 symlink_files += self.working_tree_files
2305 symlink_dirs += self.working_tree_dirs
2306 to_symlink = symlink_files + symlink_dirs
2307 for name in set(to_symlink):
2308 dst = os.path.realpath(os.path.join(destdir, name))
2309 if os.path.lexists(dst):
2310 src = os.path.realpath(os.path.join(srcdir, name))
2311 # Fail if the links are pointing to the wrong place
2312 if src != dst:
2313 raise GitError('cannot overwrite a local work tree')
2314
David James8d201162013-10-11 17:03:19 -07002315 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2316 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2317
2318 Args:
2319 gitdir: The bare git repository. Must already be initialized.
2320 dotgit: The repository you would like to initialize.
2321 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2322 Only one work tree can store refs under a given |gitdir|.
2323 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2324 This saves you the effort of initializing |dotgit| yourself.
2325 """
Kevin Degi384b3c52014-10-16 16:02:58 -06002326 symlink_files = self.shareable_files
2327 symlink_dirs = self.shareable_dirs
David James8d201162013-10-11 17:03:19 -07002328 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002329 symlink_files += self.working_tree_files
2330 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002331 to_symlink = symlink_files + symlink_dirs
2332
2333 to_copy = []
2334 if copy_all:
2335 to_copy = os.listdir(gitdir)
2336
2337 for name in set(to_copy).union(to_symlink):
2338 try:
2339 src = os.path.realpath(os.path.join(gitdir, name))
2340 dst = os.path.realpath(os.path.join(dotgit, name))
2341
Kevin Degi384b3c52014-10-16 16:02:58 -06002342 if os.path.lexists(dst):
2343 continue
David James8d201162013-10-11 17:03:19 -07002344
2345 # If the source dir doesn't exist, create an empty dir.
2346 if name in symlink_dirs and not os.path.lexists(src):
2347 os.makedirs(src)
2348
Conley Owens80b87fe2014-05-09 17:13:44 -07002349 # If the source file doesn't exist, ensure the destination
2350 # file doesn't either.
2351 if name in symlink_files and not os.path.lexists(src):
2352 try:
2353 os.remove(dst)
2354 except OSError:
2355 pass
2356
David James8d201162013-10-11 17:03:19 -07002357 if name in to_symlink:
2358 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2359 elif copy_all and not os.path.islink(dst):
2360 if os.path.isdir(src):
2361 shutil.copytree(src, dst)
2362 elif os.path.isfile(src):
2363 shutil.copy(src, dst)
2364 except OSError as e:
2365 if e.errno == errno.EPERM:
2366 raise GitError('filesystem must support symlinks')
2367 else:
2368 raise
2369
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002370 def _InitWorkTree(self):
2371 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002372 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002373 try:
2374 if init_dotgit:
2375 os.makedirs(dotgit)
2376 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2377 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002378
Kevin Degib1a07b82015-07-27 13:33:43 -06002379 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
Kevin Degi384b3c52014-10-16 16:02:58 -06002380
Kevin Degib1a07b82015-07-27 13:33:43 -06002381 if init_dotgit:
2382 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002383
Kevin Degib1a07b82015-07-27 13:33:43 -06002384 cmd = ['read-tree', '--reset', '-u']
2385 cmd.append('-v')
2386 cmd.append(HEAD)
2387 if GitCommand(self, cmd).Wait() != 0:
2388 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002389
Kevin Degib1a07b82015-07-27 13:33:43 -06002390 self._CopyAndLinkFiles()
2391 except Exception:
2392 if init_dotgit:
2393 shutil.rmtree(dotgit)
2394 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002395
2396 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002397 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002398
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002399 def _revlist(self, *args, **kw):
2400 a = []
2401 a.extend(args)
2402 a.append('--')
2403 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002404
2405 @property
2406 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002407 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002408
Julien Camperguedd654222014-01-09 16:21:37 +01002409 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2410 """Get logs between two revisions of this project."""
2411 comp = '..'
2412 if rev1:
2413 revs = [rev1]
2414 if rev2:
2415 revs.extend([comp, rev2])
2416 cmd = ['log', ''.join(revs)]
2417 out = DiffColoring(self.config)
2418 if out.is_on and color:
2419 cmd.append('--color')
2420 if oneline:
2421 cmd.append('--oneline')
2422
2423 try:
2424 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2425 if log.Wait() == 0:
2426 return log.stdout
2427 except GitError:
2428 # worktree may not exist if groups changed for example. In that case,
2429 # try in gitdir instead.
2430 if not os.path.exists(self.worktree):
2431 return self.bare_git.log(*cmd[1:])
2432 else:
2433 raise
2434 return None
2435
2436 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2437 """Get the list of logs from this revision to given revisionId"""
2438 logs = {}
2439 selfId = self.GetRevisionId(self._allrefs)
2440 toId = toProject.GetRevisionId(toProject._allrefs)
2441
2442 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2443 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2444 return logs
2445
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002446 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002447 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002448 self._project = project
2449 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002450 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002451
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002452 def LsOthers(self):
2453 p = GitCommand(self._project,
2454 ['ls-files',
2455 '-z',
2456 '--others',
2457 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002458 bare=False,
David James8d201162013-10-11 17:03:19 -07002459 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002460 capture_stdout=True,
2461 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002462 if p.Wait() == 0:
2463 out = p.stdout
2464 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002465 return out[:-1].split('\0') # pylint: disable=W1401
2466 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002467 return []
2468
2469 def DiffZ(self, name, *args):
2470 cmd = [name]
2471 cmd.append('-z')
2472 cmd.extend(args)
2473 p = GitCommand(self._project,
2474 cmd,
David James8d201162013-10-11 17:03:19 -07002475 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002476 bare=False,
2477 capture_stdout=True,
2478 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002479 try:
2480 out = p.process.stdout.read()
2481 r = {}
2482 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002483 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002484 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002485 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002486 info = next(out)
2487 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002488 except StopIteration:
2489 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002490
2491 class _Info(object):
2492 def __init__(self, path, omode, nmode, oid, nid, state):
2493 self.path = path
2494 self.src_path = None
2495 self.old_mode = omode
2496 self.new_mode = nmode
2497 self.old_id = oid
2498 self.new_id = nid
2499
2500 if len(state) == 1:
2501 self.status = state
2502 self.level = None
2503 else:
2504 self.status = state[:1]
2505 self.level = state[1:]
2506 while self.level.startswith('0'):
2507 self.level = self.level[1:]
2508
2509 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002510 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002511 if info.status in ('R', 'C'):
2512 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002513 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002514 r[info.path] = info
2515 return r
2516 finally:
2517 p.Wait()
2518
2519 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002520 if self._bare:
2521 path = os.path.join(self._project.gitdir, HEAD)
2522 else:
2523 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002524 try:
2525 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002526 except IOError as e:
2527 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002528 try:
2529 line = fd.read()
2530 finally:
2531 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302532 try:
2533 line = line.decode()
2534 except AttributeError:
2535 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002536 if line.startswith('ref: '):
2537 return line[5:-1]
2538 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002539
2540 def SetHead(self, ref, message=None):
2541 cmdv = []
2542 if message is not None:
2543 cmdv.extend(['-m', message])
2544 cmdv.append(HEAD)
2545 cmdv.append(ref)
2546 self.symbolic_ref(*cmdv)
2547
2548 def DetachHead(self, new, message=None):
2549 cmdv = ['--no-deref']
2550 if message is not None:
2551 cmdv.extend(['-m', message])
2552 cmdv.append(HEAD)
2553 cmdv.append(new)
2554 self.update_ref(*cmdv)
2555
2556 def UpdateRef(self, name, new, old=None,
2557 message=None,
2558 detach=False):
2559 cmdv = []
2560 if message is not None:
2561 cmdv.extend(['-m', message])
2562 if detach:
2563 cmdv.append('--no-deref')
2564 cmdv.append(name)
2565 cmdv.append(new)
2566 if old is not None:
2567 cmdv.append(old)
2568 self.update_ref(*cmdv)
2569
2570 def DeleteRef(self, name, old=None):
2571 if not old:
2572 old = self.rev_parse(name)
2573 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002574 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002575
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002576 def rev_list(self, *args, **kw):
2577 if 'format' in kw:
2578 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2579 else:
2580 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002581 cmdv.extend(args)
2582 p = GitCommand(self._project,
2583 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002584 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002585 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002586 capture_stdout=True,
2587 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002588 r = []
2589 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002590 if line[-1] == '\n':
2591 line = line[:-1]
2592 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002593 if p.Wait() != 0:
2594 raise GitError('%s rev-list %s: %s' % (
2595 self._project.name,
2596 str(args),
2597 p.stderr))
2598 return r
2599
2600 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002601 """Allow arbitrary git commands using pythonic syntax.
2602
2603 This allows you to do things like:
2604 git_obj.rev_parse('HEAD')
2605
2606 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2607 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002608 Any other positional arguments will be passed to the git command, and the
2609 following keyword arguments are supported:
2610 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002611
2612 Args:
2613 name: The name of the git command to call. Any '_' characters will
2614 be replaced with '-'.
2615
2616 Returns:
2617 A callable object that will try to call git with the named command.
2618 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002619 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002620 def runner(*args, **kwargs):
2621 cmdv = []
2622 config = kwargs.pop('config', None)
2623 for k in kwargs:
2624 raise TypeError('%s() got an unexpected keyword argument %r'
2625 % (name, k))
2626 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002627 if not git_require((1, 7, 2)):
2628 raise ValueError('cannot set config on command line for %s()'
2629 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302630 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002631 cmdv.append('-c')
2632 cmdv.append('%s=%s' % (k, v))
2633 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002634 cmdv.extend(args)
2635 p = GitCommand(self._project,
2636 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002637 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002638 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002639 capture_stdout=True,
2640 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002641 if p.Wait() != 0:
2642 raise GitError('%s %s: %s' % (
2643 self._project.name,
2644 name,
2645 p.stderr))
2646 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302647 try:
Conley Owensedd01512013-09-26 12:59:58 -07002648 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302649 except AttributeError:
2650 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002651 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2652 return r[:-1]
2653 return r
2654 return runner
2655
2656
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002657class _PriorSyncFailedError(Exception):
2658 def __str__(self):
2659 return 'prior sync failed; rebase still in progress'
2660
2661class _DirtyError(Exception):
2662 def __str__(self):
2663 return 'contains uncommitted changes'
2664
2665class _InfoMessage(object):
2666 def __init__(self, project, text):
2667 self.project = project
2668 self.text = text
2669
2670 def Print(self, syncbuf):
2671 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2672 syncbuf.out.nl()
2673
2674class _Failure(object):
2675 def __init__(self, project, why):
2676 self.project = project
2677 self.why = why
2678
2679 def Print(self, syncbuf):
2680 syncbuf.out.fail('error: %s/: %s',
2681 self.project.relpath,
2682 str(self.why))
2683 syncbuf.out.nl()
2684
2685class _Later(object):
2686 def __init__(self, project, action):
2687 self.project = project
2688 self.action = action
2689
2690 def Run(self, syncbuf):
2691 out = syncbuf.out
2692 out.project('project %s/', self.project.relpath)
2693 out.nl()
2694 try:
2695 self.action()
2696 out.nl()
2697 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002698 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002699 out.nl()
2700 return False
2701
2702class _SyncColoring(Coloring):
2703 def __init__(self, config):
2704 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002705 self.project = self.printer('header', attr='bold')
2706 self.info = self.printer('info')
2707 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002708
2709class SyncBuffer(object):
2710 def __init__(self, config, detach_head=False):
2711 self._messages = []
2712 self._failures = []
2713 self._later_queue1 = []
2714 self._later_queue2 = []
2715
2716 self.out = _SyncColoring(config)
2717 self.out.redirect(sys.stderr)
2718
2719 self.detach_head = detach_head
2720 self.clean = True
2721
2722 def info(self, project, fmt, *args):
2723 self._messages.append(_InfoMessage(project, fmt % args))
2724
2725 def fail(self, project, err=None):
2726 self._failures.append(_Failure(project, err))
2727 self.clean = False
2728
2729 def later1(self, project, what):
2730 self._later_queue1.append(_Later(project, what))
2731
2732 def later2(self, project, what):
2733 self._later_queue2.append(_Later(project, what))
2734
2735 def Finish(self):
2736 self._PrintMessages()
2737 self._RunLater()
2738 self._PrintMessages()
2739 return self.clean
2740
2741 def _RunLater(self):
2742 for q in ['_later_queue1', '_later_queue2']:
2743 if not self._RunQueue(q):
2744 return
2745
2746 def _RunQueue(self, queue):
2747 for m in getattr(self, queue):
2748 if not m.Run(self):
2749 self.clean = False
2750 return False
2751 setattr(self, queue, [])
2752 return True
2753
2754 def _PrintMessages(self):
2755 for m in self._messages:
2756 m.Print(self)
2757 for m in self._failures:
2758 m.Print(self)
2759
2760 self._messages = []
2761 self._failures = []
2762
2763
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002764class MetaProject(Project):
2765 """A special project housed under .repo.
2766 """
2767 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002768 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002769 manifest=manifest,
2770 name=name,
2771 gitdir=gitdir,
2772 objdir=gitdir,
2773 worktree=worktree,
2774 remote=RemoteSpec('origin'),
2775 relpath='.repo/%s' % name,
2776 revisionExpr='refs/heads/master',
2777 revisionId=None,
2778 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002779
2780 def PreSync(self):
2781 if self.Exists:
2782 cb = self.CurrentBranch
2783 if cb:
2784 base = self.GetBranch(cb).merge
2785 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002786 self.revisionExpr = base
2787 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002788
Anthony King7bdac712014-07-16 12:56:40 +01002789 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002790 """ Prepare MetaProject for manifest branch switch
2791 """
2792
2793 # detach and delete manifest branch, allowing a new
2794 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002795 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002796 self.Sync_LocalHalf(syncbuf)
2797 syncbuf.Finish()
2798
2799 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002800 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002801 capture_stdout=True,
2802 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002803
2804
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002805 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002806 def LastFetch(self):
2807 try:
2808 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2809 return os.path.getmtime(fh)
2810 except OSError:
2811 return 0
2812
2813 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002814 def HasChanges(self):
2815 """Has the remote received new commits not yet checked out?
2816 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002817 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002818 return False
2819
David Pursehouse8a68ff92012-09-24 12:15:13 +09002820 all_refs = self.bare_ref.all
2821 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002822 head = self.work_git.GetHead()
2823 if head.startswith(R_HEADS):
2824 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002825 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002826 except KeyError:
2827 head = None
2828
2829 if revid == head:
2830 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002831 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002832 return True
2833 return False