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