blob: 00f6b90457a1d9056e4a615e2cffec626a80a2cb [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Dave Borowitz137d0132015-01-02 11:12:54 -080016import contextlib
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080017import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070019import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070021import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import re
23import shutil
24import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070025import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070026import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020027import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080028import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070029import time
Dave Borowitz137d0132015-01-02 11:12:54 -080030import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070031
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070033from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070034from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090035from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080036from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080037from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070038from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
Shawn O. Pearced237b692009-04-17 18:49:50 -070040from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
David Pursehouse59bbb582013-05-17 10:49:33 +090042from pyversion import is_python3
43if not is_python3():
44 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053045 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090046 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053047
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070048def _lwrite(path, content):
49 lock = '%s.lock' % path
50
Chirayu Desai303a82f2014-08-19 22:57:17 +053051 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070052 try:
53 fd.write(content)
54 finally:
55 fd.close()
56
57 try:
58 os.rename(lock, path)
59 except OSError:
60 os.remove(lock)
61 raise
62
Shawn O. Pearce48244782009-04-16 08:25:57 -070063def _error(fmt, *args):
64 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070065 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070066
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070067def not_rev(r):
68 return '^' + r
69
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080070def sq(r):
71 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080072
Jonathan Nieder93719792015-03-17 11:29:58 -070073_project_hook_list = None
74def _ProjectHooks():
75 """List the hooks present in the 'hooks' directory.
76
77 These hooks are project hooks and are copied to the '.git/hooks' directory
78 of all subprojects.
79
80 This function caches the list of hooks (based on the contents of the
81 'repo/hooks' directory) on the first call.
82
83 Returns:
84 A list of absolute paths to all of the files in the hooks directory.
85 """
86 global _project_hook_list
87 if _project_hook_list is None:
88 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
89 d = os.path.join(d, 'hooks')
90 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
91 return _project_hook_list
92
93
Shawn O. Pearce632768b2008-10-23 11:58:52 -070094class DownloadedChange(object):
95 _commit_cache = None
96
97 def __init__(self, project, base, change_id, ps_id, commit):
98 self.project = project
99 self.base = base
100 self.change_id = change_id
101 self.ps_id = ps_id
102 self.commit = commit
103
104 @property
105 def commits(self):
106 if self._commit_cache is None:
107 self._commit_cache = self.project.bare_git.rev_list(
108 '--abbrev=8',
109 '--abbrev-commit',
110 '--pretty=oneline',
111 '--reverse',
112 '--date-order',
113 not_rev(self.base),
114 self.commit,
115 '--')
116 return self._commit_cache
117
118
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700119class ReviewableBranch(object):
120 _commit_cache = None
121
122 def __init__(self, project, branch, base):
123 self.project = project
124 self.branch = branch
125 self.base = base
126
127 @property
128 def name(self):
129 return self.branch.name
130
131 @property
132 def commits(self):
133 if self._commit_cache is None:
134 self._commit_cache = self.project.bare_git.rev_list(
135 '--abbrev=8',
136 '--abbrev-commit',
137 '--pretty=oneline',
138 '--reverse',
139 '--date-order',
140 not_rev(self.base),
141 R_HEADS + self.name,
142 '--')
143 return self._commit_cache
144
145 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800146 def unabbrev_commits(self):
147 r = dict()
148 for commit in self.project.bare_git.rev_list(
149 not_rev(self.base),
150 R_HEADS + self.name,
151 '--'):
152 r[commit[0:8]] = commit
153 return r
154
155 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700156 def date(self):
157 return self.project.bare_git.log(
158 '--pretty=format:%cd',
159 '-n', '1',
160 R_HEADS + self.name,
161 '--')
162
Bryan Jacobsf609f912013-05-06 13:36:24 -0400163 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800164 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700165 people,
Brian Harring435370c2012-07-28 15:37:04 -0700166 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400167 draft=draft,
168 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700170 def GetPublishedRefs(self):
171 refs = {}
172 output = self.project.bare_git.ls_remote(
173 self.branch.remote.SshReviewUrl(self.project.UserEmail),
174 'refs/changes/*')
175 for line in output.split('\n'):
176 try:
177 (sha, ref) = line.split()
178 refs[sha] = ref
179 except ValueError:
180 pass
181
182 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700183
184class StatusColoring(Coloring):
185 def __init__(self, config):
186 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100187 self.project = self.printer('header', attr='bold')
188 self.branch = self.printer('header', attr='bold')
189 self.nobranch = self.printer('nobranch', fg='red')
190 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700191
Anthony King7bdac712014-07-16 12:56:40 +0100192 self.added = self.printer('added', fg='green')
193 self.changed = self.printer('changed', fg='red')
194 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195
196
197class DiffColoring(Coloring):
198 def __init__(self, config):
199 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100200 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700201
Anthony King7bdac712014-07-16 12:56:40 +0100202class _Annotation(object):
James W. Mills24c13082012-04-12 15:04:13 -0500203 def __init__(self, name, value, keep):
204 self.name = name
205 self.value = value
206 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207
Anthony King7bdac712014-07-16 12:56:40 +0100208class _CopyFile(object):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800209 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210 self.src = src
211 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800212 self.abs_src = abssrc
213 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214
215 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800216 src = self.abs_src
217 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218 # copy file if it does not exist or is out of date
219 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
220 try:
221 # remove existing file first, since it might be read-only
222 if os.path.exists(dest):
223 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400224 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200225 dest_dir = os.path.dirname(dest)
226 if not os.path.isdir(dest_dir):
227 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228 shutil.copy(src, dest)
229 # make the file read-only
230 mode = os.stat(dest)[stat.ST_MODE]
231 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
232 os.chmod(dest, mode)
233 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700234 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235
Anthony King7bdac712014-07-16 12:56:40 +0100236class _LinkFile(object):
Wink Saville4c426ef2015-06-03 08:05:17 -0700237 def __init__(self, git_worktree, src, dest, relsrc, absdest):
238 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500239 self.src = src
240 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700241 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500242 self.abs_dest = absdest
243
Wink Saville4c426ef2015-06-03 08:05:17 -0700244 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500245 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700246 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500247 try:
248 # remove existing file first, since it might be read-only
Wink Saville4c426ef2015-06-03 08:05:17 -0700249 if os.path.exists(absDest):
250 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500251 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700252 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500253 if not os.path.isdir(dest_dir):
254 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700255 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500256 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700257 _error('Cannot link file %s to %s', relSrc, absDest)
258
259 def _Link(self):
260 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
261 on the src linking all of the files in the source in to the destination
262 directory.
263 """
264 # We use the absSrc to handle the situation where the current directory
265 # is not the root of the repo
266 absSrc = os.path.join(self.git_worktree, self.src)
267 if os.path.exists(absSrc):
268 # Entity exists so just a simple one to one link operation
269 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
270 else:
271 # Entity doesn't exist assume there is a wild card
272 absDestDir = self.abs_dest
273 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
274 _error('Link error: src with wildcard, %s must be a directory',
275 absDestDir)
276 else:
277 absSrcFiles = glob.glob(absSrc)
278 for absSrcFile in absSrcFiles:
279 # Create a releative path from source dir to destination dir
280 absSrcDir = os.path.dirname(absSrcFile)
281 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
282
283 # Get the source file name
284 srcFile = os.path.basename(absSrcFile)
285
286 # Now form the final full paths to srcFile. They will be
287 # absolute for the desintaiton and relative for the srouce.
288 absDest = os.path.join(absDestDir, srcFile)
289 relSrc = os.path.join(relSrcDir, srcFile)
290 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500291
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700292class RemoteSpec(object):
293 def __init__(self,
294 name,
Anthony King7bdac712014-07-16 12:56:40 +0100295 url=None,
296 review=None,
297 revision=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700298 self.name = name
299 self.url = url
300 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100301 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700302
Doug Anderson37282b42011-03-04 11:54:18 -0800303class RepoHook(object):
304 """A RepoHook contains information about a script to run as a hook.
305
306 Hooks are used to run a python script before running an upload (for instance,
307 to run presubmit checks). Eventually, we may have hooks for other actions.
308
309 This shouldn't be confused with files in the 'repo/hooks' directory. Those
310 files are copied into each '.git/hooks' folder for each project. Repo-level
311 hooks are associated instead with repo actions.
312
313 Hooks are always python. When a hook is run, we will load the hook into the
314 interpreter and execute its main() function.
315 """
316 def __init__(self,
317 hook_type,
318 hooks_project,
319 topdir,
320 abort_if_user_denies=False):
321 """RepoHook constructor.
322
323 Params:
324 hook_type: A string representing the type of hook. This is also used
325 to figure out the name of the file containing the hook. For
326 example: 'pre-upload'.
327 hooks_project: The project containing the repo hooks. If you have a
328 manifest, this is manifest.repo_hooks_project. OK if this is None,
329 which will make the hook a no-op.
330 topdir: Repo's top directory (the one containing the .repo directory).
331 Scripts will run with CWD as this directory. If you have a manifest,
332 this is manifest.topdir
333 abort_if_user_denies: If True, we'll throw a HookError() if the user
334 doesn't allow us to run the hook.
335 """
336 self._hook_type = hook_type
337 self._hooks_project = hooks_project
338 self._topdir = topdir
339 self._abort_if_user_denies = abort_if_user_denies
340
341 # Store the full path to the script for convenience.
342 if self._hooks_project:
343 self._script_fullpath = os.path.join(self._hooks_project.worktree,
344 self._hook_type + '.py')
345 else:
346 self._script_fullpath = None
347
348 def _GetHash(self):
349 """Return a hash of the contents of the hooks directory.
350
351 We'll just use git to do this. This hash has the property that if anything
352 changes in the directory we will return a different has.
353
354 SECURITY CONSIDERATION:
355 This hash only represents the contents of files in the hook directory, not
356 any other files imported or called by hooks. Changes to imported files
357 can change the script behavior without affecting the hash.
358
359 Returns:
360 A string representing the hash. This will always be ASCII so that it can
361 be printed to the user easily.
362 """
363 assert self._hooks_project, "Must have hooks to calculate their hash."
364
365 # We will use the work_git object rather than just calling GetRevisionId().
366 # That gives us a hash of the latest checked in version of the files that
367 # the user will actually be executing. Specifically, GetRevisionId()
368 # doesn't appear to change even if a user checks out a different version
369 # of the hooks repo (via git checkout) nor if a user commits their own revs.
370 #
371 # NOTE: Local (non-committed) changes will not be factored into this hash.
372 # I think this is OK, since we're really only worried about warning the user
373 # about upstream changes.
374 return self._hooks_project.work_git.rev_parse('HEAD')
375
376 def _GetMustVerb(self):
377 """Return 'must' if the hook is required; 'should' if not."""
378 if self._abort_if_user_denies:
379 return 'must'
380 else:
381 return 'should'
382
383 def _CheckForHookApproval(self):
384 """Check to see whether this hook has been approved.
385
386 We'll look at the hash of all of the hooks. If this matches the hash that
387 the user last approved, we're done. If it doesn't, we'll ask the user
388 about approval.
389
390 Note that we ask permission for each individual hook even though we use
391 the hash of all hooks when detecting changes. We'd like the user to be
392 able to approve / deny each hook individually. We only use the hash of all
393 hooks because there is no other easy way to detect changes to local imports.
394
395 Returns:
396 True if this hook is approved to run; False otherwise.
397
398 Raises:
399 HookError: Raised if the user doesn't approve and abort_if_user_denies
400 was passed to the consturctor.
401 """
Doug Anderson37282b42011-03-04 11:54:18 -0800402 hooks_config = self._hooks_project.config
403 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
404
405 # Get the last hash that the user approved for this hook; may be None.
406 old_hash = hooks_config.GetString(git_approval_key)
407
408 # Get the current hash so we can tell if scripts changed since approval.
409 new_hash = self._GetHash()
410
411 if old_hash is not None:
412 # User previously approved hook and asked not to be prompted again.
413 if new_hash == old_hash:
414 # Approval matched. We're done.
415 return True
416 else:
417 # Give the user a reason why we're prompting, since they last told
418 # us to "never ask again".
419 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
420 self._hook_type)
421 else:
422 prompt = ''
423
424 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
425 if sys.stdout.isatty():
426 prompt += ('Repo %s run the script:\n'
427 ' %s\n'
428 '\n'
429 'Do you want to allow this script to run '
430 '(yes/yes-never-ask-again/NO)? ') % (
431 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530432 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900433 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800434
435 # User is doing a one-time approval.
436 if response in ('y', 'yes'):
437 return True
438 elif response == 'yes-never-ask-again':
439 hooks_config.SetString(git_approval_key, new_hash)
440 return True
441
442 # For anything else, we'll assume no approval.
443 if self._abort_if_user_denies:
444 raise HookError('You must allow the %s hook or use --no-verify.' %
445 self._hook_type)
446
447 return False
448
449 def _ExecuteHook(self, **kwargs):
450 """Actually execute the given hook.
451
452 This will run the hook's 'main' function in our python interpreter.
453
454 Args:
455 kwargs: Keyword arguments to pass to the hook. These are often specific
456 to the hook type. For instance, pre-upload hooks will contain
457 a project_list.
458 """
459 # Keep sys.path and CWD stashed away so that we can always restore them
460 # upon function exit.
461 orig_path = os.getcwd()
462 orig_syspath = sys.path
463
464 try:
465 # Always run hooks with CWD as topdir.
466 os.chdir(self._topdir)
467
468 # Put the hook dir as the first item of sys.path so hooks can do
469 # relative imports. We want to replace the repo dir as [0] so
470 # hooks can't import repo files.
471 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
472
473 # Exec, storing global context in the context dict. We catch exceptions
474 # and convert to a HookError w/ just the failing traceback.
475 context = {}
476 try:
Anthony King70f68902014-05-05 21:15:34 +0100477 exec(compile(open(self._script_fullpath).read(),
478 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800479 except Exception:
480 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
481 traceback.format_exc(), self._hook_type))
482
483 # Running the script should have defined a main() function.
484 if 'main' not in context:
485 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
486
487
488 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
489 # We don't actually want hooks to define their main with this argument--
490 # it's there to remind them that their hook should always take **kwargs.
491 # For instance, a pre-upload hook should be defined like:
492 # def main(project_list, **kwargs):
493 #
494 # This allows us to later expand the API without breaking old hooks.
495 kwargs = kwargs.copy()
496 kwargs['hook_should_take_kwargs'] = True
497
498 # Call the main function in the hook. If the hook should cause the
499 # build to fail, it will raise an Exception. We'll catch that convert
500 # to a HookError w/ just the failing traceback.
501 try:
502 context['main'](**kwargs)
503 except Exception:
504 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
505 'above.' % (
506 traceback.format_exc(), self._hook_type))
507 finally:
508 # Restore sys.path and CWD.
509 sys.path = orig_syspath
510 os.chdir(orig_path)
511
512 def Run(self, user_allows_all_hooks, **kwargs):
513 """Run the hook.
514
515 If the hook doesn't exist (because there is no hooks project or because
516 this particular hook is not enabled), this is a no-op.
517
518 Args:
519 user_allows_all_hooks: If True, we will never prompt about running the
520 hook--we'll just assume it's OK to run it.
521 kwargs: Keyword arguments to pass to the hook. These are often specific
522 to the hook type. For instance, pre-upload hooks will contain
523 a project_list.
524
525 Raises:
526 HookError: If there was a problem finding the hook or the user declined
527 to run a required hook (from _CheckForHookApproval).
528 """
529 # No-op if there is no hooks project or if hook is disabled.
530 if ((not self._hooks_project) or
531 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
532 return
533
534 # Bail with a nice error if we can't find the hook.
535 if not os.path.isfile(self._script_fullpath):
536 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
537
538 # Make sure the user is OK with running the hook.
539 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
540 return
541
542 # Run the hook with the same version of python we're using.
543 self._ExecuteHook(**kwargs)
544
545
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700546class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600547 # These objects can be shared between several working trees.
548 shareable_files = ['description', 'info']
549 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
550 # These objects can only be used by a single working tree.
551 working_tree_files = ['config', 'packed-refs', 'shallow']
552 working_tree_dirs = ['logs', 'refs']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700553 def __init__(self,
554 manifest,
555 name,
556 remote,
557 gitdir,
David James8d201162013-10-11 17:03:19 -0700558 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700559 worktree,
560 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700561 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800562 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100563 rebase=True,
564 groups=None,
565 sync_c=False,
566 sync_s=False,
567 clone_depth=None,
568 upstream=None,
569 parent=None,
570 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900571 dest_branch=None,
572 optimized_fetch=False):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800573 """Init a Project object.
574
575 Args:
576 manifest: The XmlManifest object.
577 name: The `name` attribute of manifest.xml's project element.
578 remote: RemoteSpec object specifying its remote's properties.
579 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700580 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800581 worktree: Absolute path of git working tree.
582 relpath: Relative path of git working tree to repo's top directory.
583 revisionExpr: The `revision` attribute of manifest.xml's project element.
584 revisionId: git commit id for checking out.
585 rebase: The `rebase` attribute of manifest.xml's project element.
586 groups: The `groups` attribute of manifest.xml's project element.
587 sync_c: The `sync-c` attribute of manifest.xml's project element.
588 sync_s: The `sync-s` attribute of manifest.xml's project element.
589 upstream: The `upstream` attribute of manifest.xml's project element.
590 parent: The parent Project object.
591 is_derived: False if the project was explicitly defined in the manifest;
592 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400593 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900594 optimized_fetch: If True, when a project is set to a sha1 revision, only
595 fetch from the remote if the sha1 is not present locally.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800596 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700597 self.manifest = manifest
598 self.name = name
599 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800600 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700601 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800602 if worktree:
603 self.worktree = worktree.replace('\\', '/')
604 else:
605 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700606 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700607 self.revisionExpr = revisionExpr
608
609 if revisionId is None \
610 and revisionExpr \
611 and IsId(revisionExpr):
612 self.revisionId = revisionExpr
613 else:
614 self.revisionId = revisionId
615
Mike Pontillod3153822012-02-28 11:53:24 -0800616 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700617 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700618 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800619 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900620 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700621 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800622 self.parent = parent
623 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900624 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800625 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800626
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700627 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700628 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500629 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500630 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700631 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100632 gitdir=self.gitdir,
633 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700634
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800635 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700636 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800637 else:
638 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700639 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700640 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700641 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400642 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700643
Doug Anderson37282b42011-03-04 11:54:18 -0800644 # This will be filled in if a project is later identified to be the
645 # project containing repo hooks.
646 self.enabled_repo_hooks = []
647
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700648 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800649 def Derived(self):
650 return self.is_derived
651
652 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600654 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700655
656 @property
657 def CurrentBranch(self):
658 """Obtain the name of the currently checked out branch.
659 The branch name omits the 'refs/heads/' prefix.
660 None is returned if the project is on a detached HEAD.
661 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700662 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700663 if b.startswith(R_HEADS):
664 return b[len(R_HEADS):]
665 return None
666
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700667 def IsRebaseInProgress(self):
668 w = self.worktree
669 g = os.path.join(w, '.git')
670 return os.path.exists(os.path.join(g, 'rebase-apply')) \
671 or os.path.exists(os.path.join(g, 'rebase-merge')) \
672 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200673
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700674 def IsDirty(self, consider_untracked=True):
675 """Is the working directory modified in some way?
676 """
677 self.work_git.update_index('-q',
678 '--unmerged',
679 '--ignore-missing',
680 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900681 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700682 return True
683 if self.work_git.DiffZ('diff-files'):
684 return True
685 if consider_untracked and self.work_git.LsOthers():
686 return True
687 return False
688
689 _userident_name = None
690 _userident_email = None
691
692 @property
693 def UserName(self):
694 """Obtain the user's personal name.
695 """
696 if self._userident_name is None:
697 self._LoadUserIdentity()
698 return self._userident_name
699
700 @property
701 def UserEmail(self):
702 """Obtain the user's email address. This is very likely
703 to be their Gerrit login.
704 """
705 if self._userident_email is None:
706 self._LoadUserIdentity()
707 return self._userident_email
708
709 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900710 u = self.bare_git.var('GIT_COMMITTER_IDENT')
711 m = re.compile("^(.*) <([^>]*)> ").match(u)
712 if m:
713 self._userident_name = m.group(1)
714 self._userident_email = m.group(2)
715 else:
716 self._userident_name = ''
717 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700718
719 def GetRemote(self, name):
720 """Get the configuration for a single remote.
721 """
722 return self.config.GetRemote(name)
723
724 def GetBranch(self, name):
725 """Get the configuration for a single branch.
726 """
727 return self.config.GetBranch(name)
728
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700729 def GetBranches(self):
730 """Get all existing local branches.
731 """
732 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900733 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700734 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700735
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530736 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700737 if name.startswith(R_HEADS):
738 name = name[len(R_HEADS):]
739 b = self.GetBranch(name)
740 b.current = name == current
741 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900742 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700743 heads[name] = b
744
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530745 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700746 if name.startswith(R_PUB):
747 name = name[len(R_PUB):]
748 b = heads.get(name)
749 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900750 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700751
752 return heads
753
Colin Cross5acde752012-03-28 20:15:45 -0700754 def MatchesGroups(self, manifest_groups):
755 """Returns true if the manifest groups specified at init should cause
756 this project to be synced.
757 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700758 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700759
760 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700761 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700762 manifest_groups: "-group1,group2"
763 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500764
765 The special manifest group "default" will match any project that
766 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700767 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500768 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700769 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500770 if not 'notdefault' in expanded_project_groups:
771 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700772
Conley Owens971de8e2012-04-16 10:36:08 -0700773 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700774 for group in expanded_manifest_groups:
775 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700776 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700777 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700778 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700779
Conley Owens971de8e2012-04-16 10:36:08 -0700780 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700781
782## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700783 def UncommitedFiles(self, get_all=True):
784 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700785
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700786 Args:
787 get_all: a boolean, if True - get information about all different
788 uncommitted files. If False - return as soon as any kind of
789 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500790 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700791 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500792 self.work_git.update_index('-q',
793 '--unmerged',
794 '--ignore-missing',
795 '--refresh')
796 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700797 details.append("rebase in progress")
798 if not get_all:
799 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500800
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700801 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
802 if changes:
803 details.extend(changes)
804 if not get_all:
805 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500806
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700807 changes = self.work_git.DiffZ('diff-files').keys()
808 if changes:
809 details.extend(changes)
810 if not get_all:
811 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500812
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700813 changes = self.work_git.LsOthers()
814 if changes:
815 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500816
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700817 return details
818
819 def HasChanges(self):
820 """Returns true if there are uncommitted changes.
821 """
822 if self.UncommitedFiles(get_all=False):
823 return True
824 else:
825 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500826
Terence Haddock4655e812011-03-31 12:33:34 +0200827 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200829
830 Args:
831 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 """
833 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200834 if output_redir == None:
835 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700836 print(file=output_redir)
837 print('project %s/' % self.relpath, file=output_redir)
838 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839 return
840
841 self.work_git.update_index('-q',
842 '--unmerged',
843 '--ignore-missing',
844 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700845 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
847 df = self.work_git.DiffZ('diff-files')
848 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100849 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700850 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700851
852 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200853 if not output_redir == None:
854 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700855 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700856
857 branch = self.CurrentBranch
858 if branch is None:
859 out.nobranch('(*** NO BRANCH ***)')
860 else:
861 out.branch('branch %s', branch)
862 out.nl()
863
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700864 if rb:
865 out.important('prior sync failed; rebase still in progress')
866 out.nl()
867
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868 paths = list()
869 paths.extend(di.keys())
870 paths.extend(df.keys())
871 paths.extend(do)
872
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530873 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900874 try:
875 i = di[p]
876 except KeyError:
877 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900879 try:
880 f = df[p]
881 except KeyError:
882 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200883
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900884 if i:
885 i_status = i.status.upper()
886 else:
887 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900889 if f:
890 f_status = f.status.lower()
891 else:
892 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893
894 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800895 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896 i.src_path, p, i.level)
897 else:
898 line = ' %s%s\t%s' % (i_status, f_status, p)
899
900 if i and not f:
901 out.added('%s', line)
902 elif (i and f) or (not i and f):
903 out.changed('%s', line)
904 elif not i and not f:
905 out.untracked('%s', line)
906 else:
907 out.write('%s', line)
908 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200909
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700910 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911
pelyad67872d2012-03-28 14:49:58 +0300912 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913 """Prints the status of the repository to stdout.
914 """
915 out = DiffColoring(self.config)
916 cmd = ['diff']
917 if out.is_on:
918 cmd.append('--color')
919 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300920 if absolute_paths:
921 cmd.append('--src-prefix=a/%s/' % self.relpath)
922 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923 cmd.append('--')
924 p = GitCommand(self,
925 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100926 capture_stdout=True,
927 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928 has_diff = False
929 for line in p.process.stdout:
930 if not has_diff:
931 out.nl()
932 out.project('project %s/' % self.relpath)
933 out.nl()
934 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700935 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936 p.Wait()
937
938
939## Publish / Upload ##
940
David Pursehouse8a68ff92012-09-24 12:15:13 +0900941 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 """Was the branch published (uploaded) for code review?
943 If so, returns the SHA-1 hash of the last published
944 state for the branch.
945 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700946 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900947 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700948 try:
949 return self.bare_git.rev_parse(key)
950 except GitError:
951 return None
952 else:
953 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900954 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700955 except KeyError:
956 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957
David Pursehouse8a68ff92012-09-24 12:15:13 +0900958 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700959 """Prunes any stale published refs.
960 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900961 if all_refs is None:
962 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963 heads = set()
964 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530965 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966 if name.startswith(R_HEADS):
967 heads.add(name)
968 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900969 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530971 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 n = name[len(R_PUB):]
973 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900974 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700975
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700976 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700977 """List any branches which can be uploaded for review.
978 """
979 heads = {}
980 pubed = {}
981
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530982 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900984 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700985 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900986 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987
988 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530989 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900990 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700992 if selected_branch and branch != selected_branch:
993 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700994
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800995 rb = self.GetUploadableBranch(branch)
996 if rb:
997 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998 return ready
999
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001000 def GetUploadableBranch(self, branch_name):
1001 """Get a single uploadable branch, or None.
1002 """
1003 branch = self.GetBranch(branch_name)
1004 base = branch.LocalMerge
1005 if branch.LocalMerge:
1006 rb = ReviewableBranch(self, branch, base)
1007 if rb.commits:
1008 return rb
1009 return None
1010
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001011 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001012 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001013 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001014 draft=False,
1015 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016 """Uploads the named branch for code review.
1017 """
1018 if branch is None:
1019 branch = self.CurrentBranch
1020 if branch is None:
1021 raise GitError('not currently on a branch')
1022
1023 branch = self.GetBranch(branch)
1024 if not branch.LocalMerge:
1025 raise GitError('branch %s does not track a remote' % branch.name)
1026 if not branch.remote.review:
1027 raise GitError('remote %s has no review url' % branch.remote.name)
1028
Bryan Jacobsf609f912013-05-06 13:36:24 -04001029 if dest_branch is None:
1030 dest_branch = self.dest_branch
1031 if dest_branch is None:
1032 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001033 if not dest_branch.startswith(R_HEADS):
1034 dest_branch = R_HEADS + dest_branch
1035
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001036 if not branch.remote.projectname:
1037 branch.remote.projectname = self.name
1038 branch.remote.Save()
1039
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001040 url = branch.remote.ReviewUrl(self.UserEmail)
1041 if url is None:
1042 raise UploadError('review not configured')
1043 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001044
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001045 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001046 rp = ['gerrit receive-pack']
1047 for e in people[0]:
1048 rp.append('--reviewer=%s' % sq(e))
1049 for e in people[1]:
1050 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001051 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001052
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001053 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001054
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001055 if dest_branch.startswith(R_HEADS):
1056 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001057
1058 upload_type = 'for'
1059 if draft:
1060 upload_type = 'drafts'
1061
1062 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1063 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001064 if auto_topic:
1065 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001066 if not url.startswith('ssh://'):
1067 rp = ['r=%s' % p for p in people[0]] + \
1068 ['cc=%s' % p for p in people[1]]
1069 if rp:
1070 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001071 cmd.append(ref_spec)
1072
Anthony King7bdac712014-07-16 12:56:40 +01001073 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001074 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075
1076 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1077 self.bare_git.UpdateRef(R_PUB + branch.name,
1078 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001079 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080
1081
1082## Sync ##
1083
Julien Campergue335f5ef2013-10-16 11:02:35 +02001084 def _ExtractArchive(self, tarpath, path=None):
1085 """Extract the given tar on its current location
1086
1087 Args:
1088 - tarpath: The path to the actual tar file
1089
1090 """
1091 try:
1092 with tarfile.open(tarpath, 'r') as tar:
1093 tar.extractall(path=path)
1094 return True
1095 except (IOError, tarfile.TarError) as e:
1096 print("error: Cannot extract archive %s: "
1097 "%s" % (tarpath, str(e)), file=sys.stderr)
1098 return False
1099
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001100 def Sync_NetworkHalf(self,
1101 quiet=False,
1102 is_new=None,
1103 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001104 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001105 no_tags=False,
David Pursehouseb1553542014-09-04 21:28:09 +09001106 archive=False,
1107 optimized_fetch=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108 """Perform only the network IO portion of the sync process.
1109 Local working directory/branch state is not affected.
1110 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001111 if archive and not isinstance(self, MetaProject):
1112 if self.remote.url.startswith(('http://', 'https://')):
1113 print("error: %s: Cannot fetch archives from http/https "
1114 "remotes." % self.name, file=sys.stderr)
1115 return False
1116
1117 name = self.relpath.replace('\\', '/')
1118 name = name.replace('/', '_')
1119 tarpath = '%s.tar' % name
1120 topdir = self.manifest.topdir
1121
1122 try:
1123 self._FetchArchive(tarpath, cwd=topdir)
1124 except GitError as e:
1125 print('error: %s' % str(e), file=sys.stderr)
1126 return False
1127
1128 # From now on, we only need absolute tarpath
1129 tarpath = os.path.join(topdir, tarpath)
1130
1131 if not self._ExtractArchive(tarpath, path=topdir):
1132 return False
1133 try:
1134 os.remove(tarpath)
1135 except OSError as e:
1136 print("warn: Cannot remove archive %s: "
1137 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001138 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001139 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001140 if is_new is None:
1141 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001142 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001143 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001144 else:
1145 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001147
1148 if is_new:
1149 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1150 try:
1151 fd = open(alt, 'rb')
1152 try:
1153 alt_dir = fd.readline().rstrip()
1154 finally:
1155 fd.close()
1156 except IOError:
1157 alt_dir = None
1158 else:
1159 alt_dir = None
1160
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001161 if clone_bundle \
1162 and alt_dir is None \
1163 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001164 is_new = False
1165
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001166 if not current_branch_only:
1167 if self.sync_c:
1168 current_branch_only = True
1169 elif not self.manifest._loaded:
1170 # Manifest cannot check defaults until it syncs.
1171 current_branch_only = False
1172 elif self.manifest.default.sync_c:
1173 current_branch_only = True
1174
David Pursehouseb1553542014-09-04 21:28:09 +09001175 need_to_fetch = not (optimized_fetch and \
1176 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1177 if (need_to_fetch
Conley Owens666d5342014-05-01 13:09:57 -07001178 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1179 current_branch_only=current_branch_only,
1180 no_tags=no_tags)):
Anthony King7bdac712014-07-16 12:56:40 +01001181 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001182
1183 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001184 self._InitMRef()
1185 else:
1186 self._InitMirrorHead()
1187 try:
1188 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1189 except OSError:
1190 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001191 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001192
1193 def PostRepoUpgrade(self):
1194 self._InitHooks()
1195
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001196 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001197 for copyfile in self.copyfiles:
1198 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001199 for linkfile in self.linkfiles:
1200 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201
Julien Camperguedd654222014-01-09 16:21:37 +01001202 def GetCommitRevisionId(self):
1203 """Get revisionId of a commit.
1204
1205 Use this method instead of GetRevisionId to get the id of the commit rather
1206 than the id of the current git object (for example, a tag)
1207
1208 """
1209 if not self.revisionExpr.startswith(R_TAGS):
1210 return self.GetRevisionId(self._allrefs)
1211
1212 try:
1213 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1214 except GitError:
1215 raise ManifestInvalidRevisionError(
1216 'revision %s in %s not found' % (self.revisionExpr,
1217 self.name))
1218
David Pursehouse8a68ff92012-09-24 12:15:13 +09001219 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001220 if self.revisionId:
1221 return self.revisionId
1222
1223 rem = self.GetRemote(self.remote.name)
1224 rev = rem.ToLocal(self.revisionExpr)
1225
David Pursehouse8a68ff92012-09-24 12:15:13 +09001226 if all_refs is not None and rev in all_refs:
1227 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001228
1229 try:
1230 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1231 except GitError:
1232 raise ManifestInvalidRevisionError(
1233 'revision %s in %s not found' % (self.revisionExpr,
1234 self.name))
1235
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001236 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001237 """Perform only the local IO portion of the sync process.
1238 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001239 """
David James8d201162013-10-11 17:03:19 -07001240 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001241 all_refs = self.bare_ref.all
1242 self.CleanPublishedCache(all_refs)
1243 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001244
David Pursehouse1d947b32012-10-25 12:23:11 +09001245 def _doff():
1246 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001247 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001248
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001249 head = self.work_git.GetHead()
1250 if head.startswith(R_HEADS):
1251 branch = head[len(R_HEADS):]
1252 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001253 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001254 except KeyError:
1255 head = None
1256 else:
1257 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001259 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001260 # Currently on a detached HEAD. The user is assumed to
1261 # not have any local modifications worth worrying about.
1262 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001263 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001264 syncbuf.fail(self, _PriorSyncFailedError())
1265 return
1266
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001267 if head == revid:
1268 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001269 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001270 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001271 if not syncbuf.detach_head:
1272 return
1273 else:
1274 lost = self._revlist(not_rev(revid), HEAD)
1275 if lost:
1276 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001277
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001278 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001279 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001280 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001281 syncbuf.fail(self, e)
1282 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001283 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001284 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001285
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001286 if head == revid:
1287 # No changes; don't do anything further.
1288 #
1289 return
1290
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001291 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001293 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001295 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001297 syncbuf.info(self,
1298 "leaving %s; does not track upstream",
1299 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001301 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001302 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001303 syncbuf.fail(self, e)
1304 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001305 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001306 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001308 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001309 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001311 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001312 if not_merged:
1313 if upstream_gain:
1314 # The user has published this branch and some of those
1315 # commits are not yet merged upstream. We do not want
1316 # to rewrite the published commits so we punt.
1317 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001318 syncbuf.fail(self,
1319 "branch %s is published (but not merged) and is now %d commits behind"
1320 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001321 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001322 elif pub == head:
1323 # All published commits are merged, and thus we are a
1324 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001325 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001326 syncbuf.later1(self, _doff)
1327 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001329 # Examine the local commits not in the remote. Find the
1330 # last one attributed to this user, if any.
1331 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001332 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001333 last_mine = None
1334 cnt_mine = 0
1335 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301336 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001337 if committer_email == self.UserEmail:
1338 last_mine = commit_id
1339 cnt_mine += 1
1340
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001341 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001342 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001343
1344 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001345 syncbuf.fail(self, _DirtyError())
1346 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001347
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001348 # If the upstream switched on us, warn the user.
1349 #
1350 if branch.merge != self.revisionExpr:
1351 if branch.merge and self.revisionExpr:
1352 syncbuf.info(self,
1353 'manifest switched %s...%s',
1354 branch.merge,
1355 self.revisionExpr)
1356 elif branch.merge:
1357 syncbuf.info(self,
1358 'manifest no longer tracks %s',
1359 branch.merge)
1360
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001361 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001362 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001363 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001365 syncbuf.info(self,
1366 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001367 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001369 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001370 if not ID_RE.match(self.revisionExpr):
1371 # in case of manifest sync the revisionExpr might be a SHA1
1372 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001373 if not branch.merge.startswith('refs/'):
1374 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001375 branch.Save()
1376
Mike Pontillod3153822012-02-28 11:53:24 -08001377 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001378 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001379 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001380 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001381 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001382 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001383 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001384 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001385 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001386 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001387 syncbuf.fail(self, e)
1388 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001390 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001392 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393 # dest should already be an absolute path, but src is project relative
1394 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001395 abssrc = os.path.join(self.worktree, src)
1396 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001397
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001398 def AddLinkFile(self, src, dest, absdest):
1399 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001400 # make src relative path to dest
1401 absdestdir = os.path.dirname(absdest)
1402 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001403 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001404
James W. Mills24c13082012-04-12 15:04:13 -05001405 def AddAnnotation(self, name, value, keep):
1406 self.annotations.append(_Annotation(name, value, keep))
1407
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001408 def DownloadPatchSet(self, change_id, patch_id):
1409 """Download a single patch set of a single change to FETCH_HEAD.
1410 """
1411 remote = self.GetRemote(self.remote.name)
1412
1413 cmd = ['fetch', remote.name]
1414 cmd.append('refs/changes/%2.2d/%d/%d' \
1415 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001416 if GitCommand(self, cmd, bare=True).Wait() != 0:
1417 return None
1418 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001419 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001420 change_id,
1421 patch_id,
1422 self.bare_git.rev_parse('FETCH_HEAD'))
1423
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001424
1425## Branch Management ##
1426
1427 def StartBranch(self, name):
1428 """Create a new branch off the manifest's revision.
1429 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001430 head = self.work_git.GetHead()
1431 if head == (R_HEADS + name):
1432 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001433
David Pursehouse8a68ff92012-09-24 12:15:13 +09001434 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001435 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001436 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001437 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001438 capture_stdout=True,
1439 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001440
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001441 branch = self.GetBranch(name)
1442 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001443 branch.merge = self.revisionExpr
Alexandre Boeglin38258272015-04-30 14:50:33 +02001444 if not branch.merge.startswith('refs/') and not ID_RE.match(self.revisionExpr):
Conley Owens04f2f0e2014-10-01 17:22:46 -07001445 branch.merge = R_HEADS + self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001446 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001447
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001448 if head.startswith(R_HEADS):
1449 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001450 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001451 except KeyError:
1452 head = None
1453
1454 if revid and head and revid == head:
1455 ref = os.path.join(self.gitdir, R_HEADS + name)
1456 try:
1457 os.makedirs(os.path.dirname(ref))
1458 except OSError:
1459 pass
1460 _lwrite(ref, '%s\n' % revid)
1461 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1462 'ref: %s%s\n' % (R_HEADS, name))
1463 branch.Save()
1464 return True
1465
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001466 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001467 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001468 capture_stdout=True,
1469 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001470 branch.Save()
1471 return True
1472 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001473
Wink Saville02d79452009-04-10 13:01:24 -07001474 def CheckoutBranch(self, name):
1475 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001476
1477 Args:
1478 name: The name of the branch to checkout.
1479
1480 Returns:
1481 True if the checkout succeeded; False if it didn't; None if the branch
1482 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001483 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001484 rev = R_HEADS + name
1485 head = self.work_git.GetHead()
1486 if head == rev:
1487 # Already on the branch
1488 #
1489 return True
Wink Saville02d79452009-04-10 13:01:24 -07001490
David Pursehouse8a68ff92012-09-24 12:15:13 +09001491 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001492 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001493 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001494 except KeyError:
1495 # Branch does not exist in this project
1496 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001497 return None
Wink Saville02d79452009-04-10 13:01:24 -07001498
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001499 if head.startswith(R_HEADS):
1500 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001501 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001502 except KeyError:
1503 head = None
1504
1505 if head == revid:
1506 # Same revision; just update HEAD to point to the new
1507 # target branch, but otherwise take no other action.
1508 #
1509 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1510 'ref: %s%s\n' % (R_HEADS, name))
1511 return True
1512
1513 return GitCommand(self,
1514 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001515 capture_stdout=True,
1516 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001517
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001518 def AbandonBranch(self, name):
1519 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001520
1521 Args:
1522 name: The name of the branch to abandon.
1523
1524 Returns:
1525 True if the abandon succeeded; False if it didn't; None if the branch
1526 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001527 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001528 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001529 all_refs = self.bare_ref.all
1530 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001531 # Doesn't exist
1532 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001533
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001534 head = self.work_git.GetHead()
1535 if head == rev:
1536 # We can't destroy the branch while we are sitting
1537 # on it. Switch to a detached HEAD.
1538 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001539 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001540
David Pursehouse8a68ff92012-09-24 12:15:13 +09001541 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001542 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001543 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1544 '%s\n' % revid)
1545 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001546 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001547
1548 return GitCommand(self,
1549 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001550 capture_stdout=True,
1551 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001552
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001553 def PruneHeads(self):
1554 """Prune any topic branches already merged into upstream.
1555 """
1556 cb = self.CurrentBranch
1557 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001558 left = self._allrefs
1559 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001560 if name.startswith(R_HEADS):
1561 name = name[len(R_HEADS):]
1562 if cb is None or name != cb:
1563 kill.append(name)
1564
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001565 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001566 if cb is not None \
1567 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001568 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001569 self.work_git.DetachHead(HEAD)
1570 kill.append(cb)
1571
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001572 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001573 old = self.bare_git.GetHead()
1574 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001575 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1576
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001577 try:
1578 self.bare_git.DetachHead(rev)
1579
1580 b = ['branch', '-d']
1581 b.extend(kill)
1582 b = GitCommand(self, b, bare=True,
1583 capture_stdout=True,
1584 capture_stderr=True)
1585 b.Wait()
1586 finally:
1587 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001588 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001589
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001590 for branch in kill:
1591 if (R_HEADS + branch) not in left:
1592 self.CleanPublishedCache()
1593 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001594
1595 if cb and cb not in kill:
1596 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001597 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001598
1599 kept = []
1600 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001601 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001602 branch = self.GetBranch(branch)
1603 base = branch.LocalMerge
1604 if not base:
1605 base = rev
1606 kept.append(ReviewableBranch(self, branch, base))
1607 return kept
1608
1609
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001610## Submodule Management ##
1611
1612 def GetRegisteredSubprojects(self):
1613 result = []
1614 def rec(subprojects):
1615 if not subprojects:
1616 return
1617 result.extend(subprojects)
1618 for p in subprojects:
1619 rec(p.subprojects)
1620 rec(self.subprojects)
1621 return result
1622
1623 def _GetSubmodules(self):
1624 # Unfortunately we cannot call `git submodule status --recursive` here
1625 # because the working tree might not exist yet, and it cannot be used
1626 # without a working tree in its current implementation.
1627
1628 def get_submodules(gitdir, rev):
1629 # Parse .gitmodules for submodule sub_paths and sub_urls
1630 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1631 if not sub_paths:
1632 return []
1633 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1634 # revision of submodule repository
1635 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1636 submodules = []
1637 for sub_path, sub_url in zip(sub_paths, sub_urls):
1638 try:
1639 sub_rev = sub_revs[sub_path]
1640 except KeyError:
1641 # Ignore non-exist submodules
1642 continue
1643 submodules.append((sub_rev, sub_path, sub_url))
1644 return submodules
1645
1646 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1647 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1648 def parse_gitmodules(gitdir, rev):
1649 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1650 try:
Anthony King7bdac712014-07-16 12:56:40 +01001651 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1652 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001653 except GitError:
1654 return [], []
1655 if p.Wait() != 0:
1656 return [], []
1657
1658 gitmodules_lines = []
1659 fd, temp_gitmodules_path = tempfile.mkstemp()
1660 try:
1661 os.write(fd, p.stdout)
1662 os.close(fd)
1663 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001664 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1665 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001666 if p.Wait() != 0:
1667 return [], []
1668 gitmodules_lines = p.stdout.split('\n')
1669 except GitError:
1670 return [], []
1671 finally:
1672 os.remove(temp_gitmodules_path)
1673
1674 names = set()
1675 paths = {}
1676 urls = {}
1677 for line in gitmodules_lines:
1678 if not line:
1679 continue
1680 m = re_path.match(line)
1681 if m:
1682 names.add(m.group(1))
1683 paths[m.group(1)] = m.group(2)
1684 continue
1685 m = re_url.match(line)
1686 if m:
1687 names.add(m.group(1))
1688 urls[m.group(1)] = m.group(2)
1689 continue
1690 names = sorted(names)
1691 return ([paths.get(name, '') for name in names],
1692 [urls.get(name, '') for name in names])
1693
1694 def git_ls_tree(gitdir, rev, paths):
1695 cmd = ['ls-tree', rev, '--']
1696 cmd.extend(paths)
1697 try:
Anthony King7bdac712014-07-16 12:56:40 +01001698 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1699 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001700 except GitError:
1701 return []
1702 if p.Wait() != 0:
1703 return []
1704 objects = {}
1705 for line in p.stdout.split('\n'):
1706 if not line.strip():
1707 continue
1708 object_rev, object_path = line.split()[2:4]
1709 objects[object_path] = object_rev
1710 return objects
1711
1712 try:
1713 rev = self.GetRevisionId()
1714 except GitError:
1715 return []
1716 return get_submodules(self.gitdir, rev)
1717
1718 def GetDerivedSubprojects(self):
1719 result = []
1720 if not self.Exists:
1721 # If git repo does not exist yet, querying its submodules will
1722 # mess up its states; so return here.
1723 return result
1724 for rev, path, url in self._GetSubmodules():
1725 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001726 relpath, worktree, gitdir, objdir = \
1727 self.manifest.GetSubprojectPaths(self, name, path)
1728 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001729 if project:
1730 result.extend(project.GetDerivedSubprojects())
1731 continue
David James8d201162013-10-11 17:03:19 -07001732
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001733 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001734 url=url,
1735 review=self.remote.review,
1736 revision=self.remote.revision)
1737 subproject = Project(manifest=self.manifest,
1738 name=name,
1739 remote=remote,
1740 gitdir=gitdir,
1741 objdir=objdir,
1742 worktree=worktree,
1743 relpath=relpath,
1744 revisionExpr=self.revisionExpr,
1745 revisionId=rev,
1746 rebase=self.rebase,
1747 groups=self.groups,
1748 sync_c=self.sync_c,
1749 sync_s=self.sync_s,
1750 parent=self,
1751 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001752 result.append(subproject)
1753 result.extend(subproject.GetDerivedSubprojects())
1754 return result
1755
1756
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001757## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001758 def _CheckForSha1(self):
1759 try:
1760 # if revision (sha or tag) is not present then following function
1761 # throws an error.
1762 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1763 return True
1764 except GitError:
1765 # There is no such persistent revision. We have to fetch it.
1766 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001767
Julien Campergue335f5ef2013-10-16 11:02:35 +02001768 def _FetchArchive(self, tarpath, cwd=None):
1769 cmd = ['archive', '-v', '-o', tarpath]
1770 cmd.append('--remote=%s' % self.remote.url)
1771 cmd.append('--prefix=%s/' % self.relpath)
1772 cmd.append(self.revisionExpr)
1773
1774 command = GitCommand(self, cmd, cwd=cwd,
1775 capture_stdout=True,
1776 capture_stderr=True)
1777
1778 if command.Wait() != 0:
1779 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1780
Conley Owens80b87fe2014-05-09 17:13:44 -07001781
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001782 def _RemoteFetch(self, name=None,
1783 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001784 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001785 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001786 alt_dir=None,
1787 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001788
1789 is_sha1 = False
1790 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001791 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001792
David Pursehouse9bc422f2014-04-15 10:28:56 +09001793 # The depth should not be used when fetching to a mirror because
1794 # it will result in a shallow repository that cannot be cloned or
1795 # fetched from.
1796 if not self.manifest.IsMirror:
1797 if self.clone_depth:
1798 depth = self.clone_depth
1799 else:
1800 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001801 # The repo project should never be synced with partial depth
1802 if self.relpath == '.repo/repo':
1803 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001804
Shawn Pearce69e04d82014-01-29 12:48:54 -08001805 if depth:
1806 current_branch_only = True
1807
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001808 if ID_RE.match(self.revisionExpr) is not None:
1809 is_sha1 = True
1810
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001811 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001812 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001813 # this is a tag and its sha1 value should never change
1814 tag_name = self.revisionExpr[len(R_TAGS):]
1815
1816 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001817 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001818 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001819 if is_sha1 and not depth:
1820 # When syncing a specific commit and --depth is not set:
1821 # * if upstream is explicitly specified and is not a sha1, fetch only
1822 # upstream as users expect only upstream to be fetch.
1823 # Note: The commit might not be in upstream in which case the sync
1824 # will fail.
1825 # * otherwise, fetch all branches to make sure we end up with the
1826 # specific commit.
1827 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001828
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001829 if not name:
1830 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001831
1832 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001833 remote = self.GetRemote(name)
1834 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001835 ssh_proxy = True
1836
Shawn O. Pearce88443382010-10-08 10:02:09 +02001837 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001838 if alt_dir and 'objects' == os.path.basename(alt_dir):
1839 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001840 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1841 remote = self.GetRemote(name)
1842
David Pursehouse8a68ff92012-09-24 12:15:13 +09001843 all_refs = self.bare_ref.all
1844 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001845 tmp = set()
1846
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301847 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001848 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001849 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001850 all_refs[r] = ref_id
1851 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001852 continue
1853
David Pursehouse8a68ff92012-09-24 12:15:13 +09001854 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001855 continue
1856
David Pursehouse8a68ff92012-09-24 12:15:13 +09001857 r = 'refs/_alt/%s' % ref_id
1858 all_refs[r] = ref_id
1859 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001860 tmp.add(r)
1861
Shawn O. Pearce88443382010-10-08 10:02:09 +02001862 tmp_packed = ''
1863 old_packed = ''
1864
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301865 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001866 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001867 tmp_packed += line
1868 if r not in tmp:
1869 old_packed += line
1870
1871 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001872 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001873 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001874
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001875 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001876
Conley Owensf97e8382015-01-21 11:12:46 -08001877 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001878 cmd.append('--depth=%s' % depth)
1879
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001880 if quiet:
1881 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001882 if not self.worktree:
1883 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001884 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001885
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001886 # If using depth then we should not get all the tags since they may
1887 # be outside of the depth.
1888 if no_tags or depth:
1889 cmd.append('--no-tags')
1890 else:
1891 cmd.append('--tags')
1892
Conley Owens80b87fe2014-05-09 17:13:44 -07001893 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001894 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001895 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001896 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001897 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001898 spec.append('tag')
1899 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001900
David Pursehouse403b64e2015-04-27 10:41:33 +09001901 if not self.manifest.IsMirror:
1902 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06001903 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001904 # Shallow checkout of a specific commit, fetch from that commit and not
1905 # the heads only as the commit might be deeper in the history.
1906 spec.append(branch)
1907 else:
1908 if is_sha1:
1909 branch = self.upstream
1910 if branch is not None and branch.strip():
1911 if not branch.startswith('refs/'):
1912 branch = R_HEADS + branch
1913 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001914 cmd.extend(spec)
1915
1916 shallowfetch = self.config.GetString('repo.shallowfetch')
1917 if shallowfetch and shallowfetch != ' '.join(spec):
Anthony King23ff7df2015-03-28 19:42:39 +00001918 GitCommand(self, ['fetch', '--depth=2147483647', name]
1919 + shallowfetch.split(),
Conley Owens80b87fe2014-05-09 17:13:44 -07001920 bare=True, ssh_proxy=ssh_proxy).Wait()
1921 if depth:
Anthony King7bdac712014-07-16 12:56:40 +01001922 self.config.SetString('repo.shallowfetch', ' '.join(spec))
Conley Owens80b87fe2014-05-09 17:13:44 -07001923 else:
Anthony King7bdac712014-07-16 12:56:40 +01001924 self.config.SetString('repo.shallowfetch', None)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001925
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001926 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001927 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001928 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001929 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001930 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001931 ok = True
1932 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001933 # If needed, run the 'git remote prune' the first time through the loop
1934 elif (not _i and
1935 "error:" in gitcmd.stderr and
1936 "git remote prune" in gitcmd.stderr):
1937 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001938 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001939 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001940 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001941 break
1942 continue
Brian Harring14a66742012-09-28 20:21:57 -07001943 elif current_branch_only and is_sha1 and ret == 128:
1944 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1945 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1946 # abort the optimization attempt and do a full sync.
1947 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001948 elif ret < 0:
1949 # Git died with a signal, exit immediately
1950 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001951 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001952
1953 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001954 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001955 if old_packed != '':
1956 _lwrite(packed_refs, old_packed)
1957 else:
1958 os.remove(packed_refs)
1959 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001960
1961 if is_sha1 and current_branch_only and self.upstream:
1962 # We just synced the upstream given branch; verify we
1963 # got what we wanted, else trigger a second run of all
1964 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001965 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06001966 if not depth:
1967 # Avoid infinite recursion when depth is True (since depth implies
1968 # current_branch_only)
1969 return self._RemoteFetch(name=name, current_branch_only=False,
1970 initial=False, quiet=quiet, alt_dir=alt_dir)
1971 if self.clone_depth:
1972 self.clone_depth = None
1973 return self._RemoteFetch(name=name, current_branch_only=current_branch_only,
1974 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07001975
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001976 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001977
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001978 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001979 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001980 return False
1981
1982 remote = self.GetRemote(self.remote.name)
1983 bundle_url = remote.url + '/clone.bundle'
1984 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001985 if GetSchemeFromUrl(bundle_url) not in (
1986 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001987 return False
1988
1989 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1990 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1991
1992 exist_dst = os.path.exists(bundle_dst)
1993 exist_tmp = os.path.exists(bundle_tmp)
1994
1995 if not initial and not exist_dst and not exist_tmp:
1996 return False
1997
1998 if not exist_dst:
1999 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2000 if not exist_dst:
2001 return False
2002
2003 cmd = ['fetch']
2004 if quiet:
2005 cmd.append('--quiet')
2006 if not self.worktree:
2007 cmd.append('--update-head-ok')
2008 cmd.append(bundle_dst)
2009 for f in remote.fetch:
2010 cmd.append(str(f))
2011 cmd.append('refs/tags/*:refs/tags/*')
2012
2013 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002014 if os.path.exists(bundle_dst):
2015 os.remove(bundle_dst)
2016 if os.path.exists(bundle_tmp):
2017 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002018 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002019
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002020 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002021 if os.path.exists(dstPath):
2022 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002023
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002024 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002025 if quiet:
2026 cmd += ['--silent']
2027 if os.path.exists(tmpPath):
2028 size = os.stat(tmpPath).st_size
2029 if size >= 1024:
2030 cmd += ['--continue-at', '%d' % (size,)]
2031 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002032 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002033 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2034 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz497bde42015-01-02 13:58:05 -08002035 with self._GetBundleCookieFile(srcUrl, quiet) as cookiefile:
Dave Borowitz137d0132015-01-02 11:12:54 -08002036 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002037 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002038 if srcUrl.startswith('persistent-'):
2039 srcUrl = srcUrl[len('persistent-'):]
2040 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002041
Dave Borowitz137d0132015-01-02 11:12:54 -08002042 if IsTrace():
2043 Trace('%s', ' '.join(cmd))
2044 try:
2045 proc = subprocess.Popen(cmd)
2046 except OSError:
2047 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002048
Dave Borowitz137d0132015-01-02 11:12:54 -08002049 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002050
Dave Borowitz137d0132015-01-02 11:12:54 -08002051 if curlret == 22:
2052 # From curl man page:
2053 # 22: HTTP page not retrieved. The requested url was not found or
2054 # returned another error with the HTTP error code being 400 or above.
2055 # This return code only appears if -f, --fail is used.
2056 if not quiet:
2057 print("Server does not provide clone.bundle; ignoring.",
2058 file=sys.stderr)
2059 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002060
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002061 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002062 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002063 os.rename(tmpPath, dstPath)
2064 return True
2065 else:
2066 os.remove(tmpPath)
2067 return False
2068 else:
2069 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002070
Kris Giesingc8d882a2014-12-23 13:02:32 -08002071 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002072 try:
2073 with open(path) as f:
2074 if f.read(16) == '# v2 git bundle\n':
2075 return True
2076 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002077 if not quiet:
2078 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002079 return False
2080 except OSError:
2081 return False
2082
Dave Borowitz137d0132015-01-02 11:12:54 -08002083 @contextlib.contextmanager
Dave Borowitz497bde42015-01-02 13:58:05 -08002084 def _GetBundleCookieFile(self, url, quiet):
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002085 if url.startswith('persistent-'):
2086 try:
2087 p = subprocess.Popen(
2088 ['git-remote-persistent-https', '-print_config', url],
2089 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2090 stderr=subprocess.PIPE)
Dave Borowitz137d0132015-01-02 11:12:54 -08002091 try:
2092 prefix = 'http.cookiefile='
2093 cookiefile = None
2094 for line in p.stdout:
2095 line = line.strip()
2096 if line.startswith(prefix):
2097 cookiefile = line[len(prefix):]
2098 break
2099 # Leave subprocess open, as cookie file may be transient.
2100 if cookiefile:
2101 yield cookiefile
2102 return
2103 finally:
2104 p.stdin.close()
2105 if p.wait():
2106 err_msg = p.stderr.read()
2107 if ' -print_config' in err_msg:
2108 pass # Persistent proxy doesn't support -print_config.
Dave Borowitz497bde42015-01-02 13:58:05 -08002109 elif not quiet:
Dave Borowitz137d0132015-01-02 11:12:54 -08002110 print(err_msg, file=sys.stderr)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002111 except OSError as e:
2112 if e.errno == errno.ENOENT:
2113 pass # No persistent proxy.
2114 raise
Dave Borowitz137d0132015-01-02 11:12:54 -08002115 yield GitConfig.ForUser().GetString('http.cookiefile')
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002116
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002117 def _Checkout(self, rev, quiet=False):
2118 cmd = ['checkout']
2119 if quiet:
2120 cmd.append('-q')
2121 cmd.append(rev)
2122 cmd.append('--')
2123 if GitCommand(self, cmd).Wait() != 0:
2124 if self._allrefs:
2125 raise GitError('%s checkout %s ' % (self.name, rev))
2126
Anthony King7bdac712014-07-16 12:56:40 +01002127 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002128 cmd = ['cherry-pick']
2129 cmd.append(rev)
2130 cmd.append('--')
2131 if GitCommand(self, cmd).Wait() != 0:
2132 if self._allrefs:
2133 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2134
Anthony King7bdac712014-07-16 12:56:40 +01002135 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002136 cmd = ['revert']
2137 cmd.append('--no-edit')
2138 cmd.append(rev)
2139 cmd.append('--')
2140 if GitCommand(self, cmd).Wait() != 0:
2141 if self._allrefs:
2142 raise GitError('%s revert %s ' % (self.name, rev))
2143
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002144 def _ResetHard(self, rev, quiet=True):
2145 cmd = ['reset', '--hard']
2146 if quiet:
2147 cmd.append('-q')
2148 cmd.append(rev)
2149 if GitCommand(self, cmd).Wait() != 0:
2150 raise GitError('%s reset --hard %s ' % (self.name, rev))
2151
Anthony King7bdac712014-07-16 12:56:40 +01002152 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002153 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002154 if onto is not None:
2155 cmd.extend(['--onto', onto])
2156 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002157 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002158 raise GitError('%s rebase %s ' % (self.name, upstream))
2159
Pierre Tardy3d125942012-05-04 12:18:12 +02002160 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002161 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002162 if ffonly:
2163 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002164 if GitCommand(self, cmd).Wait() != 0:
2165 raise GitError('%s merge %s ' % (self.name, head))
2166
Jonathan Nieder93719792015-03-17 11:29:58 -07002167 def _InitGitDir(self, mirror_git=None):
Kevin Degi384b3c52014-10-16 16:02:58 -06002168 init_git_dir = not os.path.exists(self.gitdir)
2169 init_obj_dir = not os.path.exists(self.objdir)
2170 # Initialize the bare repository, which contains all of the objects.
2171 if init_obj_dir:
2172 os.makedirs(self.objdir)
2173 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002174
Kevin Degi384b3c52014-10-16 16:02:58 -06002175 # If we have a separate directory to hold refs, initialize it as well.
2176 if self.objdir != self.gitdir:
2177 if init_git_dir:
David James8d201162013-10-11 17:03:19 -07002178 os.makedirs(self.gitdir)
Kevin Degi384b3c52014-10-16 16:02:58 -06002179
2180 if init_obj_dir or init_git_dir:
David James8d201162013-10-11 17:03:19 -07002181 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2182 copy_all=True)
Kevin Degi384b3c52014-10-16 16:02:58 -06002183 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002184
Kevin Degi384b3c52014-10-16 16:02:58 -06002185 if init_git_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002186 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002187 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002188
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002189 if ref_dir or mirror_git:
2190 if not mirror_git:
2191 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002192 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2193 self.relpath + '.git')
2194
2195 if os.path.exists(mirror_git):
2196 ref_dir = mirror_git
2197
2198 elif os.path.exists(repo_git):
2199 ref_dir = repo_git
2200
2201 else:
2202 ref_dir = None
2203
2204 if ref_dir:
2205 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2206 os.path.join(ref_dir, 'objects') + '\n')
2207
Jimmie Westera0444582012-10-24 13:44:42 +02002208 self._UpdateHooks()
2209
2210 m = self.manifest.manifestProject.config
2211 for key in ['user.name', 'user.email']:
Anthony King7bdac712014-07-16 12:56:40 +01002212 if m.Has(key, include_defaults=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002213 self.config.SetString(key, m.GetString(key))
Jonathan Nieder93719792015-03-17 11:29:58 -07002214 if self.manifest.IsMirror:
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002215 self.config.SetString('core.bare', 'true')
2216 else:
2217 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002218
Jimmie Westera0444582012-10-24 13:44:42 +02002219 def _UpdateHooks(self):
2220 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002221 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002222
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002223 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002224 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002225 if not os.path.exists(hooks):
2226 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002227 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002228 name = os.path.basename(stock_hook)
2229
Victor Boivie65e0f352011-04-18 11:23:29 +02002230 if name in ('commit-msg',) and not self.remote.review \
2231 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002232 # Don't install a Gerrit Code Review hook if this
2233 # project does not appear to use it for reviews.
2234 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002235 # Since the manifest project is one of those, but also
2236 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002237 continue
2238
2239 dst = os.path.join(hooks, name)
2240 if os.path.islink(dst):
2241 continue
2242 if os.path.exists(dst):
2243 if filecmp.cmp(stock_hook, dst, shallow=False):
2244 os.remove(dst)
2245 else:
2246 _error("%s: Not replacing %s hook", self.relpath, name)
2247 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002248 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002249 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002250 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002251 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002252 raise GitError('filesystem must support symlinks')
2253 else:
2254 raise
2255
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002256 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002257 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002258 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002259 remote.url = self.remote.url
2260 remote.review = self.remote.review
2261 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002262
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002263 if self.worktree:
2264 remote.ResetFetch(mirror=False)
2265 else:
2266 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002267 remote.Save()
2268
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002269 def _InitMRef(self):
2270 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002271 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002272
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002273 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002274 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002275
2276 def _InitAnyMRef(self, ref):
2277 cur = self.bare_ref.symref(ref)
2278
2279 if self.revisionId:
2280 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2281 msg = 'manifest set to %s' % self.revisionId
2282 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002283 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002284 else:
2285 remote = self.GetRemote(self.remote.name)
2286 dst = remote.ToLocal(self.revisionExpr)
2287 if cur != dst:
2288 msg = 'manifest set to %s' % self.revisionExpr
2289 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002290
Kevin Degi384b3c52014-10-16 16:02:58 -06002291 def _CheckDirReference(self, srcdir, destdir, share_refs):
2292 symlink_files = self.shareable_files
2293 symlink_dirs = self.shareable_dirs
2294 if share_refs:
2295 symlink_files += self.working_tree_files
2296 symlink_dirs += self.working_tree_dirs
2297 to_symlink = symlink_files + symlink_dirs
2298 for name in set(to_symlink):
2299 dst = os.path.realpath(os.path.join(destdir, name))
2300 if os.path.lexists(dst):
2301 src = os.path.realpath(os.path.join(srcdir, name))
2302 # Fail if the links are pointing to the wrong place
2303 if src != dst:
2304 raise GitError('cannot overwrite a local work tree')
2305
David James8d201162013-10-11 17:03:19 -07002306 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2307 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2308
2309 Args:
2310 gitdir: The bare git repository. Must already be initialized.
2311 dotgit: The repository you would like to initialize.
2312 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2313 Only one work tree can store refs under a given |gitdir|.
2314 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2315 This saves you the effort of initializing |dotgit| yourself.
2316 """
Kevin Degi384b3c52014-10-16 16:02:58 -06002317 symlink_files = self.shareable_files
2318 symlink_dirs = self.shareable_dirs
David James8d201162013-10-11 17:03:19 -07002319 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002320 symlink_files += self.working_tree_files
2321 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002322 to_symlink = symlink_files + symlink_dirs
2323
2324 to_copy = []
2325 if copy_all:
2326 to_copy = os.listdir(gitdir)
2327
2328 for name in set(to_copy).union(to_symlink):
2329 try:
2330 src = os.path.realpath(os.path.join(gitdir, name))
2331 dst = os.path.realpath(os.path.join(dotgit, name))
2332
Kevin Degi384b3c52014-10-16 16:02:58 -06002333 if os.path.lexists(dst):
2334 continue
David James8d201162013-10-11 17:03:19 -07002335
2336 # If the source dir doesn't exist, create an empty dir.
2337 if name in symlink_dirs and not os.path.lexists(src):
2338 os.makedirs(src)
2339
Conley Owens80b87fe2014-05-09 17:13:44 -07002340 # If the source file doesn't exist, ensure the destination
2341 # file doesn't either.
2342 if name in symlink_files and not os.path.lexists(src):
2343 try:
2344 os.remove(dst)
2345 except OSError:
2346 pass
2347
David James8d201162013-10-11 17:03:19 -07002348 if name in to_symlink:
2349 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2350 elif copy_all and not os.path.islink(dst):
2351 if os.path.isdir(src):
2352 shutil.copytree(src, dst)
2353 elif os.path.isfile(src):
2354 shutil.copy(src, dst)
2355 except OSError as e:
2356 if e.errno == errno.EPERM:
2357 raise GitError('filesystem must support symlinks')
2358 else:
2359 raise
2360
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002361 def _InitWorkTree(self):
2362 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002363 init_dotgit = not os.path.exists(dotgit)
2364 if init_dotgit:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002365 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002366 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2367 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002368
Kevin Degi384b3c52014-10-16 16:02:58 -06002369 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2370
2371 if init_dotgit:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002372 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002373
2374 cmd = ['read-tree', '--reset', '-u']
2375 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002376 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002377 if GitCommand(self, cmd).Wait() != 0:
2378 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002379
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002380 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002381
2382 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002383 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002384
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002385 def _revlist(self, *args, **kw):
2386 a = []
2387 a.extend(args)
2388 a.append('--')
2389 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002390
2391 @property
2392 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002393 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002394
Julien Camperguedd654222014-01-09 16:21:37 +01002395 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2396 """Get logs between two revisions of this project."""
2397 comp = '..'
2398 if rev1:
2399 revs = [rev1]
2400 if rev2:
2401 revs.extend([comp, rev2])
2402 cmd = ['log', ''.join(revs)]
2403 out = DiffColoring(self.config)
2404 if out.is_on and color:
2405 cmd.append('--color')
2406 if oneline:
2407 cmd.append('--oneline')
2408
2409 try:
2410 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2411 if log.Wait() == 0:
2412 return log.stdout
2413 except GitError:
2414 # worktree may not exist if groups changed for example. In that case,
2415 # try in gitdir instead.
2416 if not os.path.exists(self.worktree):
2417 return self.bare_git.log(*cmd[1:])
2418 else:
2419 raise
2420 return None
2421
2422 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2423 """Get the list of logs from this revision to given revisionId"""
2424 logs = {}
2425 selfId = self.GetRevisionId(self._allrefs)
2426 toId = toProject.GetRevisionId(toProject._allrefs)
2427
2428 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2429 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2430 return logs
2431
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002432 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002433 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002434 self._project = project
2435 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002436 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002437
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002438 def LsOthers(self):
2439 p = GitCommand(self._project,
2440 ['ls-files',
2441 '-z',
2442 '--others',
2443 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002444 bare=False,
David James8d201162013-10-11 17:03:19 -07002445 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002446 capture_stdout=True,
2447 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002448 if p.Wait() == 0:
2449 out = p.stdout
2450 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002451 return out[:-1].split('\0') # pylint: disable=W1401
2452 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002453 return []
2454
2455 def DiffZ(self, name, *args):
2456 cmd = [name]
2457 cmd.append('-z')
2458 cmd.extend(args)
2459 p = GitCommand(self._project,
2460 cmd,
David James8d201162013-10-11 17:03:19 -07002461 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002462 bare=False,
2463 capture_stdout=True,
2464 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002465 try:
2466 out = p.process.stdout.read()
2467 r = {}
2468 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002469 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002470 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002471 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002472 info = next(out)
2473 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002474 except StopIteration:
2475 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002476
2477 class _Info(object):
2478 def __init__(self, path, omode, nmode, oid, nid, state):
2479 self.path = path
2480 self.src_path = None
2481 self.old_mode = omode
2482 self.new_mode = nmode
2483 self.old_id = oid
2484 self.new_id = nid
2485
2486 if len(state) == 1:
2487 self.status = state
2488 self.level = None
2489 else:
2490 self.status = state[:1]
2491 self.level = state[1:]
2492 while self.level.startswith('0'):
2493 self.level = self.level[1:]
2494
2495 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002496 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002497 if info.status in ('R', 'C'):
2498 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002499 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002500 r[info.path] = info
2501 return r
2502 finally:
2503 p.Wait()
2504
2505 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002506 if self._bare:
2507 path = os.path.join(self._project.gitdir, HEAD)
2508 else:
2509 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002510 try:
2511 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002512 except IOError as e:
2513 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002514 try:
2515 line = fd.read()
2516 finally:
2517 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302518 try:
2519 line = line.decode()
2520 except AttributeError:
2521 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002522 if line.startswith('ref: '):
2523 return line[5:-1]
2524 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002525
2526 def SetHead(self, ref, message=None):
2527 cmdv = []
2528 if message is not None:
2529 cmdv.extend(['-m', message])
2530 cmdv.append(HEAD)
2531 cmdv.append(ref)
2532 self.symbolic_ref(*cmdv)
2533
2534 def DetachHead(self, new, message=None):
2535 cmdv = ['--no-deref']
2536 if message is not None:
2537 cmdv.extend(['-m', message])
2538 cmdv.append(HEAD)
2539 cmdv.append(new)
2540 self.update_ref(*cmdv)
2541
2542 def UpdateRef(self, name, new, old=None,
2543 message=None,
2544 detach=False):
2545 cmdv = []
2546 if message is not None:
2547 cmdv.extend(['-m', message])
2548 if detach:
2549 cmdv.append('--no-deref')
2550 cmdv.append(name)
2551 cmdv.append(new)
2552 if old is not None:
2553 cmdv.append(old)
2554 self.update_ref(*cmdv)
2555
2556 def DeleteRef(self, name, old=None):
2557 if not old:
2558 old = self.rev_parse(name)
2559 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002560 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002561
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002562 def rev_list(self, *args, **kw):
2563 if 'format' in kw:
2564 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2565 else:
2566 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002567 cmdv.extend(args)
2568 p = GitCommand(self._project,
2569 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002570 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002571 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002572 capture_stdout=True,
2573 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002574 r = []
2575 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002576 if line[-1] == '\n':
2577 line = line[:-1]
2578 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002579 if p.Wait() != 0:
2580 raise GitError('%s rev-list %s: %s' % (
2581 self._project.name,
2582 str(args),
2583 p.stderr))
2584 return r
2585
2586 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002587 """Allow arbitrary git commands using pythonic syntax.
2588
2589 This allows you to do things like:
2590 git_obj.rev_parse('HEAD')
2591
2592 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2593 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002594 Any other positional arguments will be passed to the git command, and the
2595 following keyword arguments are supported:
2596 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002597
2598 Args:
2599 name: The name of the git command to call. Any '_' characters will
2600 be replaced with '-'.
2601
2602 Returns:
2603 A callable object that will try to call git with the named command.
2604 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002605 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002606 def runner(*args, **kwargs):
2607 cmdv = []
2608 config = kwargs.pop('config', None)
2609 for k in kwargs:
2610 raise TypeError('%s() got an unexpected keyword argument %r'
2611 % (name, k))
2612 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002613 if not git_require((1, 7, 2)):
2614 raise ValueError('cannot set config on command line for %s()'
2615 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302616 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002617 cmdv.append('-c')
2618 cmdv.append('%s=%s' % (k, v))
2619 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002620 cmdv.extend(args)
2621 p = GitCommand(self._project,
2622 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002623 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002624 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002625 capture_stdout=True,
2626 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002627 if p.Wait() != 0:
2628 raise GitError('%s %s: %s' % (
2629 self._project.name,
2630 name,
2631 p.stderr))
2632 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302633 try:
Conley Owensedd01512013-09-26 12:59:58 -07002634 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302635 except AttributeError:
2636 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002637 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2638 return r[:-1]
2639 return r
2640 return runner
2641
2642
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002643class _PriorSyncFailedError(Exception):
2644 def __str__(self):
2645 return 'prior sync failed; rebase still in progress'
2646
2647class _DirtyError(Exception):
2648 def __str__(self):
2649 return 'contains uncommitted changes'
2650
2651class _InfoMessage(object):
2652 def __init__(self, project, text):
2653 self.project = project
2654 self.text = text
2655
2656 def Print(self, syncbuf):
2657 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2658 syncbuf.out.nl()
2659
2660class _Failure(object):
2661 def __init__(self, project, why):
2662 self.project = project
2663 self.why = why
2664
2665 def Print(self, syncbuf):
2666 syncbuf.out.fail('error: %s/: %s',
2667 self.project.relpath,
2668 str(self.why))
2669 syncbuf.out.nl()
2670
2671class _Later(object):
2672 def __init__(self, project, action):
2673 self.project = project
2674 self.action = action
2675
2676 def Run(self, syncbuf):
2677 out = syncbuf.out
2678 out.project('project %s/', self.project.relpath)
2679 out.nl()
2680 try:
2681 self.action()
2682 out.nl()
2683 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002684 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002685 out.nl()
2686 return False
2687
2688class _SyncColoring(Coloring):
2689 def __init__(self, config):
2690 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002691 self.project = self.printer('header', attr='bold')
2692 self.info = self.printer('info')
2693 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002694
2695class SyncBuffer(object):
2696 def __init__(self, config, detach_head=False):
2697 self._messages = []
2698 self._failures = []
2699 self._later_queue1 = []
2700 self._later_queue2 = []
2701
2702 self.out = _SyncColoring(config)
2703 self.out.redirect(sys.stderr)
2704
2705 self.detach_head = detach_head
2706 self.clean = True
2707
2708 def info(self, project, fmt, *args):
2709 self._messages.append(_InfoMessage(project, fmt % args))
2710
2711 def fail(self, project, err=None):
2712 self._failures.append(_Failure(project, err))
2713 self.clean = False
2714
2715 def later1(self, project, what):
2716 self._later_queue1.append(_Later(project, what))
2717
2718 def later2(self, project, what):
2719 self._later_queue2.append(_Later(project, what))
2720
2721 def Finish(self):
2722 self._PrintMessages()
2723 self._RunLater()
2724 self._PrintMessages()
2725 return self.clean
2726
2727 def _RunLater(self):
2728 for q in ['_later_queue1', '_later_queue2']:
2729 if not self._RunQueue(q):
2730 return
2731
2732 def _RunQueue(self, queue):
2733 for m in getattr(self, queue):
2734 if not m.Run(self):
2735 self.clean = False
2736 return False
2737 setattr(self, queue, [])
2738 return True
2739
2740 def _PrintMessages(self):
2741 for m in self._messages:
2742 m.Print(self)
2743 for m in self._failures:
2744 m.Print(self)
2745
2746 self._messages = []
2747 self._failures = []
2748
2749
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002750class MetaProject(Project):
2751 """A special project housed under .repo.
2752 """
2753 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002754 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002755 manifest=manifest,
2756 name=name,
2757 gitdir=gitdir,
2758 objdir=gitdir,
2759 worktree=worktree,
2760 remote=RemoteSpec('origin'),
2761 relpath='.repo/%s' % name,
2762 revisionExpr='refs/heads/master',
2763 revisionId=None,
2764 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002765
2766 def PreSync(self):
2767 if self.Exists:
2768 cb = self.CurrentBranch
2769 if cb:
2770 base = self.GetBranch(cb).merge
2771 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002772 self.revisionExpr = base
2773 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002774
Anthony King7bdac712014-07-16 12:56:40 +01002775 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002776 """ Prepare MetaProject for manifest branch switch
2777 """
2778
2779 # detach and delete manifest branch, allowing a new
2780 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002781 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002782 self.Sync_LocalHalf(syncbuf)
2783 syncbuf.Finish()
2784
2785 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002786 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002787 capture_stdout=True,
2788 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002789
2790
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002791 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002792 def LastFetch(self):
2793 try:
2794 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2795 return os.path.getmtime(fh)
2796 except OSError:
2797 return 0
2798
2799 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002800 def HasChanges(self):
2801 """Has the remote received new commits not yet checked out?
2802 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002803 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002804 return False
2805
David Pursehouse8a68ff92012-09-24 12:15:13 +09002806 all_refs = self.bare_ref.all
2807 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002808 head = self.work_git.GetHead()
2809 if head.startswith(R_HEADS):
2810 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002811 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002812 except KeyError:
2813 head = None
2814
2815 if revid == head:
2816 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002817 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002818 return True
2819 return False