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