blob: 2f70e996da483fbaa251001d3981909dbd164a3c [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):
547 def __init__(self,
548 manifest,
549 name,
550 remote,
551 gitdir,
David James8d201162013-10-11 17:03:19 -0700552 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700553 worktree,
554 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700555 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800556 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100557 rebase=True,
558 groups=None,
559 sync_c=False,
560 sync_s=False,
561 clone_depth=None,
562 upstream=None,
563 parent=None,
564 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900565 dest_branch=None,
566 optimized_fetch=False):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800567 """Init a Project object.
568
569 Args:
570 manifest: The XmlManifest object.
571 name: The `name` attribute of manifest.xml's project element.
572 remote: RemoteSpec object specifying its remote's properties.
573 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700574 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800575 worktree: Absolute path of git working tree.
576 relpath: Relative path of git working tree to repo's top directory.
577 revisionExpr: The `revision` attribute of manifest.xml's project element.
578 revisionId: git commit id for checking out.
579 rebase: The `rebase` attribute of manifest.xml's project element.
580 groups: The `groups` attribute of manifest.xml's project element.
581 sync_c: The `sync-c` attribute of manifest.xml's project element.
582 sync_s: The `sync-s` attribute of manifest.xml's project element.
583 upstream: The `upstream` attribute of manifest.xml's project element.
584 parent: The parent Project object.
585 is_derived: False if the project was explicitly defined in the manifest;
586 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400587 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900588 optimized_fetch: If True, when a project is set to a sha1 revision, only
589 fetch from the remote if the sha1 is not present locally.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800590 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700591 self.manifest = manifest
592 self.name = name
593 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800594 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700595 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800596 if worktree:
597 self.worktree = worktree.replace('\\', '/')
598 else:
599 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700600 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700601 self.revisionExpr = revisionExpr
602
603 if revisionId is None \
604 and revisionExpr \
605 and IsId(revisionExpr):
606 self.revisionId = revisionExpr
607 else:
608 self.revisionId = revisionId
609
Mike Pontillod3153822012-02-28 11:53:24 -0800610 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700611 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700612 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800613 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900614 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700615 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800616 self.parent = parent
617 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900618 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800619 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800620
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700621 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700622 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500623 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500624 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700625 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100626 gitdir=self.gitdir,
627 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700628
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800629 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700630 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800631 else:
632 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700633 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700634 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700635 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400636 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700637
Doug Anderson37282b42011-03-04 11:54:18 -0800638 # This will be filled in if a project is later identified to be the
639 # project containing repo hooks.
640 self.enabled_repo_hooks = []
641
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700642 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800643 def Derived(self):
644 return self.is_derived
645
646 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700647 def Exists(self):
648 return os.path.isdir(self.gitdir)
649
650 @property
651 def CurrentBranch(self):
652 """Obtain the name of the currently checked out branch.
653 The branch name omits the 'refs/heads/' prefix.
654 None is returned if the project is on a detached HEAD.
655 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700656 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700657 if b.startswith(R_HEADS):
658 return b[len(R_HEADS):]
659 return None
660
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700661 def IsRebaseInProgress(self):
662 w = self.worktree
663 g = os.path.join(w, '.git')
664 return os.path.exists(os.path.join(g, 'rebase-apply')) \
665 or os.path.exists(os.path.join(g, 'rebase-merge')) \
666 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200667
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700668 def IsDirty(self, consider_untracked=True):
669 """Is the working directory modified in some way?
670 """
671 self.work_git.update_index('-q',
672 '--unmerged',
673 '--ignore-missing',
674 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900675 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700676 return True
677 if self.work_git.DiffZ('diff-files'):
678 return True
679 if consider_untracked and self.work_git.LsOthers():
680 return True
681 return False
682
683 _userident_name = None
684 _userident_email = None
685
686 @property
687 def UserName(self):
688 """Obtain the user's personal name.
689 """
690 if self._userident_name is None:
691 self._LoadUserIdentity()
692 return self._userident_name
693
694 @property
695 def UserEmail(self):
696 """Obtain the user's email address. This is very likely
697 to be their Gerrit login.
698 """
699 if self._userident_email is None:
700 self._LoadUserIdentity()
701 return self._userident_email
702
703 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900704 u = self.bare_git.var('GIT_COMMITTER_IDENT')
705 m = re.compile("^(.*) <([^>]*)> ").match(u)
706 if m:
707 self._userident_name = m.group(1)
708 self._userident_email = m.group(2)
709 else:
710 self._userident_name = ''
711 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712
713 def GetRemote(self, name):
714 """Get the configuration for a single remote.
715 """
716 return self.config.GetRemote(name)
717
718 def GetBranch(self, name):
719 """Get the configuration for a single branch.
720 """
721 return self.config.GetBranch(name)
722
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700723 def GetBranches(self):
724 """Get all existing local branches.
725 """
726 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900727 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700728 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700729
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530730 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700731 if name.startswith(R_HEADS):
732 name = name[len(R_HEADS):]
733 b = self.GetBranch(name)
734 b.current = name == current
735 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900736 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700737 heads[name] = b
738
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530739 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700740 if name.startswith(R_PUB):
741 name = name[len(R_PUB):]
742 b = heads.get(name)
743 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900744 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700745
746 return heads
747
Colin Cross5acde752012-03-28 20:15:45 -0700748 def MatchesGroups(self, manifest_groups):
749 """Returns true if the manifest groups specified at init should cause
750 this project to be synced.
751 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700752 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700753
754 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700755 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700756 manifest_groups: "-group1,group2"
757 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500758
759 The special manifest group "default" will match any project that
760 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700761 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500762 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700763 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500764 if not 'notdefault' in expanded_project_groups:
765 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700766
Conley Owens971de8e2012-04-16 10:36:08 -0700767 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700768 for group in expanded_manifest_groups:
769 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700770 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700771 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700772 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700773
Conley Owens971de8e2012-04-16 10:36:08 -0700774 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775
776## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700777 def UncommitedFiles(self, get_all=True):
778 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700779
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700780 Args:
781 get_all: a boolean, if True - get information about all different
782 uncommitted files. If False - return as soon as any kind of
783 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500784 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700785 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500786 self.work_git.update_index('-q',
787 '--unmerged',
788 '--ignore-missing',
789 '--refresh')
790 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700791 details.append("rebase in progress")
792 if not get_all:
793 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500794
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700795 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
796 if changes:
797 details.extend(changes)
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-files').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.LsOthers()
808 if changes:
809 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500810
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700811 return details
812
813 def HasChanges(self):
814 """Returns true if there are uncommitted changes.
815 """
816 if self.UncommitedFiles(get_all=False):
817 return True
818 else:
819 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500820
Terence Haddock4655e812011-03-31 12:33:34 +0200821 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700822 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200823
824 Args:
825 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700826 """
827 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200828 if output_redir == None:
829 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700830 print(file=output_redir)
831 print('project %s/' % self.relpath, file=output_redir)
832 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700833 return
834
835 self.work_git.update_index('-q',
836 '--unmerged',
837 '--ignore-missing',
838 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700839 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700840 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
841 df = self.work_git.DiffZ('diff-files')
842 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100843 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700844 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845
846 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200847 if not output_redir == None:
848 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700849 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700850
851 branch = self.CurrentBranch
852 if branch is None:
853 out.nobranch('(*** NO BRANCH ***)')
854 else:
855 out.branch('branch %s', branch)
856 out.nl()
857
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700858 if rb:
859 out.important('prior sync failed; rebase still in progress')
860 out.nl()
861
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862 paths = list()
863 paths.extend(di.keys())
864 paths.extend(df.keys())
865 paths.extend(do)
866
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530867 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900868 try:
869 i = di[p]
870 except KeyError:
871 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900873 try:
874 f = df[p]
875 except KeyError:
876 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200877
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900878 if i:
879 i_status = i.status.upper()
880 else:
881 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700882
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900883 if f:
884 f_status = f.status.lower()
885 else:
886 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700887
888 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800889 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890 i.src_path, p, i.level)
891 else:
892 line = ' %s%s\t%s' % (i_status, f_status, p)
893
894 if i and not f:
895 out.added('%s', line)
896 elif (i and f) or (not i and f):
897 out.changed('%s', line)
898 elif not i and not f:
899 out.untracked('%s', line)
900 else:
901 out.write('%s', line)
902 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200903
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700904 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905
pelyad67872d2012-03-28 14:49:58 +0300906 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700907 """Prints the status of the repository to stdout.
908 """
909 out = DiffColoring(self.config)
910 cmd = ['diff']
911 if out.is_on:
912 cmd.append('--color')
913 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300914 if absolute_paths:
915 cmd.append('--src-prefix=a/%s/' % self.relpath)
916 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917 cmd.append('--')
918 p = GitCommand(self,
919 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100920 capture_stdout=True,
921 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 has_diff = False
923 for line in p.process.stdout:
924 if not has_diff:
925 out.nl()
926 out.project('project %s/' % self.relpath)
927 out.nl()
928 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700929 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 p.Wait()
931
932
933## Publish / Upload ##
934
David Pursehouse8a68ff92012-09-24 12:15:13 +0900935 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936 """Was the branch published (uploaded) for code review?
937 If so, returns the SHA-1 hash of the last published
938 state for the branch.
939 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700940 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900941 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700942 try:
943 return self.bare_git.rev_parse(key)
944 except GitError:
945 return None
946 else:
947 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900948 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700949 except KeyError:
950 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951
David Pursehouse8a68ff92012-09-24 12:15:13 +0900952 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953 """Prunes any stale published refs.
954 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900955 if all_refs is None:
956 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957 heads = set()
958 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530959 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700960 if name.startswith(R_HEADS):
961 heads.add(name)
962 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900963 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530965 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966 n = name[len(R_PUB):]
967 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900968 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700970 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700971 """List any branches which can be uploaded for review.
972 """
973 heads = {}
974 pubed = {}
975
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530976 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700977 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900978 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900980 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981
982 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530983 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900984 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700985 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700986 if selected_branch and branch != selected_branch:
987 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700988
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800989 rb = self.GetUploadableBranch(branch)
990 if rb:
991 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700992 return ready
993
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800994 def GetUploadableBranch(self, branch_name):
995 """Get a single uploadable branch, or None.
996 """
997 branch = self.GetBranch(branch_name)
998 base = branch.LocalMerge
999 if branch.LocalMerge:
1000 rb = ReviewableBranch(self, branch, base)
1001 if rb.commits:
1002 return rb
1003 return None
1004
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001005 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001006 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001007 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001008 draft=False,
1009 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001010 """Uploads the named branch for code review.
1011 """
1012 if branch is None:
1013 branch = self.CurrentBranch
1014 if branch is None:
1015 raise GitError('not currently on a branch')
1016
1017 branch = self.GetBranch(branch)
1018 if not branch.LocalMerge:
1019 raise GitError('branch %s does not track a remote' % branch.name)
1020 if not branch.remote.review:
1021 raise GitError('remote %s has no review url' % branch.remote.name)
1022
Bryan Jacobsf609f912013-05-06 13:36:24 -04001023 if dest_branch is None:
1024 dest_branch = self.dest_branch
1025 if dest_branch is None:
1026 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001027 if not dest_branch.startswith(R_HEADS):
1028 dest_branch = R_HEADS + dest_branch
1029
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001030 if not branch.remote.projectname:
1031 branch.remote.projectname = self.name
1032 branch.remote.Save()
1033
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001034 url = branch.remote.ReviewUrl(self.UserEmail)
1035 if url is None:
1036 raise UploadError('review not configured')
1037 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001038
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001039 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001040 rp = ['gerrit receive-pack']
1041 for e in people[0]:
1042 rp.append('--reviewer=%s' % sq(e))
1043 for e in people[1]:
1044 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001045 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001046
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001047 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001048
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001049 if dest_branch.startswith(R_HEADS):
1050 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001051
1052 upload_type = 'for'
1053 if draft:
1054 upload_type = 'drafts'
1055
1056 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1057 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001058 if auto_topic:
1059 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001060 if not url.startswith('ssh://'):
1061 rp = ['r=%s' % p for p in people[0]] + \
1062 ['cc=%s' % p for p in people[1]]
1063 if rp:
1064 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001065 cmd.append(ref_spec)
1066
Anthony King7bdac712014-07-16 12:56:40 +01001067 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001068 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069
1070 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1071 self.bare_git.UpdateRef(R_PUB + branch.name,
1072 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001073 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074
1075
1076## Sync ##
1077
Julien Campergue335f5ef2013-10-16 11:02:35 +02001078 def _ExtractArchive(self, tarpath, path=None):
1079 """Extract the given tar on its current location
1080
1081 Args:
1082 - tarpath: The path to the actual tar file
1083
1084 """
1085 try:
1086 with tarfile.open(tarpath, 'r') as tar:
1087 tar.extractall(path=path)
1088 return True
1089 except (IOError, tarfile.TarError) as e:
1090 print("error: Cannot extract archive %s: "
1091 "%s" % (tarpath, str(e)), file=sys.stderr)
1092 return False
1093
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001094 def Sync_NetworkHalf(self,
1095 quiet=False,
1096 is_new=None,
1097 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001098 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001099 no_tags=False,
David Pursehouseb1553542014-09-04 21:28:09 +09001100 archive=False,
1101 optimized_fetch=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102 """Perform only the network IO portion of the sync process.
1103 Local working directory/branch state is not affected.
1104 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001105 if archive and not isinstance(self, MetaProject):
1106 if self.remote.url.startswith(('http://', 'https://')):
1107 print("error: %s: Cannot fetch archives from http/https "
1108 "remotes." % self.name, file=sys.stderr)
1109 return False
1110
1111 name = self.relpath.replace('\\', '/')
1112 name = name.replace('/', '_')
1113 tarpath = '%s.tar' % name
1114 topdir = self.manifest.topdir
1115
1116 try:
1117 self._FetchArchive(tarpath, cwd=topdir)
1118 except GitError as e:
1119 print('error: %s' % str(e), file=sys.stderr)
1120 return False
1121
1122 # From now on, we only need absolute tarpath
1123 tarpath = os.path.join(topdir, tarpath)
1124
1125 if not self._ExtractArchive(tarpath, path=topdir):
1126 return False
1127 try:
1128 os.remove(tarpath)
1129 except OSError as e:
1130 print("warn: Cannot remove archive %s: "
1131 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001132 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001133 return True
1134
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001135 if is_new is None:
1136 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001137 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001138 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001139 else:
1140 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001141 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001142
1143 if is_new:
1144 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1145 try:
1146 fd = open(alt, 'rb')
1147 try:
1148 alt_dir = fd.readline().rstrip()
1149 finally:
1150 fd.close()
1151 except IOError:
1152 alt_dir = None
1153 else:
1154 alt_dir = None
1155
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001156 if clone_bundle \
1157 and alt_dir is None \
1158 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001159 is_new = False
1160
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001161 if not current_branch_only:
1162 if self.sync_c:
1163 current_branch_only = True
1164 elif not self.manifest._loaded:
1165 # Manifest cannot check defaults until it syncs.
1166 current_branch_only = False
1167 elif self.manifest.default.sync_c:
1168 current_branch_only = True
1169
David Pursehouseb1553542014-09-04 21:28:09 +09001170 need_to_fetch = not (optimized_fetch and \
1171 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1172 if (need_to_fetch
Conley Owens666d5342014-05-01 13:09:57 -07001173 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1174 current_branch_only=current_branch_only,
1175 no_tags=no_tags)):
Anthony King7bdac712014-07-16 12:56:40 +01001176 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001177
1178 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001179 self._InitMRef()
1180 else:
1181 self._InitMirrorHead()
1182 try:
1183 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1184 except OSError:
1185 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001186 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001187
1188 def PostRepoUpgrade(self):
1189 self._InitHooks()
1190
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001191 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001192 for copyfile in self.copyfiles:
1193 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001194 for linkfile in self.linkfiles:
1195 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196
Julien Camperguedd654222014-01-09 16:21:37 +01001197 def GetCommitRevisionId(self):
1198 """Get revisionId of a commit.
1199
1200 Use this method instead of GetRevisionId to get the id of the commit rather
1201 than the id of the current git object (for example, a tag)
1202
1203 """
1204 if not self.revisionExpr.startswith(R_TAGS):
1205 return self.GetRevisionId(self._allrefs)
1206
1207 try:
1208 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1209 except GitError:
1210 raise ManifestInvalidRevisionError(
1211 'revision %s in %s not found' % (self.revisionExpr,
1212 self.name))
1213
David Pursehouse8a68ff92012-09-24 12:15:13 +09001214 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001215 if self.revisionId:
1216 return self.revisionId
1217
1218 rem = self.GetRemote(self.remote.name)
1219 rev = rem.ToLocal(self.revisionExpr)
1220
David Pursehouse8a68ff92012-09-24 12:15:13 +09001221 if all_refs is not None and rev in all_refs:
1222 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001223
1224 try:
1225 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1226 except GitError:
1227 raise ManifestInvalidRevisionError(
1228 'revision %s in %s not found' % (self.revisionExpr,
1229 self.name))
1230
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001231 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001232 """Perform only the local IO portion of the sync process.
1233 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001234 """
David James8d201162013-10-11 17:03:19 -07001235 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001236 all_refs = self.bare_ref.all
1237 self.CleanPublishedCache(all_refs)
1238 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001239
David Pursehouse1d947b32012-10-25 12:23:11 +09001240 def _doff():
1241 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001242 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001243
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001244 head = self.work_git.GetHead()
1245 if head.startswith(R_HEADS):
1246 branch = head[len(R_HEADS):]
1247 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001248 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001249 except KeyError:
1250 head = None
1251 else:
1252 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001253
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001254 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001255 # Currently on a detached HEAD. The user is assumed to
1256 # not have any local modifications worth worrying about.
1257 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001258 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001259 syncbuf.fail(self, _PriorSyncFailedError())
1260 return
1261
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001262 if head == revid:
1263 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001264 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001265 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001266 if not syncbuf.detach_head:
1267 return
1268 else:
1269 lost = self._revlist(not_rev(revid), HEAD)
1270 if lost:
1271 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001272
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001273 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001274 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001275 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001276 syncbuf.fail(self, e)
1277 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001278 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001279 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001281 if head == revid:
1282 # No changes; don't do anything further.
1283 #
1284 return
1285
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001287
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001288 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001289 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001290 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001291 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001292 syncbuf.info(self,
1293 "leaving %s; does not track upstream",
1294 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001295 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001296 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001297 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001298 syncbuf.fail(self, e)
1299 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001300 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001301 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001303 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001304 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001306 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 if not_merged:
1308 if upstream_gain:
1309 # The user has published this branch and some of those
1310 # commits are not yet merged upstream. We do not want
1311 # to rewrite the published commits so we punt.
1312 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001313 syncbuf.fail(self,
1314 "branch %s is published (but not merged) and is now %d commits behind"
1315 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001316 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001317 elif pub == head:
1318 # All published commits are merged, and thus we are a
1319 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001320 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001321 syncbuf.later1(self, _doff)
1322 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001324 # Examine the local commits not in the remote. Find the
1325 # last one attributed to this user, if any.
1326 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001327 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001328 last_mine = None
1329 cnt_mine = 0
1330 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301331 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001332 if committer_email == self.UserEmail:
1333 last_mine = commit_id
1334 cnt_mine += 1
1335
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001336 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001337 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001338
1339 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001340 syncbuf.fail(self, _DirtyError())
1341 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001343 # If the upstream switched on us, warn the user.
1344 #
1345 if branch.merge != self.revisionExpr:
1346 if branch.merge and self.revisionExpr:
1347 syncbuf.info(self,
1348 'manifest switched %s...%s',
1349 branch.merge,
1350 self.revisionExpr)
1351 elif branch.merge:
1352 syncbuf.info(self,
1353 'manifest no longer tracks %s',
1354 branch.merge)
1355
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001356 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001358 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001360 syncbuf.info(self,
1361 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001362 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001364 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001365 if not ID_RE.match(self.revisionExpr):
1366 # in case of manifest sync the revisionExpr might be a SHA1
1367 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001368 if not branch.merge.startswith('refs/'):
1369 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001370 branch.Save()
1371
Mike Pontillod3153822012-02-28 11:53:24 -08001372 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001373 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001374 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001375 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001376 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001377 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001378 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001379 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001380 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001381 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001382 syncbuf.fail(self, e)
1383 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001384 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001385 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001387 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001388 # dest should already be an absolute path, but src is project relative
1389 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001390 abssrc = os.path.join(self.worktree, src)
1391 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001392
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001393 def AddLinkFile(self, src, dest, absdest):
1394 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001395 # make src relative path to dest
1396 absdestdir = os.path.dirname(absdest)
1397 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001398 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001399
James W. Mills24c13082012-04-12 15:04:13 -05001400 def AddAnnotation(self, name, value, keep):
1401 self.annotations.append(_Annotation(name, value, keep))
1402
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001403 def DownloadPatchSet(self, change_id, patch_id):
1404 """Download a single patch set of a single change to FETCH_HEAD.
1405 """
1406 remote = self.GetRemote(self.remote.name)
1407
1408 cmd = ['fetch', remote.name]
1409 cmd.append('refs/changes/%2.2d/%d/%d' \
1410 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001411 if GitCommand(self, cmd, bare=True).Wait() != 0:
1412 return None
1413 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001414 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001415 change_id,
1416 patch_id,
1417 self.bare_git.rev_parse('FETCH_HEAD'))
1418
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001419
1420## Branch Management ##
1421
1422 def StartBranch(self, name):
1423 """Create a new branch off the manifest's revision.
1424 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001425 head = self.work_git.GetHead()
1426 if head == (R_HEADS + name):
1427 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001428
David Pursehouse8a68ff92012-09-24 12:15:13 +09001429 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001430 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001431 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001432 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001433 capture_stdout=True,
1434 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001435
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001436 branch = self.GetBranch(name)
1437 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001438 branch.merge = self.revisionExpr
Alexandre Boeglin38258272015-04-30 14:50:33 +02001439 if not branch.merge.startswith('refs/') and not ID_RE.match(self.revisionExpr):
Conley Owens04f2f0e2014-10-01 17:22:46 -07001440 branch.merge = R_HEADS + self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001441 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001442
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001443 if head.startswith(R_HEADS):
1444 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001445 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001446 except KeyError:
1447 head = None
1448
1449 if revid and head and revid == head:
1450 ref = os.path.join(self.gitdir, R_HEADS + name)
1451 try:
1452 os.makedirs(os.path.dirname(ref))
1453 except OSError:
1454 pass
1455 _lwrite(ref, '%s\n' % revid)
1456 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1457 'ref: %s%s\n' % (R_HEADS, name))
1458 branch.Save()
1459 return True
1460
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001461 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001462 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001463 capture_stdout=True,
1464 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001465 branch.Save()
1466 return True
1467 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001468
Wink Saville02d79452009-04-10 13:01:24 -07001469 def CheckoutBranch(self, name):
1470 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001471
1472 Args:
1473 name: The name of the branch to checkout.
1474
1475 Returns:
1476 True if the checkout succeeded; False if it didn't; None if the branch
1477 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001478 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001479 rev = R_HEADS + name
1480 head = self.work_git.GetHead()
1481 if head == rev:
1482 # Already on the branch
1483 #
1484 return True
Wink Saville02d79452009-04-10 13:01:24 -07001485
David Pursehouse8a68ff92012-09-24 12:15:13 +09001486 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001487 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001488 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001489 except KeyError:
1490 # Branch does not exist in this project
1491 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001492 return None
Wink Saville02d79452009-04-10 13:01:24 -07001493
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001494 if head.startswith(R_HEADS):
1495 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001496 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001497 except KeyError:
1498 head = None
1499
1500 if head == revid:
1501 # Same revision; just update HEAD to point to the new
1502 # target branch, but otherwise take no other action.
1503 #
1504 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1505 'ref: %s%s\n' % (R_HEADS, name))
1506 return True
1507
1508 return GitCommand(self,
1509 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001510 capture_stdout=True,
1511 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001512
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001513 def AbandonBranch(self, name):
1514 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001515
1516 Args:
1517 name: The name of the branch to abandon.
1518
1519 Returns:
1520 True if the abandon succeeded; False if it didn't; None if the branch
1521 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001522 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001523 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001524 all_refs = self.bare_ref.all
1525 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001526 # Doesn't exist
1527 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001528
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001529 head = self.work_git.GetHead()
1530 if head == rev:
1531 # We can't destroy the branch while we are sitting
1532 # on it. Switch to a detached HEAD.
1533 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001534 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001535
David Pursehouse8a68ff92012-09-24 12:15:13 +09001536 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001537 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001538 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1539 '%s\n' % revid)
1540 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001541 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001542
1543 return GitCommand(self,
1544 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001545 capture_stdout=True,
1546 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001547
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001548 def PruneHeads(self):
1549 """Prune any topic branches already merged into upstream.
1550 """
1551 cb = self.CurrentBranch
1552 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001553 left = self._allrefs
1554 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001555 if name.startswith(R_HEADS):
1556 name = name[len(R_HEADS):]
1557 if cb is None or name != cb:
1558 kill.append(name)
1559
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001560 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001561 if cb is not None \
1562 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001563 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001564 self.work_git.DetachHead(HEAD)
1565 kill.append(cb)
1566
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001567 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001568 old = self.bare_git.GetHead()
1569 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001570 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1571
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001572 try:
1573 self.bare_git.DetachHead(rev)
1574
1575 b = ['branch', '-d']
1576 b.extend(kill)
1577 b = GitCommand(self, b, bare=True,
1578 capture_stdout=True,
1579 capture_stderr=True)
1580 b.Wait()
1581 finally:
1582 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001583 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001584
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001585 for branch in kill:
1586 if (R_HEADS + branch) not in left:
1587 self.CleanPublishedCache()
1588 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001589
1590 if cb and cb not in kill:
1591 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001592 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001593
1594 kept = []
1595 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001596 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001597 branch = self.GetBranch(branch)
1598 base = branch.LocalMerge
1599 if not base:
1600 base = rev
1601 kept.append(ReviewableBranch(self, branch, base))
1602 return kept
1603
1604
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001605## Submodule Management ##
1606
1607 def GetRegisteredSubprojects(self):
1608 result = []
1609 def rec(subprojects):
1610 if not subprojects:
1611 return
1612 result.extend(subprojects)
1613 for p in subprojects:
1614 rec(p.subprojects)
1615 rec(self.subprojects)
1616 return result
1617
1618 def _GetSubmodules(self):
1619 # Unfortunately we cannot call `git submodule status --recursive` here
1620 # because the working tree might not exist yet, and it cannot be used
1621 # without a working tree in its current implementation.
1622
1623 def get_submodules(gitdir, rev):
1624 # Parse .gitmodules for submodule sub_paths and sub_urls
1625 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1626 if not sub_paths:
1627 return []
1628 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1629 # revision of submodule repository
1630 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1631 submodules = []
1632 for sub_path, sub_url in zip(sub_paths, sub_urls):
1633 try:
1634 sub_rev = sub_revs[sub_path]
1635 except KeyError:
1636 # Ignore non-exist submodules
1637 continue
1638 submodules.append((sub_rev, sub_path, sub_url))
1639 return submodules
1640
1641 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1642 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1643 def parse_gitmodules(gitdir, rev):
1644 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1645 try:
Anthony King7bdac712014-07-16 12:56:40 +01001646 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1647 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001648 except GitError:
1649 return [], []
1650 if p.Wait() != 0:
1651 return [], []
1652
1653 gitmodules_lines = []
1654 fd, temp_gitmodules_path = tempfile.mkstemp()
1655 try:
1656 os.write(fd, p.stdout)
1657 os.close(fd)
1658 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001659 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1660 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001661 if p.Wait() != 0:
1662 return [], []
1663 gitmodules_lines = p.stdout.split('\n')
1664 except GitError:
1665 return [], []
1666 finally:
1667 os.remove(temp_gitmodules_path)
1668
1669 names = set()
1670 paths = {}
1671 urls = {}
1672 for line in gitmodules_lines:
1673 if not line:
1674 continue
1675 m = re_path.match(line)
1676 if m:
1677 names.add(m.group(1))
1678 paths[m.group(1)] = m.group(2)
1679 continue
1680 m = re_url.match(line)
1681 if m:
1682 names.add(m.group(1))
1683 urls[m.group(1)] = m.group(2)
1684 continue
1685 names = sorted(names)
1686 return ([paths.get(name, '') for name in names],
1687 [urls.get(name, '') for name in names])
1688
1689 def git_ls_tree(gitdir, rev, paths):
1690 cmd = ['ls-tree', rev, '--']
1691 cmd.extend(paths)
1692 try:
Anthony King7bdac712014-07-16 12:56:40 +01001693 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1694 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001695 except GitError:
1696 return []
1697 if p.Wait() != 0:
1698 return []
1699 objects = {}
1700 for line in p.stdout.split('\n'):
1701 if not line.strip():
1702 continue
1703 object_rev, object_path = line.split()[2:4]
1704 objects[object_path] = object_rev
1705 return objects
1706
1707 try:
1708 rev = self.GetRevisionId()
1709 except GitError:
1710 return []
1711 return get_submodules(self.gitdir, rev)
1712
1713 def GetDerivedSubprojects(self):
1714 result = []
1715 if not self.Exists:
1716 # If git repo does not exist yet, querying its submodules will
1717 # mess up its states; so return here.
1718 return result
1719 for rev, path, url in self._GetSubmodules():
1720 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001721 relpath, worktree, gitdir, objdir = \
1722 self.manifest.GetSubprojectPaths(self, name, path)
1723 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001724 if project:
1725 result.extend(project.GetDerivedSubprojects())
1726 continue
David James8d201162013-10-11 17:03:19 -07001727
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001728 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001729 url=url,
1730 review=self.remote.review,
1731 revision=self.remote.revision)
1732 subproject = Project(manifest=self.manifest,
1733 name=name,
1734 remote=remote,
1735 gitdir=gitdir,
1736 objdir=objdir,
1737 worktree=worktree,
1738 relpath=relpath,
1739 revisionExpr=self.revisionExpr,
1740 revisionId=rev,
1741 rebase=self.rebase,
1742 groups=self.groups,
1743 sync_c=self.sync_c,
1744 sync_s=self.sync_s,
1745 parent=self,
1746 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001747 result.append(subproject)
1748 result.extend(subproject.GetDerivedSubprojects())
1749 return result
1750
1751
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001752## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001753 def _CheckForSha1(self):
1754 try:
1755 # if revision (sha or tag) is not present then following function
1756 # throws an error.
1757 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1758 return True
1759 except GitError:
1760 # There is no such persistent revision. We have to fetch it.
1761 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001762
Julien Campergue335f5ef2013-10-16 11:02:35 +02001763 def _FetchArchive(self, tarpath, cwd=None):
1764 cmd = ['archive', '-v', '-o', tarpath]
1765 cmd.append('--remote=%s' % self.remote.url)
1766 cmd.append('--prefix=%s/' % self.relpath)
1767 cmd.append(self.revisionExpr)
1768
1769 command = GitCommand(self, cmd, cwd=cwd,
1770 capture_stdout=True,
1771 capture_stderr=True)
1772
1773 if command.Wait() != 0:
1774 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1775
Conley Owens80b87fe2014-05-09 17:13:44 -07001776
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001777 def _RemoteFetch(self, name=None,
1778 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001779 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001780 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001781 alt_dir=None,
1782 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001783
1784 is_sha1 = False
1785 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001786 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001787
David Pursehouse9bc422f2014-04-15 10:28:56 +09001788 # The depth should not be used when fetching to a mirror because
1789 # it will result in a shallow repository that cannot be cloned or
1790 # fetched from.
1791 if not self.manifest.IsMirror:
1792 if self.clone_depth:
1793 depth = self.clone_depth
1794 else:
1795 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001796 # The repo project should never be synced with partial depth
1797 if self.relpath == '.repo/repo':
1798 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001799
Shawn Pearce69e04d82014-01-29 12:48:54 -08001800 if depth:
1801 current_branch_only = True
1802
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001803 if ID_RE.match(self.revisionExpr) is not None:
1804 is_sha1 = True
1805
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001806 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001807 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001808 # this is a tag and its sha1 value should never change
1809 tag_name = self.revisionExpr[len(R_TAGS):]
1810
1811 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001812 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001813 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001814 if is_sha1 and not depth:
1815 # When syncing a specific commit and --depth is not set:
1816 # * if upstream is explicitly specified and is not a sha1, fetch only
1817 # upstream as users expect only upstream to be fetch.
1818 # Note: The commit might not be in upstream in which case the sync
1819 # will fail.
1820 # * otherwise, fetch all branches to make sure we end up with the
1821 # specific commit.
1822 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001823
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001824 if not name:
1825 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001826
1827 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001828 remote = self.GetRemote(name)
1829 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001830 ssh_proxy = True
1831
Shawn O. Pearce88443382010-10-08 10:02:09 +02001832 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001833 if alt_dir and 'objects' == os.path.basename(alt_dir):
1834 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001835 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1836 remote = self.GetRemote(name)
1837
David Pursehouse8a68ff92012-09-24 12:15:13 +09001838 all_refs = self.bare_ref.all
1839 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001840 tmp = set()
1841
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301842 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001843 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001844 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001845 all_refs[r] = ref_id
1846 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001847 continue
1848
David Pursehouse8a68ff92012-09-24 12:15:13 +09001849 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001850 continue
1851
David Pursehouse8a68ff92012-09-24 12:15:13 +09001852 r = 'refs/_alt/%s' % ref_id
1853 all_refs[r] = ref_id
1854 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001855 tmp.add(r)
1856
Shawn O. Pearce88443382010-10-08 10:02:09 +02001857 tmp_packed = ''
1858 old_packed = ''
1859
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301860 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001861 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001862 tmp_packed += line
1863 if r not in tmp:
1864 old_packed += line
1865
1866 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001867 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001868 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001869
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001870 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001871
Conley Owensf97e8382015-01-21 11:12:46 -08001872 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001873 cmd.append('--depth=%s' % depth)
1874
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001875 if quiet:
1876 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001877 if not self.worktree:
1878 cmd.append('--update-head-ok')
David Pursehouseb4d43b92015-04-28 18:28:12 +09001879 if self.manifest.IsMirror:
1880 cmd.append('--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001881 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001882
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001883 # If using depth then we should not get all the tags since they may
1884 # be outside of the depth.
1885 if no_tags or depth:
1886 cmd.append('--no-tags')
1887 else:
1888 cmd.append('--tags')
1889
Conley Owens80b87fe2014-05-09 17:13:44 -07001890 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001891 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001892 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001893 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001894 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001895 spec.append('tag')
1896 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001897
David Pursehouse403b64e2015-04-27 10:41:33 +09001898 if not self.manifest.IsMirror:
1899 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06001900 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001901 # Shallow checkout of a specific commit, fetch from that commit and not
1902 # the heads only as the commit might be deeper in the history.
1903 spec.append(branch)
1904 else:
1905 if is_sha1:
1906 branch = self.upstream
1907 if branch is not None and branch.strip():
1908 if not branch.startswith('refs/'):
1909 branch = R_HEADS + branch
1910 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001911 cmd.extend(spec)
1912
1913 shallowfetch = self.config.GetString('repo.shallowfetch')
1914 if shallowfetch and shallowfetch != ' '.join(spec):
Anthony King23ff7df2015-03-28 19:42:39 +00001915 GitCommand(self, ['fetch', '--depth=2147483647', name]
1916 + shallowfetch.split(),
Conley Owens80b87fe2014-05-09 17:13:44 -07001917 bare=True, ssh_proxy=ssh_proxy).Wait()
1918 if depth:
Anthony King7bdac712014-07-16 12:56:40 +01001919 self.config.SetString('repo.shallowfetch', ' '.join(spec))
Conley Owens80b87fe2014-05-09 17:13:44 -07001920 else:
Anthony King7bdac712014-07-16 12:56:40 +01001921 self.config.SetString('repo.shallowfetch', None)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001922
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001923 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001924 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001925 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001926 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001927 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001928 ok = True
1929 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001930 # If needed, run the 'git remote prune' the first time through the loop
1931 elif (not _i and
1932 "error:" in gitcmd.stderr and
1933 "git remote prune" in gitcmd.stderr):
1934 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001935 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001936 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001937 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001938 break
1939 continue
Brian Harring14a66742012-09-28 20:21:57 -07001940 elif current_branch_only and is_sha1 and ret == 128:
1941 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1942 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1943 # abort the optimization attempt and do a full sync.
1944 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001945 elif ret < 0:
1946 # Git died with a signal, exit immediately
1947 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001948 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001949
1950 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001951 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001952 if old_packed != '':
1953 _lwrite(packed_refs, old_packed)
1954 else:
1955 os.remove(packed_refs)
1956 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001957
1958 if is_sha1 and current_branch_only and self.upstream:
1959 # We just synced the upstream given branch; verify we
1960 # got what we wanted, else trigger a second run of all
1961 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001962 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06001963 if not depth:
1964 # Avoid infinite recursion when depth is True (since depth implies
1965 # current_branch_only)
1966 return self._RemoteFetch(name=name, current_branch_only=False,
1967 initial=False, quiet=quiet, alt_dir=alt_dir)
1968 if self.clone_depth:
1969 self.clone_depth = None
1970 return self._RemoteFetch(name=name, current_branch_only=current_branch_only,
1971 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07001972
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001973 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001974
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001975 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001976 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001977 return False
1978
1979 remote = self.GetRemote(self.remote.name)
1980 bundle_url = remote.url + '/clone.bundle'
1981 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001982 if GetSchemeFromUrl(bundle_url) not in (
1983 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001984 return False
1985
1986 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1987 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1988
1989 exist_dst = os.path.exists(bundle_dst)
1990 exist_tmp = os.path.exists(bundle_tmp)
1991
1992 if not initial and not exist_dst and not exist_tmp:
1993 return False
1994
1995 if not exist_dst:
1996 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1997 if not exist_dst:
1998 return False
1999
2000 cmd = ['fetch']
2001 if quiet:
2002 cmd.append('--quiet')
2003 if not self.worktree:
2004 cmd.append('--update-head-ok')
2005 cmd.append(bundle_dst)
2006 for f in remote.fetch:
2007 cmd.append(str(f))
2008 cmd.append('refs/tags/*:refs/tags/*')
2009
2010 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002011 if os.path.exists(bundle_dst):
2012 os.remove(bundle_dst)
2013 if os.path.exists(bundle_tmp):
2014 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002015 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002016
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002017 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002018 if os.path.exists(dstPath):
2019 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002020
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002021 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002022 if quiet:
2023 cmd += ['--silent']
2024 if os.path.exists(tmpPath):
2025 size = os.stat(tmpPath).st_size
2026 if size >= 1024:
2027 cmd += ['--continue-at', '%d' % (size,)]
2028 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002029 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002030 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2031 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz497bde42015-01-02 13:58:05 -08002032 with self._GetBundleCookieFile(srcUrl, quiet) as cookiefile:
Dave Borowitz137d0132015-01-02 11:12:54 -08002033 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002034 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002035 if srcUrl.startswith('persistent-'):
2036 srcUrl = srcUrl[len('persistent-'):]
2037 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002038
Dave Borowitz137d0132015-01-02 11:12:54 -08002039 if IsTrace():
2040 Trace('%s', ' '.join(cmd))
2041 try:
2042 proc = subprocess.Popen(cmd)
2043 except OSError:
2044 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002045
Dave Borowitz137d0132015-01-02 11:12:54 -08002046 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002047
Dave Borowitz137d0132015-01-02 11:12:54 -08002048 if curlret == 22:
2049 # From curl man page:
2050 # 22: HTTP page not retrieved. The requested url was not found or
2051 # returned another error with the HTTP error code being 400 or above.
2052 # This return code only appears if -f, --fail is used.
2053 if not quiet:
2054 print("Server does not provide clone.bundle; ignoring.",
2055 file=sys.stderr)
2056 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002057
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002058 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002059 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002060 os.rename(tmpPath, dstPath)
2061 return True
2062 else:
2063 os.remove(tmpPath)
2064 return False
2065 else:
2066 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002067
Kris Giesingc8d882a2014-12-23 13:02:32 -08002068 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002069 try:
2070 with open(path) as f:
2071 if f.read(16) == '# v2 git bundle\n':
2072 return True
2073 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002074 if not quiet:
2075 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002076 return False
2077 except OSError:
2078 return False
2079
Dave Borowitz137d0132015-01-02 11:12:54 -08002080 @contextlib.contextmanager
Dave Borowitz497bde42015-01-02 13:58:05 -08002081 def _GetBundleCookieFile(self, url, quiet):
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002082 if url.startswith('persistent-'):
2083 try:
2084 p = subprocess.Popen(
2085 ['git-remote-persistent-https', '-print_config', url],
2086 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2087 stderr=subprocess.PIPE)
Dave Borowitz137d0132015-01-02 11:12:54 -08002088 try:
2089 prefix = 'http.cookiefile='
2090 cookiefile = None
2091 for line in p.stdout:
2092 line = line.strip()
2093 if line.startswith(prefix):
2094 cookiefile = line[len(prefix):]
2095 break
2096 # Leave subprocess open, as cookie file may be transient.
2097 if cookiefile:
2098 yield cookiefile
2099 return
2100 finally:
2101 p.stdin.close()
2102 if p.wait():
2103 err_msg = p.stderr.read()
2104 if ' -print_config' in err_msg:
2105 pass # Persistent proxy doesn't support -print_config.
Dave Borowitz497bde42015-01-02 13:58:05 -08002106 elif not quiet:
Dave Borowitz137d0132015-01-02 11:12:54 -08002107 print(err_msg, file=sys.stderr)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002108 except OSError as e:
2109 if e.errno == errno.ENOENT:
2110 pass # No persistent proxy.
2111 raise
Dave Borowitz137d0132015-01-02 11:12:54 -08002112 yield GitConfig.ForUser().GetString('http.cookiefile')
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002113
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002114 def _Checkout(self, rev, quiet=False):
2115 cmd = ['checkout']
2116 if quiet:
2117 cmd.append('-q')
2118 cmd.append(rev)
2119 cmd.append('--')
2120 if GitCommand(self, cmd).Wait() != 0:
2121 if self._allrefs:
2122 raise GitError('%s checkout %s ' % (self.name, rev))
2123
Anthony King7bdac712014-07-16 12:56:40 +01002124 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002125 cmd = ['cherry-pick']
2126 cmd.append(rev)
2127 cmd.append('--')
2128 if GitCommand(self, cmd).Wait() != 0:
2129 if self._allrefs:
2130 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2131
Anthony King7bdac712014-07-16 12:56:40 +01002132 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002133 cmd = ['revert']
2134 cmd.append('--no-edit')
2135 cmd.append(rev)
2136 cmd.append('--')
2137 if GitCommand(self, cmd).Wait() != 0:
2138 if self._allrefs:
2139 raise GitError('%s revert %s ' % (self.name, rev))
2140
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002141 def _ResetHard(self, rev, quiet=True):
2142 cmd = ['reset', '--hard']
2143 if quiet:
2144 cmd.append('-q')
2145 cmd.append(rev)
2146 if GitCommand(self, cmd).Wait() != 0:
2147 raise GitError('%s reset --hard %s ' % (self.name, rev))
2148
Anthony King7bdac712014-07-16 12:56:40 +01002149 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002150 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002151 if onto is not None:
2152 cmd.extend(['--onto', onto])
2153 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002154 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002155 raise GitError('%s rebase %s ' % (self.name, upstream))
2156
Pierre Tardy3d125942012-05-04 12:18:12 +02002157 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002158 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002159 if ffonly:
2160 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002161 if GitCommand(self, cmd).Wait() != 0:
2162 raise GitError('%s merge %s ' % (self.name, head))
2163
Jonathan Nieder93719792015-03-17 11:29:58 -07002164 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002165 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002166
2167 # Initialize the bare repository, which contains all of the objects.
2168 if not os.path.exists(self.objdir):
2169 os.makedirs(self.objdir)
2170 self.bare_objdir.init()
2171
2172 # If we have a separate directory to hold refs, initialize it as well.
2173 if self.objdir != self.gitdir:
2174 os.makedirs(self.gitdir)
2175 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2176 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002177
Shawn O. Pearce88443382010-10-08 10:02:09 +02002178 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002179 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002180
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002181 if ref_dir or mirror_git:
2182 if not mirror_git:
2183 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002184 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2185 self.relpath + '.git')
2186
2187 if os.path.exists(mirror_git):
2188 ref_dir = mirror_git
2189
2190 elif os.path.exists(repo_git):
2191 ref_dir = repo_git
2192
2193 else:
2194 ref_dir = None
2195
2196 if ref_dir:
2197 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2198 os.path.join(ref_dir, 'objects') + '\n')
2199
Jimmie Westera0444582012-10-24 13:44:42 +02002200 self._UpdateHooks()
2201
2202 m = self.manifest.manifestProject.config
2203 for key in ['user.name', 'user.email']:
Anthony King7bdac712014-07-16 12:56:40 +01002204 if m.Has(key, include_defaults=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002205 self.config.SetString(key, m.GetString(key))
Jonathan Nieder93719792015-03-17 11:29:58 -07002206 if self.manifest.IsMirror:
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002207 self.config.SetString('core.bare', 'true')
2208 else:
2209 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002210
Jimmie Westera0444582012-10-24 13:44:42 +02002211 def _UpdateHooks(self):
2212 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002213 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002214
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002215 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002216 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002217 if not os.path.exists(hooks):
2218 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002219 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002220 name = os.path.basename(stock_hook)
2221
Victor Boivie65e0f352011-04-18 11:23:29 +02002222 if name in ('commit-msg',) and not self.remote.review \
2223 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002224 # Don't install a Gerrit Code Review hook if this
2225 # project does not appear to use it for reviews.
2226 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002227 # Since the manifest project is one of those, but also
2228 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002229 continue
2230
2231 dst = os.path.join(hooks, name)
2232 if os.path.islink(dst):
2233 continue
2234 if os.path.exists(dst):
2235 if filecmp.cmp(stock_hook, dst, shallow=False):
2236 os.remove(dst)
2237 else:
2238 _error("%s: Not replacing %s hook", self.relpath, name)
2239 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002240 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002241 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002242 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002243 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002244 raise GitError('filesystem must support symlinks')
2245 else:
2246 raise
2247
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002248 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002249 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002250 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002251 remote.url = self.remote.url
2252 remote.review = self.remote.review
2253 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002254
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002255 if self.worktree:
2256 remote.ResetFetch(mirror=False)
2257 else:
2258 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002259 remote.Save()
2260
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002261 def _InitMRef(self):
2262 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002263 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002264
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002265 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002266 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002267
2268 def _InitAnyMRef(self, ref):
2269 cur = self.bare_ref.symref(ref)
2270
2271 if self.revisionId:
2272 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2273 msg = 'manifest set to %s' % self.revisionId
2274 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002275 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002276 else:
2277 remote = self.GetRemote(self.remote.name)
2278 dst = remote.ToLocal(self.revisionExpr)
2279 if cur != dst:
2280 msg = 'manifest set to %s' % self.revisionExpr
2281 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002282
David James8d201162013-10-11 17:03:19 -07002283 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2284 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2285
2286 Args:
2287 gitdir: The bare git repository. Must already be initialized.
2288 dotgit: The repository you would like to initialize.
2289 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2290 Only one work tree can store refs under a given |gitdir|.
2291 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2292 This saves you the effort of initializing |dotgit| yourself.
2293 """
2294 # These objects can be shared between several working trees.
2295 symlink_files = ['description', 'info']
2296 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2297 if share_refs:
2298 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002299 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002300 symlink_dirs += ['logs', 'refs']
2301 to_symlink = symlink_files + symlink_dirs
2302
2303 to_copy = []
2304 if copy_all:
2305 to_copy = os.listdir(gitdir)
2306
2307 for name in set(to_copy).union(to_symlink):
2308 try:
2309 src = os.path.realpath(os.path.join(gitdir, name))
2310 dst = os.path.realpath(os.path.join(dotgit, name))
2311
2312 if os.path.lexists(dst) and not os.path.islink(dst):
2313 raise GitError('cannot overwrite a local work tree')
2314
2315 # If the source dir doesn't exist, create an empty dir.
2316 if name in symlink_dirs and not os.path.lexists(src):
2317 os.makedirs(src)
2318
Conley Owens80b87fe2014-05-09 17:13:44 -07002319 # If the source file doesn't exist, ensure the destination
2320 # file doesn't either.
2321 if name in symlink_files and not os.path.lexists(src):
2322 try:
2323 os.remove(dst)
2324 except OSError:
2325 pass
2326
David James8d201162013-10-11 17:03:19 -07002327 if name in to_symlink:
2328 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2329 elif copy_all and not os.path.islink(dst):
2330 if os.path.isdir(src):
2331 shutil.copytree(src, dst)
2332 elif os.path.isfile(src):
2333 shutil.copy(src, dst)
2334 except OSError as e:
2335 if e.errno == errno.EPERM:
2336 raise GitError('filesystem must support symlinks')
2337 else:
2338 raise
2339
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002340 def _InitWorkTree(self):
2341 dotgit = os.path.join(self.worktree, '.git')
2342 if not os.path.exists(dotgit):
2343 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002344 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2345 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002346
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002347 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002348
2349 cmd = ['read-tree', '--reset', '-u']
2350 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002351 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002352 if GitCommand(self, cmd).Wait() != 0:
2353 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002354
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002355 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002356
2357 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002358 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002359
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002360 def _revlist(self, *args, **kw):
2361 a = []
2362 a.extend(args)
2363 a.append('--')
2364 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002365
2366 @property
2367 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002368 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002369
Julien Camperguedd654222014-01-09 16:21:37 +01002370 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2371 """Get logs between two revisions of this project."""
2372 comp = '..'
2373 if rev1:
2374 revs = [rev1]
2375 if rev2:
2376 revs.extend([comp, rev2])
2377 cmd = ['log', ''.join(revs)]
2378 out = DiffColoring(self.config)
2379 if out.is_on and color:
2380 cmd.append('--color')
2381 if oneline:
2382 cmd.append('--oneline')
2383
2384 try:
2385 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2386 if log.Wait() == 0:
2387 return log.stdout
2388 except GitError:
2389 # worktree may not exist if groups changed for example. In that case,
2390 # try in gitdir instead.
2391 if not os.path.exists(self.worktree):
2392 return self.bare_git.log(*cmd[1:])
2393 else:
2394 raise
2395 return None
2396
2397 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2398 """Get the list of logs from this revision to given revisionId"""
2399 logs = {}
2400 selfId = self.GetRevisionId(self._allrefs)
2401 toId = toProject.GetRevisionId(toProject._allrefs)
2402
2403 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2404 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2405 return logs
2406
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002407 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002408 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002409 self._project = project
2410 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002411 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002412
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002413 def LsOthers(self):
2414 p = GitCommand(self._project,
2415 ['ls-files',
2416 '-z',
2417 '--others',
2418 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002419 bare=False,
David James8d201162013-10-11 17:03:19 -07002420 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002421 capture_stdout=True,
2422 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002423 if p.Wait() == 0:
2424 out = p.stdout
2425 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002426 return out[:-1].split('\0') # pylint: disable=W1401
2427 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002428 return []
2429
2430 def DiffZ(self, name, *args):
2431 cmd = [name]
2432 cmd.append('-z')
2433 cmd.extend(args)
2434 p = GitCommand(self._project,
2435 cmd,
David James8d201162013-10-11 17:03:19 -07002436 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002437 bare=False,
2438 capture_stdout=True,
2439 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002440 try:
2441 out = p.process.stdout.read()
2442 r = {}
2443 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002444 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002445 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002446 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002447 info = next(out)
2448 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002449 except StopIteration:
2450 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002451
2452 class _Info(object):
2453 def __init__(self, path, omode, nmode, oid, nid, state):
2454 self.path = path
2455 self.src_path = None
2456 self.old_mode = omode
2457 self.new_mode = nmode
2458 self.old_id = oid
2459 self.new_id = nid
2460
2461 if len(state) == 1:
2462 self.status = state
2463 self.level = None
2464 else:
2465 self.status = state[:1]
2466 self.level = state[1:]
2467 while self.level.startswith('0'):
2468 self.level = self.level[1:]
2469
2470 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002471 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002472 if info.status in ('R', 'C'):
2473 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002474 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002475 r[info.path] = info
2476 return r
2477 finally:
2478 p.Wait()
2479
2480 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002481 if self._bare:
2482 path = os.path.join(self._project.gitdir, HEAD)
2483 else:
2484 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002485 try:
2486 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002487 except IOError as e:
2488 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002489 try:
2490 line = fd.read()
2491 finally:
2492 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302493 try:
2494 line = line.decode()
2495 except AttributeError:
2496 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002497 if line.startswith('ref: '):
2498 return line[5:-1]
2499 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002500
2501 def SetHead(self, ref, message=None):
2502 cmdv = []
2503 if message is not None:
2504 cmdv.extend(['-m', message])
2505 cmdv.append(HEAD)
2506 cmdv.append(ref)
2507 self.symbolic_ref(*cmdv)
2508
2509 def DetachHead(self, new, message=None):
2510 cmdv = ['--no-deref']
2511 if message is not None:
2512 cmdv.extend(['-m', message])
2513 cmdv.append(HEAD)
2514 cmdv.append(new)
2515 self.update_ref(*cmdv)
2516
2517 def UpdateRef(self, name, new, old=None,
2518 message=None,
2519 detach=False):
2520 cmdv = []
2521 if message is not None:
2522 cmdv.extend(['-m', message])
2523 if detach:
2524 cmdv.append('--no-deref')
2525 cmdv.append(name)
2526 cmdv.append(new)
2527 if old is not None:
2528 cmdv.append(old)
2529 self.update_ref(*cmdv)
2530
2531 def DeleteRef(self, name, old=None):
2532 if not old:
2533 old = self.rev_parse(name)
2534 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002535 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002536
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002537 def rev_list(self, *args, **kw):
2538 if 'format' in kw:
2539 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2540 else:
2541 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002542 cmdv.extend(args)
2543 p = GitCommand(self._project,
2544 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002545 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002546 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002547 capture_stdout=True,
2548 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002549 r = []
2550 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002551 if line[-1] == '\n':
2552 line = line[:-1]
2553 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002554 if p.Wait() != 0:
2555 raise GitError('%s rev-list %s: %s' % (
2556 self._project.name,
2557 str(args),
2558 p.stderr))
2559 return r
2560
2561 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002562 """Allow arbitrary git commands using pythonic syntax.
2563
2564 This allows you to do things like:
2565 git_obj.rev_parse('HEAD')
2566
2567 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2568 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002569 Any other positional arguments will be passed to the git command, and the
2570 following keyword arguments are supported:
2571 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002572
2573 Args:
2574 name: The name of the git command to call. Any '_' characters will
2575 be replaced with '-'.
2576
2577 Returns:
2578 A callable object that will try to call git with the named command.
2579 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002580 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002581 def runner(*args, **kwargs):
2582 cmdv = []
2583 config = kwargs.pop('config', None)
2584 for k in kwargs:
2585 raise TypeError('%s() got an unexpected keyword argument %r'
2586 % (name, k))
2587 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002588 if not git_require((1, 7, 2)):
2589 raise ValueError('cannot set config on command line for %s()'
2590 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302591 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002592 cmdv.append('-c')
2593 cmdv.append('%s=%s' % (k, v))
2594 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002595 cmdv.extend(args)
2596 p = GitCommand(self._project,
2597 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002598 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002599 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002600 capture_stdout=True,
2601 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002602 if p.Wait() != 0:
2603 raise GitError('%s %s: %s' % (
2604 self._project.name,
2605 name,
2606 p.stderr))
2607 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302608 try:
Conley Owensedd01512013-09-26 12:59:58 -07002609 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302610 except AttributeError:
2611 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002612 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2613 return r[:-1]
2614 return r
2615 return runner
2616
2617
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002618class _PriorSyncFailedError(Exception):
2619 def __str__(self):
2620 return 'prior sync failed; rebase still in progress'
2621
2622class _DirtyError(Exception):
2623 def __str__(self):
2624 return 'contains uncommitted changes'
2625
2626class _InfoMessage(object):
2627 def __init__(self, project, text):
2628 self.project = project
2629 self.text = text
2630
2631 def Print(self, syncbuf):
2632 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2633 syncbuf.out.nl()
2634
2635class _Failure(object):
2636 def __init__(self, project, why):
2637 self.project = project
2638 self.why = why
2639
2640 def Print(self, syncbuf):
2641 syncbuf.out.fail('error: %s/: %s',
2642 self.project.relpath,
2643 str(self.why))
2644 syncbuf.out.nl()
2645
2646class _Later(object):
2647 def __init__(self, project, action):
2648 self.project = project
2649 self.action = action
2650
2651 def Run(self, syncbuf):
2652 out = syncbuf.out
2653 out.project('project %s/', self.project.relpath)
2654 out.nl()
2655 try:
2656 self.action()
2657 out.nl()
2658 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002659 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002660 out.nl()
2661 return False
2662
2663class _SyncColoring(Coloring):
2664 def __init__(self, config):
2665 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002666 self.project = self.printer('header', attr='bold')
2667 self.info = self.printer('info')
2668 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002669
2670class SyncBuffer(object):
2671 def __init__(self, config, detach_head=False):
2672 self._messages = []
2673 self._failures = []
2674 self._later_queue1 = []
2675 self._later_queue2 = []
2676
2677 self.out = _SyncColoring(config)
2678 self.out.redirect(sys.stderr)
2679
2680 self.detach_head = detach_head
2681 self.clean = True
2682
2683 def info(self, project, fmt, *args):
2684 self._messages.append(_InfoMessage(project, fmt % args))
2685
2686 def fail(self, project, err=None):
2687 self._failures.append(_Failure(project, err))
2688 self.clean = False
2689
2690 def later1(self, project, what):
2691 self._later_queue1.append(_Later(project, what))
2692
2693 def later2(self, project, what):
2694 self._later_queue2.append(_Later(project, what))
2695
2696 def Finish(self):
2697 self._PrintMessages()
2698 self._RunLater()
2699 self._PrintMessages()
2700 return self.clean
2701
2702 def _RunLater(self):
2703 for q in ['_later_queue1', '_later_queue2']:
2704 if not self._RunQueue(q):
2705 return
2706
2707 def _RunQueue(self, queue):
2708 for m in getattr(self, queue):
2709 if not m.Run(self):
2710 self.clean = False
2711 return False
2712 setattr(self, queue, [])
2713 return True
2714
2715 def _PrintMessages(self):
2716 for m in self._messages:
2717 m.Print(self)
2718 for m in self._failures:
2719 m.Print(self)
2720
2721 self._messages = []
2722 self._failures = []
2723
2724
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002725class MetaProject(Project):
2726 """A special project housed under .repo.
2727 """
2728 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002729 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002730 manifest=manifest,
2731 name=name,
2732 gitdir=gitdir,
2733 objdir=gitdir,
2734 worktree=worktree,
2735 remote=RemoteSpec('origin'),
2736 relpath='.repo/%s' % name,
2737 revisionExpr='refs/heads/master',
2738 revisionId=None,
2739 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002740
2741 def PreSync(self):
2742 if self.Exists:
2743 cb = self.CurrentBranch
2744 if cb:
2745 base = self.GetBranch(cb).merge
2746 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002747 self.revisionExpr = base
2748 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002749
Anthony King7bdac712014-07-16 12:56:40 +01002750 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002751 """ Prepare MetaProject for manifest branch switch
2752 """
2753
2754 # detach and delete manifest branch, allowing a new
2755 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002756 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002757 self.Sync_LocalHalf(syncbuf)
2758 syncbuf.Finish()
2759
2760 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002761 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002762 capture_stdout=True,
2763 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002764
2765
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002766 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002767 def LastFetch(self):
2768 try:
2769 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2770 return os.path.getmtime(fh)
2771 except OSError:
2772 return 0
2773
2774 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002775 def HasChanges(self):
2776 """Has the remote received new commits not yet checked out?
2777 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002778 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002779 return False
2780
David Pursehouse8a68ff92012-09-24 12:15:13 +09002781 all_refs = self.bare_ref.all
2782 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002783 head = self.work_git.GetHead()
2784 if head.startswith(R_HEADS):
2785 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002786 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002787 except KeyError:
2788 head = None
2789
2790 if revid == head:
2791 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002792 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002793 return True
2794 return False