blob: 3f1e3b6575d38594f9d7ba2418a066337d0f0d69 [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
1900 if is_sha1 and depth:
1901 # 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():
Brian Harring14a66742012-09-28 20:21:57 -07001963 return self._RemoteFetch(name=name, current_branch_only=False,
1964 initial=False, quiet=quiet, alt_dir=alt_dir)
1965
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001966 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001967
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001968 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001969 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001970 return False
1971
1972 remote = self.GetRemote(self.remote.name)
1973 bundle_url = remote.url + '/clone.bundle'
1974 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001975 if GetSchemeFromUrl(bundle_url) not in (
1976 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001977 return False
1978
1979 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1980 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1981
1982 exist_dst = os.path.exists(bundle_dst)
1983 exist_tmp = os.path.exists(bundle_tmp)
1984
1985 if not initial and not exist_dst and not exist_tmp:
1986 return False
1987
1988 if not exist_dst:
1989 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1990 if not exist_dst:
1991 return False
1992
1993 cmd = ['fetch']
1994 if quiet:
1995 cmd.append('--quiet')
1996 if not self.worktree:
1997 cmd.append('--update-head-ok')
1998 cmd.append(bundle_dst)
1999 for f in remote.fetch:
2000 cmd.append(str(f))
2001 cmd.append('refs/tags/*:refs/tags/*')
2002
2003 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002004 if os.path.exists(bundle_dst):
2005 os.remove(bundle_dst)
2006 if os.path.exists(bundle_tmp):
2007 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002008 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002009
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002010 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002011 if os.path.exists(dstPath):
2012 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002013
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002014 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002015 if quiet:
2016 cmd += ['--silent']
2017 if os.path.exists(tmpPath):
2018 size = os.stat(tmpPath).st_size
2019 if size >= 1024:
2020 cmd += ['--continue-at', '%d' % (size,)]
2021 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002022 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002023 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2024 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz497bde42015-01-02 13:58:05 -08002025 with self._GetBundleCookieFile(srcUrl, quiet) as cookiefile:
Dave Borowitz137d0132015-01-02 11:12:54 -08002026 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002027 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002028 if srcUrl.startswith('persistent-'):
2029 srcUrl = srcUrl[len('persistent-'):]
2030 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002031
Dave Borowitz137d0132015-01-02 11:12:54 -08002032 if IsTrace():
2033 Trace('%s', ' '.join(cmd))
2034 try:
2035 proc = subprocess.Popen(cmd)
2036 except OSError:
2037 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002038
Dave Borowitz137d0132015-01-02 11:12:54 -08002039 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002040
Dave Borowitz137d0132015-01-02 11:12:54 -08002041 if curlret == 22:
2042 # From curl man page:
2043 # 22: HTTP page not retrieved. The requested url was not found or
2044 # returned another error with the HTTP error code being 400 or above.
2045 # This return code only appears if -f, --fail is used.
2046 if not quiet:
2047 print("Server does not provide clone.bundle; ignoring.",
2048 file=sys.stderr)
2049 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002050
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002051 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002052 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002053 os.rename(tmpPath, dstPath)
2054 return True
2055 else:
2056 os.remove(tmpPath)
2057 return False
2058 else:
2059 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002060
Kris Giesingc8d882a2014-12-23 13:02:32 -08002061 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002062 try:
2063 with open(path) as f:
2064 if f.read(16) == '# v2 git bundle\n':
2065 return True
2066 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002067 if not quiet:
2068 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002069 return False
2070 except OSError:
2071 return False
2072
Dave Borowitz137d0132015-01-02 11:12:54 -08002073 @contextlib.contextmanager
Dave Borowitz497bde42015-01-02 13:58:05 -08002074 def _GetBundleCookieFile(self, url, quiet):
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002075 if url.startswith('persistent-'):
2076 try:
2077 p = subprocess.Popen(
2078 ['git-remote-persistent-https', '-print_config', url],
2079 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2080 stderr=subprocess.PIPE)
Dave Borowitz137d0132015-01-02 11:12:54 -08002081 try:
2082 prefix = 'http.cookiefile='
2083 cookiefile = None
2084 for line in p.stdout:
2085 line = line.strip()
2086 if line.startswith(prefix):
2087 cookiefile = line[len(prefix):]
2088 break
2089 # Leave subprocess open, as cookie file may be transient.
2090 if cookiefile:
2091 yield cookiefile
2092 return
2093 finally:
2094 p.stdin.close()
2095 if p.wait():
2096 err_msg = p.stderr.read()
2097 if ' -print_config' in err_msg:
2098 pass # Persistent proxy doesn't support -print_config.
Dave Borowitz497bde42015-01-02 13:58:05 -08002099 elif not quiet:
Dave Borowitz137d0132015-01-02 11:12:54 -08002100 print(err_msg, file=sys.stderr)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002101 except OSError as e:
2102 if e.errno == errno.ENOENT:
2103 pass # No persistent proxy.
2104 raise
Dave Borowitz137d0132015-01-02 11:12:54 -08002105 yield GitConfig.ForUser().GetString('http.cookiefile')
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002106
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002107 def _Checkout(self, rev, quiet=False):
2108 cmd = ['checkout']
2109 if quiet:
2110 cmd.append('-q')
2111 cmd.append(rev)
2112 cmd.append('--')
2113 if GitCommand(self, cmd).Wait() != 0:
2114 if self._allrefs:
2115 raise GitError('%s checkout %s ' % (self.name, rev))
2116
Anthony King7bdac712014-07-16 12:56:40 +01002117 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002118 cmd = ['cherry-pick']
2119 cmd.append(rev)
2120 cmd.append('--')
2121 if GitCommand(self, cmd).Wait() != 0:
2122 if self._allrefs:
2123 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2124
Anthony King7bdac712014-07-16 12:56:40 +01002125 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002126 cmd = ['revert']
2127 cmd.append('--no-edit')
2128 cmd.append(rev)
2129 cmd.append('--')
2130 if GitCommand(self, cmd).Wait() != 0:
2131 if self._allrefs:
2132 raise GitError('%s revert %s ' % (self.name, rev))
2133
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134 def _ResetHard(self, rev, quiet=True):
2135 cmd = ['reset', '--hard']
2136 if quiet:
2137 cmd.append('-q')
2138 cmd.append(rev)
2139 if GitCommand(self, cmd).Wait() != 0:
2140 raise GitError('%s reset --hard %s ' % (self.name, rev))
2141
Anthony King7bdac712014-07-16 12:56:40 +01002142 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002143 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002144 if onto is not None:
2145 cmd.extend(['--onto', onto])
2146 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002147 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002148 raise GitError('%s rebase %s ' % (self.name, upstream))
2149
Pierre Tardy3d125942012-05-04 12:18:12 +02002150 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002151 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002152 if ffonly:
2153 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002154 if GitCommand(self, cmd).Wait() != 0:
2155 raise GitError('%s merge %s ' % (self.name, head))
2156
Jonathan Nieder93719792015-03-17 11:29:58 -07002157 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002158 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002159
2160 # Initialize the bare repository, which contains all of the objects.
2161 if not os.path.exists(self.objdir):
2162 os.makedirs(self.objdir)
2163 self.bare_objdir.init()
2164
2165 # If we have a separate directory to hold refs, initialize it as well.
2166 if self.objdir != self.gitdir:
2167 os.makedirs(self.gitdir)
2168 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2169 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002170
Shawn O. Pearce88443382010-10-08 10:02:09 +02002171 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002172 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002173
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002174 if ref_dir or mirror_git:
2175 if not mirror_git:
2176 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002177 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2178 self.relpath + '.git')
2179
2180 if os.path.exists(mirror_git):
2181 ref_dir = mirror_git
2182
2183 elif os.path.exists(repo_git):
2184 ref_dir = repo_git
2185
2186 else:
2187 ref_dir = None
2188
2189 if ref_dir:
2190 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2191 os.path.join(ref_dir, 'objects') + '\n')
2192
Jimmie Westera0444582012-10-24 13:44:42 +02002193 self._UpdateHooks()
2194
2195 m = self.manifest.manifestProject.config
2196 for key in ['user.name', 'user.email']:
Anthony King7bdac712014-07-16 12:56:40 +01002197 if m.Has(key, include_defaults=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002198 self.config.SetString(key, m.GetString(key))
Jonathan Nieder93719792015-03-17 11:29:58 -07002199 if self.manifest.IsMirror:
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002200 self.config.SetString('core.bare', 'true')
2201 else:
2202 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002203
Jimmie Westera0444582012-10-24 13:44:42 +02002204 def _UpdateHooks(self):
2205 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002206 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002207
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002208 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002209 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002210 if not os.path.exists(hooks):
2211 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002212 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002213 name = os.path.basename(stock_hook)
2214
Victor Boivie65e0f352011-04-18 11:23:29 +02002215 if name in ('commit-msg',) and not self.remote.review \
2216 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002217 # Don't install a Gerrit Code Review hook if this
2218 # project does not appear to use it for reviews.
2219 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002220 # Since the manifest project is one of those, but also
2221 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002222 continue
2223
2224 dst = os.path.join(hooks, name)
2225 if os.path.islink(dst):
2226 continue
2227 if os.path.exists(dst):
2228 if filecmp.cmp(stock_hook, dst, shallow=False):
2229 os.remove(dst)
2230 else:
2231 _error("%s: Not replacing %s hook", self.relpath, name)
2232 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002233 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002234 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002235 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002236 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002237 raise GitError('filesystem must support symlinks')
2238 else:
2239 raise
2240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002241 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002242 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002243 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002244 remote.url = self.remote.url
2245 remote.review = self.remote.review
2246 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002247
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002248 if self.worktree:
2249 remote.ResetFetch(mirror=False)
2250 else:
2251 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002252 remote.Save()
2253
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002254 def _InitMRef(self):
2255 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002256 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002257
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002258 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002259 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002260
2261 def _InitAnyMRef(self, ref):
2262 cur = self.bare_ref.symref(ref)
2263
2264 if self.revisionId:
2265 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2266 msg = 'manifest set to %s' % self.revisionId
2267 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002268 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002269 else:
2270 remote = self.GetRemote(self.remote.name)
2271 dst = remote.ToLocal(self.revisionExpr)
2272 if cur != dst:
2273 msg = 'manifest set to %s' % self.revisionExpr
2274 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002275
David James8d201162013-10-11 17:03:19 -07002276 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2277 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2278
2279 Args:
2280 gitdir: The bare git repository. Must already be initialized.
2281 dotgit: The repository you would like to initialize.
2282 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2283 Only one work tree can store refs under a given |gitdir|.
2284 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2285 This saves you the effort of initializing |dotgit| yourself.
2286 """
2287 # These objects can be shared between several working trees.
2288 symlink_files = ['description', 'info']
2289 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2290 if share_refs:
2291 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002292 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002293 symlink_dirs += ['logs', 'refs']
2294 to_symlink = symlink_files + symlink_dirs
2295
2296 to_copy = []
2297 if copy_all:
2298 to_copy = os.listdir(gitdir)
2299
2300 for name in set(to_copy).union(to_symlink):
2301 try:
2302 src = os.path.realpath(os.path.join(gitdir, name))
2303 dst = os.path.realpath(os.path.join(dotgit, name))
2304
2305 if os.path.lexists(dst) and not os.path.islink(dst):
2306 raise GitError('cannot overwrite a local work tree')
2307
2308 # If the source dir doesn't exist, create an empty dir.
2309 if name in symlink_dirs and not os.path.lexists(src):
2310 os.makedirs(src)
2311
Conley Owens80b87fe2014-05-09 17:13:44 -07002312 # If the source file doesn't exist, ensure the destination
2313 # file doesn't either.
2314 if name in symlink_files and not os.path.lexists(src):
2315 try:
2316 os.remove(dst)
2317 except OSError:
2318 pass
2319
David James8d201162013-10-11 17:03:19 -07002320 if name in to_symlink:
2321 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2322 elif copy_all and not os.path.islink(dst):
2323 if os.path.isdir(src):
2324 shutil.copytree(src, dst)
2325 elif os.path.isfile(src):
2326 shutil.copy(src, dst)
2327 except OSError as e:
2328 if e.errno == errno.EPERM:
2329 raise GitError('filesystem must support symlinks')
2330 else:
2331 raise
2332
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002333 def _InitWorkTree(self):
2334 dotgit = os.path.join(self.worktree, '.git')
2335 if not os.path.exists(dotgit):
2336 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002337 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2338 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002339
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002340 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002341
2342 cmd = ['read-tree', '--reset', '-u']
2343 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002344 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002345 if GitCommand(self, cmd).Wait() != 0:
2346 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002347
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002348 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002349
2350 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002351 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002352
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002353 def _revlist(self, *args, **kw):
2354 a = []
2355 a.extend(args)
2356 a.append('--')
2357 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002358
2359 @property
2360 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002361 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002362
Julien Camperguedd654222014-01-09 16:21:37 +01002363 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2364 """Get logs between two revisions of this project."""
2365 comp = '..'
2366 if rev1:
2367 revs = [rev1]
2368 if rev2:
2369 revs.extend([comp, rev2])
2370 cmd = ['log', ''.join(revs)]
2371 out = DiffColoring(self.config)
2372 if out.is_on and color:
2373 cmd.append('--color')
2374 if oneline:
2375 cmd.append('--oneline')
2376
2377 try:
2378 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2379 if log.Wait() == 0:
2380 return log.stdout
2381 except GitError:
2382 # worktree may not exist if groups changed for example. In that case,
2383 # try in gitdir instead.
2384 if not os.path.exists(self.worktree):
2385 return self.bare_git.log(*cmd[1:])
2386 else:
2387 raise
2388 return None
2389
2390 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2391 """Get the list of logs from this revision to given revisionId"""
2392 logs = {}
2393 selfId = self.GetRevisionId(self._allrefs)
2394 toId = toProject.GetRevisionId(toProject._allrefs)
2395
2396 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2397 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2398 return logs
2399
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002400 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002401 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002402 self._project = project
2403 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002404 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002405
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002406 def LsOthers(self):
2407 p = GitCommand(self._project,
2408 ['ls-files',
2409 '-z',
2410 '--others',
2411 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002412 bare=False,
David James8d201162013-10-11 17:03:19 -07002413 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002414 capture_stdout=True,
2415 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002416 if p.Wait() == 0:
2417 out = p.stdout
2418 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002419 return out[:-1].split('\0') # pylint: disable=W1401
2420 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002421 return []
2422
2423 def DiffZ(self, name, *args):
2424 cmd = [name]
2425 cmd.append('-z')
2426 cmd.extend(args)
2427 p = GitCommand(self._project,
2428 cmd,
David James8d201162013-10-11 17:03:19 -07002429 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002430 bare=False,
2431 capture_stdout=True,
2432 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002433 try:
2434 out = p.process.stdout.read()
2435 r = {}
2436 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002437 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002438 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002439 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002440 info = next(out)
2441 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002442 except StopIteration:
2443 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002444
2445 class _Info(object):
2446 def __init__(self, path, omode, nmode, oid, nid, state):
2447 self.path = path
2448 self.src_path = None
2449 self.old_mode = omode
2450 self.new_mode = nmode
2451 self.old_id = oid
2452 self.new_id = nid
2453
2454 if len(state) == 1:
2455 self.status = state
2456 self.level = None
2457 else:
2458 self.status = state[:1]
2459 self.level = state[1:]
2460 while self.level.startswith('0'):
2461 self.level = self.level[1:]
2462
2463 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002464 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002465 if info.status in ('R', 'C'):
2466 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002467 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002468 r[info.path] = info
2469 return r
2470 finally:
2471 p.Wait()
2472
2473 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002474 if self._bare:
2475 path = os.path.join(self._project.gitdir, HEAD)
2476 else:
2477 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002478 try:
2479 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002480 except IOError as e:
2481 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002482 try:
2483 line = fd.read()
2484 finally:
2485 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302486 try:
2487 line = line.decode()
2488 except AttributeError:
2489 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002490 if line.startswith('ref: '):
2491 return line[5:-1]
2492 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002493
2494 def SetHead(self, ref, message=None):
2495 cmdv = []
2496 if message is not None:
2497 cmdv.extend(['-m', message])
2498 cmdv.append(HEAD)
2499 cmdv.append(ref)
2500 self.symbolic_ref(*cmdv)
2501
2502 def DetachHead(self, new, message=None):
2503 cmdv = ['--no-deref']
2504 if message is not None:
2505 cmdv.extend(['-m', message])
2506 cmdv.append(HEAD)
2507 cmdv.append(new)
2508 self.update_ref(*cmdv)
2509
2510 def UpdateRef(self, name, new, old=None,
2511 message=None,
2512 detach=False):
2513 cmdv = []
2514 if message is not None:
2515 cmdv.extend(['-m', message])
2516 if detach:
2517 cmdv.append('--no-deref')
2518 cmdv.append(name)
2519 cmdv.append(new)
2520 if old is not None:
2521 cmdv.append(old)
2522 self.update_ref(*cmdv)
2523
2524 def DeleteRef(self, name, old=None):
2525 if not old:
2526 old = self.rev_parse(name)
2527 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002528 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002529
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002530 def rev_list(self, *args, **kw):
2531 if 'format' in kw:
2532 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2533 else:
2534 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002535 cmdv.extend(args)
2536 p = GitCommand(self._project,
2537 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002538 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002539 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002540 capture_stdout=True,
2541 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002542 r = []
2543 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002544 if line[-1] == '\n':
2545 line = line[:-1]
2546 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002547 if p.Wait() != 0:
2548 raise GitError('%s rev-list %s: %s' % (
2549 self._project.name,
2550 str(args),
2551 p.stderr))
2552 return r
2553
2554 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002555 """Allow arbitrary git commands using pythonic syntax.
2556
2557 This allows you to do things like:
2558 git_obj.rev_parse('HEAD')
2559
2560 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2561 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002562 Any other positional arguments will be passed to the git command, and the
2563 following keyword arguments are supported:
2564 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002565
2566 Args:
2567 name: The name of the git command to call. Any '_' characters will
2568 be replaced with '-'.
2569
2570 Returns:
2571 A callable object that will try to call git with the named command.
2572 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002573 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002574 def runner(*args, **kwargs):
2575 cmdv = []
2576 config = kwargs.pop('config', None)
2577 for k in kwargs:
2578 raise TypeError('%s() got an unexpected keyword argument %r'
2579 % (name, k))
2580 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002581 if not git_require((1, 7, 2)):
2582 raise ValueError('cannot set config on command line for %s()'
2583 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302584 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002585 cmdv.append('-c')
2586 cmdv.append('%s=%s' % (k, v))
2587 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002588 cmdv.extend(args)
2589 p = GitCommand(self._project,
2590 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002591 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002592 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002593 capture_stdout=True,
2594 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002595 if p.Wait() != 0:
2596 raise GitError('%s %s: %s' % (
2597 self._project.name,
2598 name,
2599 p.stderr))
2600 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302601 try:
Conley Owensedd01512013-09-26 12:59:58 -07002602 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302603 except AttributeError:
2604 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002605 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2606 return r[:-1]
2607 return r
2608 return runner
2609
2610
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002611class _PriorSyncFailedError(Exception):
2612 def __str__(self):
2613 return 'prior sync failed; rebase still in progress'
2614
2615class _DirtyError(Exception):
2616 def __str__(self):
2617 return 'contains uncommitted changes'
2618
2619class _InfoMessage(object):
2620 def __init__(self, project, text):
2621 self.project = project
2622 self.text = text
2623
2624 def Print(self, syncbuf):
2625 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2626 syncbuf.out.nl()
2627
2628class _Failure(object):
2629 def __init__(self, project, why):
2630 self.project = project
2631 self.why = why
2632
2633 def Print(self, syncbuf):
2634 syncbuf.out.fail('error: %s/: %s',
2635 self.project.relpath,
2636 str(self.why))
2637 syncbuf.out.nl()
2638
2639class _Later(object):
2640 def __init__(self, project, action):
2641 self.project = project
2642 self.action = action
2643
2644 def Run(self, syncbuf):
2645 out = syncbuf.out
2646 out.project('project %s/', self.project.relpath)
2647 out.nl()
2648 try:
2649 self.action()
2650 out.nl()
2651 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002652 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002653 out.nl()
2654 return False
2655
2656class _SyncColoring(Coloring):
2657 def __init__(self, config):
2658 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002659 self.project = self.printer('header', attr='bold')
2660 self.info = self.printer('info')
2661 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002662
2663class SyncBuffer(object):
2664 def __init__(self, config, detach_head=False):
2665 self._messages = []
2666 self._failures = []
2667 self._later_queue1 = []
2668 self._later_queue2 = []
2669
2670 self.out = _SyncColoring(config)
2671 self.out.redirect(sys.stderr)
2672
2673 self.detach_head = detach_head
2674 self.clean = True
2675
2676 def info(self, project, fmt, *args):
2677 self._messages.append(_InfoMessage(project, fmt % args))
2678
2679 def fail(self, project, err=None):
2680 self._failures.append(_Failure(project, err))
2681 self.clean = False
2682
2683 def later1(self, project, what):
2684 self._later_queue1.append(_Later(project, what))
2685
2686 def later2(self, project, what):
2687 self._later_queue2.append(_Later(project, what))
2688
2689 def Finish(self):
2690 self._PrintMessages()
2691 self._RunLater()
2692 self._PrintMessages()
2693 return self.clean
2694
2695 def _RunLater(self):
2696 for q in ['_later_queue1', '_later_queue2']:
2697 if not self._RunQueue(q):
2698 return
2699
2700 def _RunQueue(self, queue):
2701 for m in getattr(self, queue):
2702 if not m.Run(self):
2703 self.clean = False
2704 return False
2705 setattr(self, queue, [])
2706 return True
2707
2708 def _PrintMessages(self):
2709 for m in self._messages:
2710 m.Print(self)
2711 for m in self._failures:
2712 m.Print(self)
2713
2714 self._messages = []
2715 self._failures = []
2716
2717
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002718class MetaProject(Project):
2719 """A special project housed under .repo.
2720 """
2721 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002722 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002723 manifest=manifest,
2724 name=name,
2725 gitdir=gitdir,
2726 objdir=gitdir,
2727 worktree=worktree,
2728 remote=RemoteSpec('origin'),
2729 relpath='.repo/%s' % name,
2730 revisionExpr='refs/heads/master',
2731 revisionId=None,
2732 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002733
2734 def PreSync(self):
2735 if self.Exists:
2736 cb = self.CurrentBranch
2737 if cb:
2738 base = self.GetBranch(cb).merge
2739 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002740 self.revisionExpr = base
2741 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002742
Anthony King7bdac712014-07-16 12:56:40 +01002743 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002744 """ Prepare MetaProject for manifest branch switch
2745 """
2746
2747 # detach and delete manifest branch, allowing a new
2748 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002749 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002750 self.Sync_LocalHalf(syncbuf)
2751 syncbuf.Finish()
2752
2753 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002754 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002755 capture_stdout=True,
2756 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002757
2758
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002759 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002760 def LastFetch(self):
2761 try:
2762 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2763 return os.path.getmtime(fh)
2764 except OSError:
2765 return 0
2766
2767 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002768 def HasChanges(self):
2769 """Has the remote received new commits not yet checked out?
2770 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002771 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002772 return False
2773
David Pursehouse8a68ff92012-09-24 12:15:13 +09002774 all_refs = self.bare_ref.all
2775 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002776 head = self.work_git.GetHead()
2777 if head.startswith(R_HEADS):
2778 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002779 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002780 except KeyError:
2781 head = None
2782
2783 if revid == head:
2784 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002785 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002786 return True
2787 return False