blob: f964b2fc8571d0e05612ca1e2aba23834f052917 [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
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070018import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Dave Borowitz137d0132015-01-02 11:12:54 -080029import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070032from git_command import GitCommand, git_require
Dan Willemsen0745bb22015-08-17 13:41:45 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070034from error import GitError, HookError, UploadError, DownloadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080035from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080036from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070037from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070038
Shawn O. Pearced237b692009-04-17 18:49:50 -070039from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
David Pursehouse59bbb582013-05-17 10:49:33 +090041from pyversion import is_python3
42if not is_python3():
43 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053044 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090045 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053046
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070047def _lwrite(path, content):
48 lock = '%s.lock' % path
49
Chirayu Desai303a82f2014-08-19 22:57:17 +053050 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070051 try:
52 fd.write(content)
53 finally:
54 fd.close()
55
56 try:
57 os.rename(lock, path)
58 except OSError:
59 os.remove(lock)
60 raise
61
Shawn O. Pearce48244782009-04-16 08:25:57 -070062def _error(fmt, *args):
63 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070064 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070065
David Pursehousef33929d2015-08-24 14:39:14 +090066def _warn(fmt, *args):
67 msg = fmt % args
68 print('warn: %s' % msg, file=sys.stderr)
69
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070070def not_rev(r):
71 return '^' + r
72
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080073def sq(r):
74 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080075
Jonathan Nieder93719792015-03-17 11:29:58 -070076_project_hook_list = None
77def _ProjectHooks():
78 """List the hooks present in the 'hooks' directory.
79
80 These hooks are project hooks and are copied to the '.git/hooks' directory
81 of all subprojects.
82
83 This function caches the list of hooks (based on the contents of the
84 'repo/hooks' directory) on the first call.
85
86 Returns:
87 A list of absolute paths to all of the files in the hooks directory.
88 """
89 global _project_hook_list
90 if _project_hook_list is None:
91 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
92 d = os.path.join(d, 'hooks')
93 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
94 return _project_hook_list
95
96
Shawn O. Pearce632768b2008-10-23 11:58:52 -070097class DownloadedChange(object):
98 _commit_cache = None
99
100 def __init__(self, project, base, change_id, ps_id, commit):
101 self.project = project
102 self.base = base
103 self.change_id = change_id
104 self.ps_id = ps_id
105 self.commit = commit
106
107 @property
108 def commits(self):
109 if self._commit_cache is None:
110 self._commit_cache = self.project.bare_git.rev_list(
111 '--abbrev=8',
112 '--abbrev-commit',
113 '--pretty=oneline',
114 '--reverse',
115 '--date-order',
116 not_rev(self.base),
117 self.commit,
118 '--')
119 return self._commit_cache
120
121
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700122class ReviewableBranch(object):
123 _commit_cache = None
124
125 def __init__(self, project, branch, base):
126 self.project = project
127 self.branch = branch
128 self.base = base
129
130 @property
131 def name(self):
132 return self.branch.name
133
134 @property
135 def commits(self):
136 if self._commit_cache is None:
137 self._commit_cache = self.project.bare_git.rev_list(
138 '--abbrev=8',
139 '--abbrev-commit',
140 '--pretty=oneline',
141 '--reverse',
142 '--date-order',
143 not_rev(self.base),
144 R_HEADS + self.name,
145 '--')
146 return self._commit_cache
147
148 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800149 def unabbrev_commits(self):
150 r = dict()
151 for commit in self.project.bare_git.rev_list(
152 not_rev(self.base),
153 R_HEADS + self.name,
154 '--'):
155 r[commit[0:8]] = commit
156 return r
157
158 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159 def date(self):
160 return self.project.bare_git.log(
161 '--pretty=format:%cd',
162 '-n', '1',
163 R_HEADS + self.name,
164 '--')
165
Bryan Jacobsf609f912013-05-06 13:36:24 -0400166 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800167 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700168 people,
Brian Harring435370c2012-07-28 15:37:04 -0700169 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400170 draft=draft,
171 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700173 def GetPublishedRefs(self):
174 refs = {}
175 output = self.project.bare_git.ls_remote(
176 self.branch.remote.SshReviewUrl(self.project.UserEmail),
177 'refs/changes/*')
178 for line in output.split('\n'):
179 try:
180 (sha, ref) = line.split()
181 refs[sha] = ref
182 except ValueError:
183 pass
184
185 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186
187class StatusColoring(Coloring):
188 def __init__(self, config):
189 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100190 self.project = self.printer('header', attr='bold')
191 self.branch = self.printer('header', attr='bold')
192 self.nobranch = self.printer('nobranch', fg='red')
193 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700194
Anthony King7bdac712014-07-16 12:56:40 +0100195 self.added = self.printer('added', fg='green')
196 self.changed = self.printer('changed', fg='red')
197 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700198
199
200class DiffColoring(Coloring):
201 def __init__(self, config):
202 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100203 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204
Anthony King7bdac712014-07-16 12:56:40 +0100205class _Annotation(object):
James W. Mills24c13082012-04-12 15:04:13 -0500206 def __init__(self, name, value, keep):
207 self.name = name
208 self.value = value
209 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210
Anthony King7bdac712014-07-16 12:56:40 +0100211class _CopyFile(object):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800212 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213 self.src = src
214 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800215 self.abs_src = abssrc
216 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217
218 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800219 src = self.abs_src
220 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221 # copy file if it does not exist or is out of date
222 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
223 try:
224 # remove existing file first, since it might be read-only
225 if os.path.exists(dest):
226 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400227 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200228 dest_dir = os.path.dirname(dest)
229 if not os.path.isdir(dest_dir):
230 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231 shutil.copy(src, dest)
232 # make the file read-only
233 mode = os.stat(dest)[stat.ST_MODE]
234 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
235 os.chmod(dest, mode)
236 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700237 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238
Anthony King7bdac712014-07-16 12:56:40 +0100239class _LinkFile(object):
Wink Saville4c426ef2015-06-03 08:05:17 -0700240 def __init__(self, git_worktree, src, dest, relsrc, absdest):
241 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500242 self.src = src
243 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700244 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500245 self.abs_dest = absdest
246
Wink Saville4c426ef2015-06-03 08:05:17 -0700247 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500248 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700249 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500250 try:
251 # remove existing file first, since it might be read-only
Wink Saville4c426ef2015-06-03 08:05:17 -0700252 if os.path.exists(absDest):
253 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500254 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700255 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500256 if not os.path.isdir(dest_dir):
257 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700258 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500259 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700260 _error('Cannot link file %s to %s', relSrc, absDest)
261
262 def _Link(self):
263 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
264 on the src linking all of the files in the source in to the destination
265 directory.
266 """
267 # We use the absSrc to handle the situation where the current directory
268 # is not the root of the repo
269 absSrc = os.path.join(self.git_worktree, self.src)
270 if os.path.exists(absSrc):
271 # Entity exists so just a simple one to one link operation
272 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
273 else:
274 # Entity doesn't exist assume there is a wild card
275 absDestDir = self.abs_dest
276 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
277 _error('Link error: src with wildcard, %s must be a directory',
278 absDestDir)
279 else:
280 absSrcFiles = glob.glob(absSrc)
281 for absSrcFile in absSrcFiles:
282 # Create a releative path from source dir to destination dir
283 absSrcDir = os.path.dirname(absSrcFile)
284 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
285
286 # Get the source file name
287 srcFile = os.path.basename(absSrcFile)
288
289 # Now form the final full paths to srcFile. They will be
290 # absolute for the desintaiton and relative for the srouce.
291 absDest = os.path.join(absDestDir, srcFile)
292 relSrc = os.path.join(relSrcDir, srcFile)
293 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500294
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700295class RemoteSpec(object):
296 def __init__(self,
297 name,
Anthony King7bdac712014-07-16 12:56:40 +0100298 url=None,
299 review=None,
300 revision=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700301 self.name = name
302 self.url = url
303 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100304 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700305
Doug Anderson37282b42011-03-04 11:54:18 -0800306class RepoHook(object):
307 """A RepoHook contains information about a script to run as a hook.
308
309 Hooks are used to run a python script before running an upload (for instance,
310 to run presubmit checks). Eventually, we may have hooks for other actions.
311
312 This shouldn't be confused with files in the 'repo/hooks' directory. Those
313 files are copied into each '.git/hooks' folder for each project. Repo-level
314 hooks are associated instead with repo actions.
315
316 Hooks are always python. When a hook is run, we will load the hook into the
317 interpreter and execute its main() function.
318 """
319 def __init__(self,
320 hook_type,
321 hooks_project,
322 topdir,
323 abort_if_user_denies=False):
324 """RepoHook constructor.
325
326 Params:
327 hook_type: A string representing the type of hook. This is also used
328 to figure out the name of the file containing the hook. For
329 example: 'pre-upload'.
330 hooks_project: The project containing the repo hooks. If you have a
331 manifest, this is manifest.repo_hooks_project. OK if this is None,
332 which will make the hook a no-op.
333 topdir: Repo's top directory (the one containing the .repo directory).
334 Scripts will run with CWD as this directory. If you have a manifest,
335 this is manifest.topdir
336 abort_if_user_denies: If True, we'll throw a HookError() if the user
337 doesn't allow us to run the hook.
338 """
339 self._hook_type = hook_type
340 self._hooks_project = hooks_project
341 self._topdir = topdir
342 self._abort_if_user_denies = abort_if_user_denies
343
344 # Store the full path to the script for convenience.
345 if self._hooks_project:
346 self._script_fullpath = os.path.join(self._hooks_project.worktree,
347 self._hook_type + '.py')
348 else:
349 self._script_fullpath = None
350
351 def _GetHash(self):
352 """Return a hash of the contents of the hooks directory.
353
354 We'll just use git to do this. This hash has the property that if anything
355 changes in the directory we will return a different has.
356
357 SECURITY CONSIDERATION:
358 This hash only represents the contents of files in the hook directory, not
359 any other files imported or called by hooks. Changes to imported files
360 can change the script behavior without affecting the hash.
361
362 Returns:
363 A string representing the hash. This will always be ASCII so that it can
364 be printed to the user easily.
365 """
366 assert self._hooks_project, "Must have hooks to calculate their hash."
367
368 # We will use the work_git object rather than just calling GetRevisionId().
369 # That gives us a hash of the latest checked in version of the files that
370 # the user will actually be executing. Specifically, GetRevisionId()
371 # doesn't appear to change even if a user checks out a different version
372 # of the hooks repo (via git checkout) nor if a user commits their own revs.
373 #
374 # NOTE: Local (non-committed) changes will not be factored into this hash.
375 # I think this is OK, since we're really only worried about warning the user
376 # about upstream changes.
377 return self._hooks_project.work_git.rev_parse('HEAD')
378
379 def _GetMustVerb(self):
380 """Return 'must' if the hook is required; 'should' if not."""
381 if self._abort_if_user_denies:
382 return 'must'
383 else:
384 return 'should'
385
386 def _CheckForHookApproval(self):
387 """Check to see whether this hook has been approved.
388
389 We'll look at the hash of all of the hooks. If this matches the hash that
390 the user last approved, we're done. If it doesn't, we'll ask the user
391 about approval.
392
393 Note that we ask permission for each individual hook even though we use
394 the hash of all hooks when detecting changes. We'd like the user to be
395 able to approve / deny each hook individually. We only use the hash of all
396 hooks because there is no other easy way to detect changes to local imports.
397
398 Returns:
399 True if this hook is approved to run; False otherwise.
400
401 Raises:
402 HookError: Raised if the user doesn't approve and abort_if_user_denies
403 was passed to the consturctor.
404 """
Doug Anderson37282b42011-03-04 11:54:18 -0800405 hooks_config = self._hooks_project.config
406 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
407
408 # Get the last hash that the user approved for this hook; may be None.
409 old_hash = hooks_config.GetString(git_approval_key)
410
411 # Get the current hash so we can tell if scripts changed since approval.
412 new_hash = self._GetHash()
413
414 if old_hash is not None:
415 # User previously approved hook and asked not to be prompted again.
416 if new_hash == old_hash:
417 # Approval matched. We're done.
418 return True
419 else:
420 # Give the user a reason why we're prompting, since they last told
421 # us to "never ask again".
422 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
423 self._hook_type)
424 else:
425 prompt = ''
426
427 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
428 if sys.stdout.isatty():
429 prompt += ('Repo %s run the script:\n'
430 ' %s\n'
431 '\n'
432 'Do you want to allow this script to run '
433 '(yes/yes-never-ask-again/NO)? ') % (
434 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530435 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900436 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800437
438 # User is doing a one-time approval.
439 if response in ('y', 'yes'):
440 return True
441 elif response == 'yes-never-ask-again':
442 hooks_config.SetString(git_approval_key, new_hash)
443 return True
444
445 # For anything else, we'll assume no approval.
446 if self._abort_if_user_denies:
447 raise HookError('You must allow the %s hook or use --no-verify.' %
448 self._hook_type)
449
450 return False
451
452 def _ExecuteHook(self, **kwargs):
453 """Actually execute the given hook.
454
455 This will run the hook's 'main' function in our python interpreter.
456
457 Args:
458 kwargs: Keyword arguments to pass to the hook. These are often specific
459 to the hook type. For instance, pre-upload hooks will contain
460 a project_list.
461 """
462 # Keep sys.path and CWD stashed away so that we can always restore them
463 # upon function exit.
464 orig_path = os.getcwd()
465 orig_syspath = sys.path
466
467 try:
468 # Always run hooks with CWD as topdir.
469 os.chdir(self._topdir)
470
471 # Put the hook dir as the first item of sys.path so hooks can do
472 # relative imports. We want to replace the repo dir as [0] so
473 # hooks can't import repo files.
474 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
475
476 # Exec, storing global context in the context dict. We catch exceptions
477 # and convert to a HookError w/ just the failing traceback.
478 context = {}
479 try:
Anthony King70f68902014-05-05 21:15:34 +0100480 exec(compile(open(self._script_fullpath).read(),
481 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800482 except Exception:
483 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
484 traceback.format_exc(), self._hook_type))
485
486 # Running the script should have defined a main() function.
487 if 'main' not in context:
488 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
489
490
491 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
492 # We don't actually want hooks to define their main with this argument--
493 # it's there to remind them that their hook should always take **kwargs.
494 # For instance, a pre-upload hook should be defined like:
495 # def main(project_list, **kwargs):
496 #
497 # This allows us to later expand the API without breaking old hooks.
498 kwargs = kwargs.copy()
499 kwargs['hook_should_take_kwargs'] = True
500
501 # Call the main function in the hook. If the hook should cause the
502 # build to fail, it will raise an Exception. We'll catch that convert
503 # to a HookError w/ just the failing traceback.
504 try:
505 context['main'](**kwargs)
506 except Exception:
507 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
508 'above.' % (
509 traceback.format_exc(), self._hook_type))
510 finally:
511 # Restore sys.path and CWD.
512 sys.path = orig_syspath
513 os.chdir(orig_path)
514
515 def Run(self, user_allows_all_hooks, **kwargs):
516 """Run the hook.
517
518 If the hook doesn't exist (because there is no hooks project or because
519 this particular hook is not enabled), this is a no-op.
520
521 Args:
522 user_allows_all_hooks: If True, we will never prompt about running the
523 hook--we'll just assume it's OK to run it.
524 kwargs: Keyword arguments to pass to the hook. These are often specific
525 to the hook type. For instance, pre-upload hooks will contain
526 a project_list.
527
528 Raises:
529 HookError: If there was a problem finding the hook or the user declined
530 to run a required hook (from _CheckForHookApproval).
531 """
532 # No-op if there is no hooks project or if hook is disabled.
533 if ((not self._hooks_project) or
534 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
535 return
536
537 # Bail with a nice error if we can't find the hook.
538 if not os.path.isfile(self._script_fullpath):
539 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
540
541 # Make sure the user is OK with running the hook.
542 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
543 return
544
545 # Run the hook with the same version of python we're using.
546 self._ExecuteHook(**kwargs)
547
548
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700549class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600550 # These objects can be shared between several working trees.
551 shareable_files = ['description', 'info']
552 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
553 # These objects can only be used by a single working tree.
554 working_tree_files = ['config', 'packed-refs', 'shallow']
555 working_tree_dirs = ['logs', 'refs']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700556 def __init__(self,
557 manifest,
558 name,
559 remote,
560 gitdir,
David James8d201162013-10-11 17:03:19 -0700561 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700562 worktree,
563 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700564 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800565 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100566 rebase=True,
567 groups=None,
568 sync_c=False,
569 sync_s=False,
570 clone_depth=None,
571 upstream=None,
572 parent=None,
573 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900574 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700575 optimized_fetch=False,
576 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800577 """Init a Project object.
578
579 Args:
580 manifest: The XmlManifest object.
581 name: The `name` attribute of manifest.xml's project element.
582 remote: RemoteSpec object specifying its remote's properties.
583 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700584 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800585 worktree: Absolute path of git working tree.
586 relpath: Relative path of git working tree to repo's top directory.
587 revisionExpr: The `revision` attribute of manifest.xml's project element.
588 revisionId: git commit id for checking out.
589 rebase: The `rebase` attribute of manifest.xml's project element.
590 groups: The `groups` attribute of manifest.xml's project element.
591 sync_c: The `sync-c` attribute of manifest.xml's project element.
592 sync_s: The `sync-s` attribute of manifest.xml's project element.
593 upstream: The `upstream` attribute of manifest.xml's project element.
594 parent: The parent Project object.
595 is_derived: False if the project was explicitly defined in the manifest;
596 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400597 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900598 optimized_fetch: If True, when a project is set to a sha1 revision, only
599 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700600 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800601 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700602 self.manifest = manifest
603 self.name = name
604 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800605 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700606 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800607 if worktree:
608 self.worktree = worktree.replace('\\', '/')
609 else:
610 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700611 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700612 self.revisionExpr = revisionExpr
613
614 if revisionId is None \
615 and revisionExpr \
616 and IsId(revisionExpr):
617 self.revisionId = revisionExpr
618 else:
619 self.revisionId = revisionId
620
Mike Pontillod3153822012-02-28 11:53:24 -0800621 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700622 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700623 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800624 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900625 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700626 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800627 self.parent = parent
628 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900629 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800630 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800631
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700632 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700633 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500634 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500635 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700636 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100637 gitdir=self.gitdir,
638 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700639
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800640 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700641 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800642 else:
643 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700644 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700645 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700646 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400647 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700648 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700649
Doug Anderson37282b42011-03-04 11:54:18 -0800650 # This will be filled in if a project is later identified to be the
651 # project containing repo hooks.
652 self.enabled_repo_hooks = []
653
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800655 def Derived(self):
656 return self.is_derived
657
658 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700659 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600660 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700661
662 @property
663 def CurrentBranch(self):
664 """Obtain the name of the currently checked out branch.
665 The branch name omits the 'refs/heads/' prefix.
666 None is returned if the project is on a detached HEAD.
667 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700668 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669 if b.startswith(R_HEADS):
670 return b[len(R_HEADS):]
671 return None
672
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700673 def IsRebaseInProgress(self):
674 w = self.worktree
675 g = os.path.join(w, '.git')
676 return os.path.exists(os.path.join(g, 'rebase-apply')) \
677 or os.path.exists(os.path.join(g, 'rebase-merge')) \
678 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200679
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700680 def IsDirty(self, consider_untracked=True):
681 """Is the working directory modified in some way?
682 """
683 self.work_git.update_index('-q',
684 '--unmerged',
685 '--ignore-missing',
686 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900687 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700688 return True
689 if self.work_git.DiffZ('diff-files'):
690 return True
691 if consider_untracked and self.work_git.LsOthers():
692 return True
693 return False
694
695 _userident_name = None
696 _userident_email = None
697
698 @property
699 def UserName(self):
700 """Obtain the user's personal name.
701 """
702 if self._userident_name is None:
703 self._LoadUserIdentity()
704 return self._userident_name
705
706 @property
707 def UserEmail(self):
708 """Obtain the user's email address. This is very likely
709 to be their Gerrit login.
710 """
711 if self._userident_email is None:
712 self._LoadUserIdentity()
713 return self._userident_email
714
715 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900716 u = self.bare_git.var('GIT_COMMITTER_IDENT')
717 m = re.compile("^(.*) <([^>]*)> ").match(u)
718 if m:
719 self._userident_name = m.group(1)
720 self._userident_email = m.group(2)
721 else:
722 self._userident_name = ''
723 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700724
725 def GetRemote(self, name):
726 """Get the configuration for a single remote.
727 """
728 return self.config.GetRemote(name)
729
730 def GetBranch(self, name):
731 """Get the configuration for a single branch.
732 """
733 return self.config.GetBranch(name)
734
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700735 def GetBranches(self):
736 """Get all existing local branches.
737 """
738 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900739 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700740 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700741
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530742 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700743 if name.startswith(R_HEADS):
744 name = name[len(R_HEADS):]
745 b = self.GetBranch(name)
746 b.current = name == current
747 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900748 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700749 heads[name] = b
750
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530751 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700752 if name.startswith(R_PUB):
753 name = name[len(R_PUB):]
754 b = heads.get(name)
755 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900756 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700757
758 return heads
759
Colin Cross5acde752012-03-28 20:15:45 -0700760 def MatchesGroups(self, manifest_groups):
761 """Returns true if the manifest groups specified at init should cause
762 this project to be synced.
763 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700764 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700765
Conley Owens971de8e2012-04-16 10:36:08 -0700766 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700767 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700768 manifest_groups: "-group1,group2"
769 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500770
771 The special manifest group "default" will match any project that
772 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700773 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500774 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700775 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500776 if not 'notdefault' in expanded_project_groups:
777 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700778
Conley Owens971de8e2012-04-16 10:36:08 -0700779 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700780 for group in expanded_manifest_groups:
781 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700782 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700783 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700784 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700785
Conley Owens971de8e2012-04-16 10:36:08 -0700786 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700787
788## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700789 def UncommitedFiles(self, get_all=True):
790 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700792 Args:
793 get_all: a boolean, if True - get information about all different
794 uncommitted files. If False - return as soon as any kind of
795 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500796 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700797 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500798 self.work_git.update_index('-q',
799 '--unmerged',
800 '--ignore-missing',
801 '--refresh')
802 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700803 details.append("rebase in progress")
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-index', '--cached', HEAD).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.DiffZ('diff-files').keys()
814 if changes:
815 details.extend(changes)
816 if not get_all:
817 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500818
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700819 changes = self.work_git.LsOthers()
820 if changes:
821 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500822
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700823 return details
824
825 def HasChanges(self):
826 """Returns true if there are uncommitted changes.
827 """
828 if self.UncommitedFiles(get_all=False):
829 return True
830 else:
831 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500832
Terence Haddock4655e812011-03-31 12:33:34 +0200833 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200835
836 Args:
837 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 """
839 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200840 if output_redir == None:
841 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700842 print(file=output_redir)
843 print('project %s/' % self.relpath, file=output_redir)
844 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845 return
846
847 self.work_git.update_index('-q',
848 '--unmerged',
849 '--ignore-missing',
850 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700851 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700852 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
853 df = self.work_git.DiffZ('diff-files')
854 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100855 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700856 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857
858 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200859 if not output_redir == None:
860 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700861 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862
863 branch = self.CurrentBranch
864 if branch is None:
865 out.nobranch('(*** NO BRANCH ***)')
866 else:
867 out.branch('branch %s', branch)
868 out.nl()
869
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700870 if rb:
871 out.important('prior sync failed; rebase still in progress')
872 out.nl()
873
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874 paths = list()
875 paths.extend(di.keys())
876 paths.extend(df.keys())
877 paths.extend(do)
878
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530879 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900880 try:
881 i = di[p]
882 except KeyError:
883 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700884
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900885 try:
886 f = df[p]
887 except KeyError:
888 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200889
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900890 if i:
891 i_status = i.status.upper()
892 else:
893 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900895 if f:
896 f_status = f.status.lower()
897 else:
898 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899
900 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800901 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902 i.src_path, p, i.level)
903 else:
904 line = ' %s%s\t%s' % (i_status, f_status, p)
905
906 if i and not f:
907 out.added('%s', line)
908 elif (i and f) or (not i and f):
909 out.changed('%s', line)
910 elif not i and not f:
911 out.untracked('%s', line)
912 else:
913 out.write('%s', line)
914 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200915
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700916 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917
pelyad67872d2012-03-28 14:49:58 +0300918 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919 """Prints the status of the repository to stdout.
920 """
921 out = DiffColoring(self.config)
922 cmd = ['diff']
923 if out.is_on:
924 cmd.append('--color')
925 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300926 if absolute_paths:
927 cmd.append('--src-prefix=a/%s/' % self.relpath)
928 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929 cmd.append('--')
930 p = GitCommand(self,
931 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100932 capture_stdout=True,
933 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934 has_diff = False
935 for line in p.process.stdout:
936 if not has_diff:
937 out.nl()
938 out.project('project %s/' % self.relpath)
939 out.nl()
940 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700941 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 p.Wait()
943
944
945## Publish / Upload ##
946
David Pursehouse8a68ff92012-09-24 12:15:13 +0900947 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 """Was the branch published (uploaded) for code review?
949 If so, returns the SHA-1 hash of the last published
950 state for the branch.
951 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700952 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900953 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700954 try:
955 return self.bare_git.rev_parse(key)
956 except GitError:
957 return None
958 else:
959 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900960 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700961 except KeyError:
962 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963
David Pursehouse8a68ff92012-09-24 12:15:13 +0900964 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700965 """Prunes any stale published refs.
966 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900967 if all_refs is None:
968 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 heads = set()
970 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530971 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 if name.startswith(R_HEADS):
973 heads.add(name)
974 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900975 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700976
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530977 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978 n = name[len(R_PUB):]
979 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900980 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700982 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 """List any branches which can be uploaded for review.
984 """
985 heads = {}
986 pubed = {}
987
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530988 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900990 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900992 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700993
994 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530995 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900996 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700998 if selected_branch and branch != selected_branch:
999 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001001 rb = self.GetUploadableBranch(branch)
1002 if rb:
1003 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001004 return ready
1005
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001006 def GetUploadableBranch(self, branch_name):
1007 """Get a single uploadable branch, or None.
1008 """
1009 branch = self.GetBranch(branch_name)
1010 base = branch.LocalMerge
1011 if branch.LocalMerge:
1012 rb = ReviewableBranch(self, branch, base)
1013 if rb.commits:
1014 return rb
1015 return None
1016
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001017 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001018 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001019 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001020 draft=False,
1021 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022 """Uploads the named branch for code review.
1023 """
1024 if branch is None:
1025 branch = self.CurrentBranch
1026 if branch is None:
1027 raise GitError('not currently on a branch')
1028
1029 branch = self.GetBranch(branch)
1030 if not branch.LocalMerge:
1031 raise GitError('branch %s does not track a remote' % branch.name)
1032 if not branch.remote.review:
1033 raise GitError('remote %s has no review url' % branch.remote.name)
1034
Bryan Jacobsf609f912013-05-06 13:36:24 -04001035 if dest_branch is None:
1036 dest_branch = self.dest_branch
1037 if dest_branch is None:
1038 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039 if not dest_branch.startswith(R_HEADS):
1040 dest_branch = R_HEADS + dest_branch
1041
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001042 if not branch.remote.projectname:
1043 branch.remote.projectname = self.name
1044 branch.remote.Save()
1045
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001046 url = branch.remote.ReviewUrl(self.UserEmail)
1047 if url is None:
1048 raise UploadError('review not configured')
1049 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001050
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001051 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001052 rp = ['gerrit receive-pack']
1053 for e in people[0]:
1054 rp.append('--reviewer=%s' % sq(e))
1055 for e in people[1]:
1056 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001057 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001058
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001059 cmd.append(url)
1060
1061 if dest_branch.startswith(R_HEADS):
1062 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001063
1064 upload_type = 'for'
1065 if draft:
1066 upload_type = 'drafts'
1067
1068 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1069 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001070 if auto_topic:
1071 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001072 if not url.startswith('ssh://'):
1073 rp = ['r=%s' % p for p in people[0]] + \
1074 ['cc=%s' % p for p in people[1]]
1075 if rp:
1076 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001077 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001078
Anthony King7bdac712014-07-16 12:56:40 +01001079 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001080 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081
1082 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1083 self.bare_git.UpdateRef(R_PUB + branch.name,
1084 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001085 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086
1087
1088## Sync ##
1089
Julien Campergue335f5ef2013-10-16 11:02:35 +02001090 def _ExtractArchive(self, tarpath, path=None):
1091 """Extract the given tar on its current location
1092
1093 Args:
1094 - tarpath: The path to the actual tar file
1095
1096 """
1097 try:
1098 with tarfile.open(tarpath, 'r') as tar:
1099 tar.extractall(path=path)
1100 return True
1101 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001102 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001103 return False
1104
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001105 def Sync_NetworkHalf(self,
1106 quiet=False,
1107 is_new=None,
1108 current_branch_only=False,
Kevin Degiabaa7f32014-11-12 11:27:45 -07001109 force_sync=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001110 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001111 no_tags=False,
David Pursehouseb1553542014-09-04 21:28:09 +09001112 archive=False,
1113 optimized_fetch=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001114 """Perform only the network IO portion of the sync process.
1115 Local working directory/branch state is not affected.
1116 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001117 if archive and not isinstance(self, MetaProject):
1118 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001119 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001120 return False
1121
1122 name = self.relpath.replace('\\', '/')
1123 name = name.replace('/', '_')
1124 tarpath = '%s.tar' % name
1125 topdir = self.manifest.topdir
1126
1127 try:
1128 self._FetchArchive(tarpath, cwd=topdir)
1129 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001130 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001131 return False
1132
1133 # From now on, we only need absolute tarpath
1134 tarpath = os.path.join(topdir, tarpath)
1135
1136 if not self._ExtractArchive(tarpath, path=topdir):
1137 return False
1138 try:
1139 os.remove(tarpath)
1140 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001141 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001142 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001143 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001144 if is_new is None:
1145 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001146 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001147 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001148 else:
1149 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001150 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001151
1152 if is_new:
1153 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1154 try:
1155 fd = open(alt, 'rb')
1156 try:
1157 alt_dir = fd.readline().rstrip()
1158 finally:
1159 fd.close()
1160 except IOError:
1161 alt_dir = None
1162 else:
1163 alt_dir = None
1164
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001165 if clone_bundle \
1166 and alt_dir is None \
1167 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001168 is_new = False
1169
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001170 if not current_branch_only:
1171 if self.sync_c:
1172 current_branch_only = True
1173 elif not self.manifest._loaded:
1174 # Manifest cannot check defaults until it syncs.
1175 current_branch_only = False
1176 elif self.manifest.default.sync_c:
1177 current_branch_only = True
1178
David Pursehouseb1553542014-09-04 21:28:09 +09001179 need_to_fetch = not (optimized_fetch and \
1180 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1181 if (need_to_fetch
Conley Owens666d5342014-05-01 13:09:57 -07001182 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1183 current_branch_only=current_branch_only,
1184 no_tags=no_tags)):
Anthony King7bdac712014-07-16 12:56:40 +01001185 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001186
1187 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001188 self._InitMRef()
1189 else:
1190 self._InitMirrorHead()
1191 try:
1192 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1193 except OSError:
1194 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001196
1197 def PostRepoUpgrade(self):
1198 self._InitHooks()
1199
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001200 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001201 if self.manifest.isGitcClient:
1202 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001203 for copyfile in self.copyfiles:
1204 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001205 for linkfile in self.linkfiles:
1206 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001207
Julien Camperguedd654222014-01-09 16:21:37 +01001208 def GetCommitRevisionId(self):
1209 """Get revisionId of a commit.
1210
1211 Use this method instead of GetRevisionId to get the id of the commit rather
1212 than the id of the current git object (for example, a tag)
1213
1214 """
1215 if not self.revisionExpr.startswith(R_TAGS):
1216 return self.GetRevisionId(self._allrefs)
1217
1218 try:
1219 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1220 except GitError:
1221 raise ManifestInvalidRevisionError(
1222 'revision %s in %s not found' % (self.revisionExpr,
1223 self.name))
1224
David Pursehouse8a68ff92012-09-24 12:15:13 +09001225 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001226 if self.revisionId:
1227 return self.revisionId
1228
1229 rem = self.GetRemote(self.remote.name)
1230 rev = rem.ToLocal(self.revisionExpr)
1231
David Pursehouse8a68ff92012-09-24 12:15:13 +09001232 if all_refs is not None and rev in all_refs:
1233 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001234
1235 try:
1236 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1237 except GitError:
1238 raise ManifestInvalidRevisionError(
1239 'revision %s in %s not found' % (self.revisionExpr,
1240 self.name))
1241
Kevin Degiabaa7f32014-11-12 11:27:45 -07001242 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001243 """Perform only the local IO portion of the sync process.
1244 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001245 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001246 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001247 all_refs = self.bare_ref.all
1248 self.CleanPublishedCache(all_refs)
1249 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001250
David Pursehouse1d947b32012-10-25 12:23:11 +09001251 def _doff():
1252 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001253 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001254
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001255 head = self.work_git.GetHead()
1256 if head.startswith(R_HEADS):
1257 branch = head[len(R_HEADS):]
1258 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001259 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001260 except KeyError:
1261 head = None
1262 else:
1263 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001264
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001265 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001266 # Currently on a detached HEAD. The user is assumed to
1267 # not have any local modifications worth worrying about.
1268 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001269 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001270 syncbuf.fail(self, _PriorSyncFailedError())
1271 return
1272
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001273 if head == revid:
1274 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001275 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001276 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001277 if not syncbuf.detach_head:
1278 return
1279 else:
1280 lost = self._revlist(not_rev(revid), HEAD)
1281 if lost:
1282 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001283
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001284 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001285 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001286 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001287 syncbuf.fail(self, e)
1288 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001289 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001290 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001291
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001292 if head == revid:
1293 # No changes; don't do anything further.
1294 #
1295 return
1296
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001299 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001301 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001303 syncbuf.info(self,
1304 "leaving %s; does not track upstream",
1305 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001307 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001308 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001309 syncbuf.fail(self, e)
1310 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001311 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001312 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001313
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001314 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001315 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001317 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318 if not_merged:
1319 if upstream_gain:
1320 # The user has published this branch and some of those
1321 # commits are not yet merged upstream. We do not want
1322 # to rewrite the published commits so we punt.
1323 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001324 syncbuf.fail(self,
1325 "branch %s is published (but not merged) and is now %d commits behind"
1326 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001327 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001328 elif pub == head:
1329 # All published commits are merged, and thus we are a
1330 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001331 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001332 syncbuf.later1(self, _doff)
1333 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001334
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001335 # Examine the local commits not in the remote. Find the
1336 # last one attributed to this user, if any.
1337 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001338 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001339 last_mine = None
1340 cnt_mine = 0
1341 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301342 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001343 if committer_email == self.UserEmail:
1344 last_mine = commit_id
1345 cnt_mine += 1
1346
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001347 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001348 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001349
1350 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001351 syncbuf.fail(self, _DirtyError())
1352 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001354 # If the upstream switched on us, warn the user.
1355 #
1356 if branch.merge != self.revisionExpr:
1357 if branch.merge and self.revisionExpr:
1358 syncbuf.info(self,
1359 'manifest switched %s...%s',
1360 branch.merge,
1361 self.revisionExpr)
1362 elif branch.merge:
1363 syncbuf.info(self,
1364 'manifest no longer tracks %s',
1365 branch.merge)
1366
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001367 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001369 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001370 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001371 syncbuf.info(self,
1372 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001373 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001374
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001375 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001376 if not ID_RE.match(self.revisionExpr):
1377 # in case of manifest sync the revisionExpr might be a SHA1
1378 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001379 if not branch.merge.startswith('refs/'):
1380 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381 branch.Save()
1382
Mike Pontillod3153822012-02-28 11:53:24 -08001383 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001384 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001385 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001386 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001387 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001388 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001390 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001391 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001392 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001393 syncbuf.fail(self, e)
1394 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001395 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001396 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001397
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001398 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399 # dest should already be an absolute path, but src is project relative
1400 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001401 abssrc = os.path.join(self.worktree, src)
1402 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001404 def AddLinkFile(self, src, dest, absdest):
1405 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001406 # make src relative path to dest
1407 absdestdir = os.path.dirname(absdest)
1408 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001409 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001410
James W. Mills24c13082012-04-12 15:04:13 -05001411 def AddAnnotation(self, name, value, keep):
1412 self.annotations.append(_Annotation(name, value, keep))
1413
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001414 def DownloadPatchSet(self, change_id, patch_id):
1415 """Download a single patch set of a single change to FETCH_HEAD.
1416 """
1417 remote = self.GetRemote(self.remote.name)
1418
1419 cmd = ['fetch', remote.name]
1420 cmd.append('refs/changes/%2.2d/%d/%d' \
1421 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001422 if GitCommand(self, cmd, bare=True).Wait() != 0:
1423 return None
1424 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001425 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001426 change_id,
1427 patch_id,
1428 self.bare_git.rev_parse('FETCH_HEAD'))
1429
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001430
1431## Branch Management ##
1432
Simran Basib9a1b732015-08-20 12:19:28 -07001433 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001434 """Create a new branch off the manifest's revision.
1435 """
Simran Basib9a1b732015-08-20 12:19:28 -07001436 if not branch_merge:
1437 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001438 head = self.work_git.GetHead()
1439 if head == (R_HEADS + name):
1440 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001441
David Pursehouse8a68ff92012-09-24 12:15:13 +09001442 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001443 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001444 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001445 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001446 capture_stdout=True,
1447 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001448
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001449 branch = self.GetBranch(name)
1450 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001451 branch.merge = branch_merge
1452 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1453 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001454 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001455
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001456 if head.startswith(R_HEADS):
1457 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001458 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001459 except KeyError:
1460 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001461 if revid and head and revid == head:
1462 ref = os.path.join(self.gitdir, R_HEADS + name)
1463 try:
1464 os.makedirs(os.path.dirname(ref))
1465 except OSError:
1466 pass
1467 _lwrite(ref, '%s\n' % revid)
1468 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1469 'ref: %s%s\n' % (R_HEADS, name))
1470 branch.Save()
1471 return True
1472
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001473 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001474 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001475 capture_stdout=True,
1476 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001477 branch.Save()
1478 return True
1479 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001480
Wink Saville02d79452009-04-10 13:01:24 -07001481 def CheckoutBranch(self, name):
1482 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001483
1484 Args:
1485 name: The name of the branch to checkout.
1486
1487 Returns:
1488 True if the checkout succeeded; False if it didn't; None if the branch
1489 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001490 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001491 rev = R_HEADS + name
1492 head = self.work_git.GetHead()
1493 if head == rev:
1494 # Already on the branch
1495 #
1496 return True
Wink Saville02d79452009-04-10 13:01:24 -07001497
David Pursehouse8a68ff92012-09-24 12:15:13 +09001498 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001499 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001500 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001501 except KeyError:
1502 # Branch does not exist in this project
1503 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001504 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001505
1506 if head.startswith(R_HEADS):
1507 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001508 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001509 except KeyError:
1510 head = None
1511
1512 if head == revid:
1513 # Same revision; just update HEAD to point to the new
1514 # target branch, but otherwise take no other action.
1515 #
1516 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1517 'ref: %s%s\n' % (R_HEADS, name))
1518 return True
Wink Saville02d79452009-04-10 13:01:24 -07001519
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001520 return GitCommand(self,
1521 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001522 capture_stdout=True,
1523 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001524
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001525 def AbandonBranch(self, name):
1526 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001527
1528 Args:
1529 name: The name of the branch to abandon.
1530
1531 Returns:
1532 True if the abandon succeeded; False if it didn't; None if the branch
1533 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001534 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001535 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001536 all_refs = self.bare_ref.all
1537 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001538 # Doesn't exist
1539 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001540
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001541 head = self.work_git.GetHead()
1542 if head == rev:
1543 # We can't destroy the branch while we are sitting
1544 # on it. Switch to a detached HEAD.
1545 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001546 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001547
David Pursehouse8a68ff92012-09-24 12:15:13 +09001548 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001549 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001550 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1551 '%s\n' % revid)
1552 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001553 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001554
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001555 return GitCommand(self,
1556 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001557 capture_stdout=True,
1558 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001559
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001560 def PruneHeads(self):
1561 """Prune any topic branches already merged into upstream.
1562 """
1563 cb = self.CurrentBranch
1564 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001565 left = self._allrefs
1566 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001567 if name.startswith(R_HEADS):
1568 name = name[len(R_HEADS):]
1569 if cb is None or name != cb:
1570 kill.append(name)
1571
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001572 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001573 if cb is not None \
1574 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001575 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001576 self.work_git.DetachHead(HEAD)
1577 kill.append(cb)
1578
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001579 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001580 old = self.bare_git.GetHead()
1581 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001582 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1583
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001584 try:
1585 self.bare_git.DetachHead(rev)
1586
1587 b = ['branch', '-d']
1588 b.extend(kill)
1589 b = GitCommand(self, b, bare=True,
1590 capture_stdout=True,
1591 capture_stderr=True)
1592 b.Wait()
1593 finally:
1594 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001595 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001596
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001597 for branch in kill:
1598 if (R_HEADS + branch) not in left:
1599 self.CleanPublishedCache()
1600 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001601
1602 if cb and cb not in kill:
1603 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001604 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001605
1606 kept = []
1607 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001608 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001609 branch = self.GetBranch(branch)
1610 base = branch.LocalMerge
1611 if not base:
1612 base = rev
1613 kept.append(ReviewableBranch(self, branch, base))
1614 return kept
1615
1616
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001617## Submodule Management ##
1618
1619 def GetRegisteredSubprojects(self):
1620 result = []
1621 def rec(subprojects):
1622 if not subprojects:
1623 return
1624 result.extend(subprojects)
1625 for p in subprojects:
1626 rec(p.subprojects)
1627 rec(self.subprojects)
1628 return result
1629
1630 def _GetSubmodules(self):
1631 # Unfortunately we cannot call `git submodule status --recursive` here
1632 # because the working tree might not exist yet, and it cannot be used
1633 # without a working tree in its current implementation.
1634
1635 def get_submodules(gitdir, rev):
1636 # Parse .gitmodules for submodule sub_paths and sub_urls
1637 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1638 if not sub_paths:
1639 return []
1640 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1641 # revision of submodule repository
1642 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1643 submodules = []
1644 for sub_path, sub_url in zip(sub_paths, sub_urls):
1645 try:
1646 sub_rev = sub_revs[sub_path]
1647 except KeyError:
1648 # Ignore non-exist submodules
1649 continue
1650 submodules.append((sub_rev, sub_path, sub_url))
1651 return submodules
1652
1653 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1654 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1655 def parse_gitmodules(gitdir, rev):
1656 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1657 try:
Anthony King7bdac712014-07-16 12:56:40 +01001658 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1659 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001660 except GitError:
1661 return [], []
1662 if p.Wait() != 0:
1663 return [], []
1664
1665 gitmodules_lines = []
1666 fd, temp_gitmodules_path = tempfile.mkstemp()
1667 try:
1668 os.write(fd, p.stdout)
1669 os.close(fd)
1670 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001671 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1672 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001673 if p.Wait() != 0:
1674 return [], []
1675 gitmodules_lines = p.stdout.split('\n')
1676 except GitError:
1677 return [], []
1678 finally:
1679 os.remove(temp_gitmodules_path)
1680
1681 names = set()
1682 paths = {}
1683 urls = {}
1684 for line in gitmodules_lines:
1685 if not line:
1686 continue
1687 m = re_path.match(line)
1688 if m:
1689 names.add(m.group(1))
1690 paths[m.group(1)] = m.group(2)
1691 continue
1692 m = re_url.match(line)
1693 if m:
1694 names.add(m.group(1))
1695 urls[m.group(1)] = m.group(2)
1696 continue
1697 names = sorted(names)
1698 return ([paths.get(name, '') for name in names],
1699 [urls.get(name, '') for name in names])
1700
1701 def git_ls_tree(gitdir, rev, paths):
1702 cmd = ['ls-tree', rev, '--']
1703 cmd.extend(paths)
1704 try:
Anthony King7bdac712014-07-16 12:56:40 +01001705 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1706 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001707 except GitError:
1708 return []
1709 if p.Wait() != 0:
1710 return []
1711 objects = {}
1712 for line in p.stdout.split('\n'):
1713 if not line.strip():
1714 continue
1715 object_rev, object_path = line.split()[2:4]
1716 objects[object_path] = object_rev
1717 return objects
1718
1719 try:
1720 rev = self.GetRevisionId()
1721 except GitError:
1722 return []
1723 return get_submodules(self.gitdir, rev)
1724
1725 def GetDerivedSubprojects(self):
1726 result = []
1727 if not self.Exists:
1728 # If git repo does not exist yet, querying its submodules will
1729 # mess up its states; so return here.
1730 return result
1731 for rev, path, url in self._GetSubmodules():
1732 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001733 relpath, worktree, gitdir, objdir = \
1734 self.manifest.GetSubprojectPaths(self, name, path)
1735 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001736 if project:
1737 result.extend(project.GetDerivedSubprojects())
1738 continue
David James8d201162013-10-11 17:03:19 -07001739
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001740 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001741 url=url,
1742 review=self.remote.review,
1743 revision=self.remote.revision)
1744 subproject = Project(manifest=self.manifest,
1745 name=name,
1746 remote=remote,
1747 gitdir=gitdir,
1748 objdir=objdir,
1749 worktree=worktree,
1750 relpath=relpath,
1751 revisionExpr=self.revisionExpr,
1752 revisionId=rev,
1753 rebase=self.rebase,
1754 groups=self.groups,
1755 sync_c=self.sync_c,
1756 sync_s=self.sync_s,
1757 parent=self,
1758 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001759 result.append(subproject)
1760 result.extend(subproject.GetDerivedSubprojects())
1761 return result
1762
1763
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001764## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001765 def _CheckForSha1(self):
1766 try:
1767 # if revision (sha or tag) is not present then following function
1768 # throws an error.
1769 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1770 return True
1771 except GitError:
1772 # There is no such persistent revision. We have to fetch it.
1773 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001774
Julien Campergue335f5ef2013-10-16 11:02:35 +02001775 def _FetchArchive(self, tarpath, cwd=None):
1776 cmd = ['archive', '-v', '-o', tarpath]
1777 cmd.append('--remote=%s' % self.remote.url)
1778 cmd.append('--prefix=%s/' % self.relpath)
1779 cmd.append(self.revisionExpr)
1780
1781 command = GitCommand(self, cmd, cwd=cwd,
1782 capture_stdout=True,
1783 capture_stderr=True)
1784
1785 if command.Wait() != 0:
1786 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1787
Conley Owens80b87fe2014-05-09 17:13:44 -07001788
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001789 def _RemoteFetch(self, name=None,
1790 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001791 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001792 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001793 alt_dir=None,
1794 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001795
1796 is_sha1 = False
1797 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001798 depth = None
1799
1800 # The depth should not be used when fetching to a mirror because
1801 # it will result in a shallow repository that cannot be cloned or
1802 # fetched from.
1803 if not self.manifest.IsMirror:
1804 if self.clone_depth:
1805 depth = self.clone_depth
1806 else:
1807 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001808 # The repo project should never be synced with partial depth
1809 if self.relpath == '.repo/repo':
1810 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001811
Shawn Pearce69e04d82014-01-29 12:48:54 -08001812 if depth:
1813 current_branch_only = True
1814
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001815 if ID_RE.match(self.revisionExpr) is not None:
1816 is_sha1 = True
1817
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001818 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001819 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001820 # this is a tag and its sha1 value should never change
1821 tag_name = self.revisionExpr[len(R_TAGS):]
1822
1823 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001824 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001825 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001826 if is_sha1 and not depth:
1827 # When syncing a specific commit and --depth is not set:
1828 # * if upstream is explicitly specified and is not a sha1, fetch only
1829 # upstream as users expect only upstream to be fetch.
1830 # Note: The commit might not be in upstream in which case the sync
1831 # will fail.
1832 # * otherwise, fetch all branches to make sure we end up with the
1833 # specific commit.
1834 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001835
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001836 if not name:
1837 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001838
1839 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001840 remote = self.GetRemote(name)
1841 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001842 ssh_proxy = True
1843
Shawn O. Pearce88443382010-10-08 10:02:09 +02001844 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001845 if alt_dir and 'objects' == os.path.basename(alt_dir):
1846 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001847 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1848 remote = self.GetRemote(name)
1849
David Pursehouse8a68ff92012-09-24 12:15:13 +09001850 all_refs = self.bare_ref.all
1851 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001852 tmp = set()
1853
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301854 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001855 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001856 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001857 all_refs[r] = ref_id
1858 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001859 continue
1860
David Pursehouse8a68ff92012-09-24 12:15:13 +09001861 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001862 continue
1863
David Pursehouse8a68ff92012-09-24 12:15:13 +09001864 r = 'refs/_alt/%s' % ref_id
1865 all_refs[r] = ref_id
1866 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001867 tmp.add(r)
1868
Shawn O. Pearce88443382010-10-08 10:02:09 +02001869 tmp_packed = ''
1870 old_packed = ''
1871
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301872 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001873 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001874 tmp_packed += line
1875 if r not in tmp:
1876 old_packed += line
1877
1878 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001879 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001880 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001881
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001882 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001883
Conley Owensf97e8382015-01-21 11:12:46 -08001884 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001885 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07001886 else:
1887 # If this repo has shallow objects, then we don't know which refs have
1888 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
1889 # do this with projects that don't have shallow objects, since it is less
1890 # efficient.
1891 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
1892 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07001893
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001894 if quiet:
1895 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001896 if not self.worktree:
1897 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001898 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001899
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001900 # If using depth then we should not get all the tags since they may
1901 # be outside of the depth.
1902 if no_tags or depth:
1903 cmd.append('--no-tags')
1904 else:
1905 cmd.append('--tags')
1906
Conley Owens80b87fe2014-05-09 17:13:44 -07001907 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001908 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001909 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001910 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001911 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001912 spec.append('tag')
1913 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001914
David Pursehouse403b64e2015-04-27 10:41:33 +09001915 if not self.manifest.IsMirror:
1916 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06001917 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001918 # Shallow checkout of a specific commit, fetch from that commit and not
1919 # the heads only as the commit might be deeper in the history.
1920 spec.append(branch)
1921 else:
1922 if is_sha1:
1923 branch = self.upstream
1924 if branch is not None and branch.strip():
1925 if not branch.startswith('refs/'):
1926 branch = R_HEADS + branch
1927 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001928 cmd.extend(spec)
1929
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001930 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001931 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001932 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001933 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001934 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001935 ok = True
1936 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001937 # If needed, run the 'git remote prune' the first time through the loop
1938 elif (not _i and
1939 "error:" in gitcmd.stderr and
1940 "git remote prune" in gitcmd.stderr):
1941 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001942 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001943 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001944 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001945 break
1946 continue
Brian Harring14a66742012-09-28 20:21:57 -07001947 elif current_branch_only and is_sha1 and ret == 128:
1948 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1949 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1950 # abort the optimization attempt and do a full sync.
1951 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001952 elif ret < 0:
1953 # Git died with a signal, exit immediately
1954 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001955 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001956
1957 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001958 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001959 if old_packed != '':
1960 _lwrite(packed_refs, old_packed)
1961 else:
1962 os.remove(packed_refs)
1963 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001964
1965 if is_sha1 and current_branch_only and self.upstream:
1966 # We just synced the upstream given branch; verify we
1967 # got what we wanted, else trigger a second run of all
1968 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001969 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06001970 if not depth:
1971 # Avoid infinite recursion when depth is True (since depth implies
1972 # current_branch_only)
1973 return self._RemoteFetch(name=name, current_branch_only=False,
1974 initial=False, quiet=quiet, alt_dir=alt_dir)
1975 if self.clone_depth:
1976 self.clone_depth = None
1977 return self._RemoteFetch(name=name, current_branch_only=current_branch_only,
1978 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07001979
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001980 return ok
1981
1982 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001983 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001984 return False
1985
1986 remote = self.GetRemote(self.remote.name)
1987 bundle_url = remote.url + '/clone.bundle'
1988 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001989 if GetSchemeFromUrl(bundle_url) not in (
1990 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001991 return False
1992
1993 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1994 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001995
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001996 exist_dst = os.path.exists(bundle_dst)
1997 exist_tmp = os.path.exists(bundle_tmp)
1998
1999 if not initial and not exist_dst and not exist_tmp:
2000 return False
2001
2002 if not exist_dst:
2003 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2004 if not exist_dst:
2005 return False
2006
2007 cmd = ['fetch']
2008 if quiet:
2009 cmd.append('--quiet')
2010 if not self.worktree:
2011 cmd.append('--update-head-ok')
2012 cmd.append(bundle_dst)
2013 for f in remote.fetch:
2014 cmd.append(str(f))
2015 cmd.append('refs/tags/*:refs/tags/*')
2016
2017 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002018 if os.path.exists(bundle_dst):
2019 os.remove(bundle_dst)
2020 if os.path.exists(bundle_tmp):
2021 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002022 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002023
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002024 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002025 if os.path.exists(dstPath):
2026 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07002027
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002028 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002029 if quiet:
2030 cmd += ['--silent']
2031 if os.path.exists(tmpPath):
2032 size = os.stat(tmpPath).st_size
2033 if size >= 1024:
2034 cmd += ['--continue-at', '%d' % (size,)]
2035 else:
2036 os.remove(tmpPath)
2037 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2038 cmd += ['--proxy', os.environ['http_proxy']]
Dan Willemsen0745bb22015-08-17 13:41:45 -07002039 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002040 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002041 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002042 if srcUrl.startswith('persistent-'):
2043 srcUrl = srcUrl[len('persistent-'):]
2044 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002045
Dave Borowitz137d0132015-01-02 11:12:54 -08002046 if IsTrace():
2047 Trace('%s', ' '.join(cmd))
2048 try:
2049 proc = subprocess.Popen(cmd)
2050 except OSError:
2051 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002052
Dave Borowitz137d0132015-01-02 11:12:54 -08002053 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002054
Dave Borowitz137d0132015-01-02 11:12:54 -08002055 if curlret == 22:
2056 # From curl man page:
2057 # 22: HTTP page not retrieved. The requested url was not found or
2058 # returned another error with the HTTP error code being 400 or above.
2059 # This return code only appears if -f, --fail is used.
2060 if not quiet:
2061 print("Server does not provide clone.bundle; ignoring.",
2062 file=sys.stderr)
2063 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002064
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002065 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002066 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002067 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002068 return True
2069 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002070 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002071 return False
2072 else:
2073 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002074
Kris Giesingc8d882a2014-12-23 13:02:32 -08002075 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002076 try:
2077 with open(path) as f:
2078 if f.read(16) == '# v2 git bundle\n':
2079 return True
2080 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002081 if not quiet:
2082 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002083 return False
2084 except OSError:
2085 return False
2086
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002087 def _Checkout(self, rev, quiet=False):
2088 cmd = ['checkout']
2089 if quiet:
2090 cmd.append('-q')
2091 cmd.append(rev)
2092 cmd.append('--')
2093 if GitCommand(self, cmd).Wait() != 0:
2094 if self._allrefs:
2095 raise GitError('%s checkout %s ' % (self.name, rev))
2096
Anthony King7bdac712014-07-16 12:56:40 +01002097 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002098 cmd = ['cherry-pick']
2099 cmd.append(rev)
2100 cmd.append('--')
2101 if GitCommand(self, cmd).Wait() != 0:
2102 if self._allrefs:
2103 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2104
Anthony King7bdac712014-07-16 12:56:40 +01002105 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002106 cmd = ['revert']
2107 cmd.append('--no-edit')
2108 cmd.append(rev)
2109 cmd.append('--')
2110 if GitCommand(self, cmd).Wait() != 0:
2111 if self._allrefs:
2112 raise GitError('%s revert %s ' % (self.name, rev))
2113
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002114 def _ResetHard(self, rev, quiet=True):
2115 cmd = ['reset', '--hard']
2116 if quiet:
2117 cmd.append('-q')
2118 cmd.append(rev)
2119 if GitCommand(self, cmd).Wait() != 0:
2120 raise GitError('%s reset --hard %s ' % (self.name, rev))
2121
Anthony King7bdac712014-07-16 12:56:40 +01002122 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002123 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002124 if onto is not None:
2125 cmd.extend(['--onto', onto])
2126 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002127 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002128 raise GitError('%s rebase %s ' % (self.name, upstream))
2129
Pierre Tardy3d125942012-05-04 12:18:12 +02002130 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002131 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002132 if ffonly:
2133 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134 if GitCommand(self, cmd).Wait() != 0:
2135 raise GitError('%s merge %s ' % (self.name, head))
2136
Kevin Degiabaa7f32014-11-12 11:27:45 -07002137 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002138 init_git_dir = not os.path.exists(self.gitdir)
2139 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002140 try:
2141 # Initialize the bare repository, which contains all of the objects.
2142 if init_obj_dir:
2143 os.makedirs(self.objdir)
2144 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002145
Kevin Degib1a07b82015-07-27 13:33:43 -06002146 # If we have a separate directory to hold refs, initialize it as well.
2147 if self.objdir != self.gitdir:
2148 if init_git_dir:
2149 os.makedirs(self.gitdir)
Kevin Degi384b3c52014-10-16 16:02:58 -06002150
Kevin Degib1a07b82015-07-27 13:33:43 -06002151 if init_obj_dir or init_git_dir:
2152 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2153 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002154 try:
2155 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2156 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002157 if force_sync:
David Pursehouse25857b82015-08-19 18:06:22 +09002158 print("Retrying clone after deleting %s" % self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002159 try:
2160 shutil.rmtree(os.path.realpath(self.gitdir))
2161 if self.worktree and os.path.exists(
2162 os.path.realpath(self.worktree)):
2163 shutil.rmtree(os.path.realpath(self.worktree))
2164 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2165 except:
2166 raise e
2167 raise e
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002168
Kevin Degib1a07b82015-07-27 13:33:43 -06002169 if init_git_dir:
2170 mp = self.manifest.manifestProject
2171 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002172
Kevin Degib1a07b82015-07-27 13:33:43 -06002173 if ref_dir or mirror_git:
2174 if not mirror_git:
2175 mirror_git = os.path.join(ref_dir, self.name + '.git')
2176 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2177 self.relpath + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002178
Kevin Degib1a07b82015-07-27 13:33:43 -06002179 if os.path.exists(mirror_git):
2180 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002181
Kevin Degib1a07b82015-07-27 13:33:43 -06002182 elif os.path.exists(repo_git):
2183 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002184
Kevin Degib1a07b82015-07-27 13:33:43 -06002185 else:
2186 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002187
Kevin Degib1a07b82015-07-27 13:33:43 -06002188 if ref_dir:
2189 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2190 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002191
Kevin Degib1a07b82015-07-27 13:33:43 -06002192 self._UpdateHooks()
Jimmie Westera0444582012-10-24 13:44:42 +02002193
Kevin Degib1a07b82015-07-27 13:33:43 -06002194 m = self.manifest.manifestProject.config
2195 for key in ['user.name', 'user.email']:
2196 if m.Has(key, include_defaults=False):
2197 self.config.SetString(key, m.GetString(key))
2198 if self.manifest.IsMirror:
2199 self.config.SetString('core.bare', 'true')
2200 else:
2201 self.config.SetString('core.bare', None)
2202 except Exception:
2203 if init_obj_dir and os.path.exists(self.objdir):
2204 shutil.rmtree(self.objdir)
2205 if init_git_dir and os.path.exists(self.gitdir):
2206 shutil.rmtree(self.gitdir)
2207 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002208
Jimmie Westera0444582012-10-24 13:44:42 +02002209 def _UpdateHooks(self):
2210 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002211 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002212
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002213 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002214 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002215 if not os.path.exists(hooks):
2216 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002217 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002218 name = os.path.basename(stock_hook)
2219
Victor Boivie65e0f352011-04-18 11:23:29 +02002220 if name in ('commit-msg',) and not self.remote.review \
2221 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002222 # Don't install a Gerrit Code Review hook if this
2223 # project does not appear to use it for reviews.
2224 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002225 # Since the manifest project is one of those, but also
2226 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002227 continue
2228
2229 dst = os.path.join(hooks, name)
2230 if os.path.islink(dst):
2231 continue
2232 if os.path.exists(dst):
2233 if filecmp.cmp(stock_hook, dst, shallow=False):
2234 os.remove(dst)
2235 else:
David Pursehousedc2545c2015-08-24 14:43:45 +09002236 _warn("%s: Not replacing locally modified %s hook", self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002237 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002238 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002239 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002240 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002241 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002242 raise GitError('filesystem must support symlinks')
2243 else:
2244 raise
2245
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002246 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002247 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002248 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002249 remote.url = self.remote.url
2250 remote.review = self.remote.review
2251 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002252
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002253 if self.worktree:
2254 remote.ResetFetch(mirror=False)
2255 else:
2256 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002257 remote.Save()
2258
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002259 def _InitMRef(self):
2260 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002261 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002262
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002263 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002264 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002265
2266 def _InitAnyMRef(self, ref):
2267 cur = self.bare_ref.symref(ref)
2268
2269 if self.revisionId:
2270 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2271 msg = 'manifest set to %s' % self.revisionId
2272 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002273 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002274 else:
2275 remote = self.GetRemote(self.remote.name)
2276 dst = remote.ToLocal(self.revisionExpr)
2277 if cur != dst:
2278 msg = 'manifest set to %s' % self.revisionExpr
2279 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002280
Kevin Degi384b3c52014-10-16 16:02:58 -06002281 def _CheckDirReference(self, srcdir, destdir, share_refs):
2282 symlink_files = self.shareable_files
2283 symlink_dirs = self.shareable_dirs
2284 if share_refs:
2285 symlink_files += self.working_tree_files
2286 symlink_dirs += self.working_tree_dirs
2287 to_symlink = symlink_files + symlink_dirs
2288 for name in set(to_symlink):
2289 dst = os.path.realpath(os.path.join(destdir, name))
2290 if os.path.lexists(dst):
2291 src = os.path.realpath(os.path.join(srcdir, name))
2292 # Fail if the links are pointing to the wrong place
2293 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002294 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002295 'work tree. If you\'re comfortable with the '
2296 'possibility of losing the work tree\'s git metadata,'
2297 ' use `repo sync --force-sync {0}` to '
2298 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002299
David James8d201162013-10-11 17:03:19 -07002300 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2301 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2302
2303 Args:
2304 gitdir: The bare git repository. Must already be initialized.
2305 dotgit: The repository you would like to initialize.
2306 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2307 Only one work tree can store refs under a given |gitdir|.
2308 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2309 This saves you the effort of initializing |dotgit| yourself.
2310 """
Kevin Degi384b3c52014-10-16 16:02:58 -06002311 symlink_files = self.shareable_files
2312 symlink_dirs = self.shareable_dirs
David James8d201162013-10-11 17:03:19 -07002313 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002314 symlink_files += self.working_tree_files
2315 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002316 to_symlink = symlink_files + symlink_dirs
2317
2318 to_copy = []
2319 if copy_all:
2320 to_copy = os.listdir(gitdir)
2321
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002322 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002323 for name in set(to_copy).union(to_symlink):
2324 try:
2325 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002326 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002327
Kevin Degi384b3c52014-10-16 16:02:58 -06002328 if os.path.lexists(dst):
2329 continue
David James8d201162013-10-11 17:03:19 -07002330
2331 # If the source dir doesn't exist, create an empty dir.
2332 if name in symlink_dirs and not os.path.lexists(src):
2333 os.makedirs(src)
2334
Conley Owens80b87fe2014-05-09 17:13:44 -07002335 # If the source file doesn't exist, ensure the destination
2336 # file doesn't either.
2337 if name in symlink_files and not os.path.lexists(src):
2338 try:
2339 os.remove(dst)
2340 except OSError:
2341 pass
2342
David James8d201162013-10-11 17:03:19 -07002343 if name in to_symlink:
2344 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2345 elif copy_all and not os.path.islink(dst):
2346 if os.path.isdir(src):
2347 shutil.copytree(src, dst)
2348 elif os.path.isfile(src):
2349 shutil.copy(src, dst)
2350 except OSError as e:
2351 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002352 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002353 else:
2354 raise
2355
Kevin Degiabaa7f32014-11-12 11:27:45 -07002356 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002357 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002358 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002359 try:
2360 if init_dotgit:
2361 os.makedirs(dotgit)
2362 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2363 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002364
Kevin Degiabaa7f32014-11-12 11:27:45 -07002365 try:
2366 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2367 except GitError as e:
2368 if force_sync:
2369 try:
2370 shutil.rmtree(dotgit)
2371 return self._InitWorkTree(force_sync=False)
2372 except:
2373 raise e
2374 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002375
Kevin Degib1a07b82015-07-27 13:33:43 -06002376 if init_dotgit:
2377 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002378
Kevin Degib1a07b82015-07-27 13:33:43 -06002379 cmd = ['read-tree', '--reset', '-u']
2380 cmd.append('-v')
2381 cmd.append(HEAD)
2382 if GitCommand(self, cmd).Wait() != 0:
2383 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002384
Kevin Degib1a07b82015-07-27 13:33:43 -06002385 self._CopyAndLinkFiles()
2386 except Exception:
2387 if init_dotgit:
2388 shutil.rmtree(dotgit)
2389 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002390
2391 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002392 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002393
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002394 def _revlist(self, *args, **kw):
2395 a = []
2396 a.extend(args)
2397 a.append('--')
2398 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002399
2400 @property
2401 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002402 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002403
Julien Camperguedd654222014-01-09 16:21:37 +01002404 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2405 """Get logs between two revisions of this project."""
2406 comp = '..'
2407 if rev1:
2408 revs = [rev1]
2409 if rev2:
2410 revs.extend([comp, rev2])
2411 cmd = ['log', ''.join(revs)]
2412 out = DiffColoring(self.config)
2413 if out.is_on and color:
2414 cmd.append('--color')
2415 if oneline:
2416 cmd.append('--oneline')
2417
2418 try:
2419 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2420 if log.Wait() == 0:
2421 return log.stdout
2422 except GitError:
2423 # worktree may not exist if groups changed for example. In that case,
2424 # try in gitdir instead.
2425 if not os.path.exists(self.worktree):
2426 return self.bare_git.log(*cmd[1:])
2427 else:
2428 raise
2429 return None
2430
2431 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2432 """Get the list of logs from this revision to given revisionId"""
2433 logs = {}
2434 selfId = self.GetRevisionId(self._allrefs)
2435 toId = toProject.GetRevisionId(toProject._allrefs)
2436
2437 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2438 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2439 return logs
2440
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002441 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002442 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002443 self._project = project
2444 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002445 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002446
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002447 def LsOthers(self):
2448 p = GitCommand(self._project,
2449 ['ls-files',
2450 '-z',
2451 '--others',
2452 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002453 bare=False,
David James8d201162013-10-11 17:03:19 -07002454 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002455 capture_stdout=True,
2456 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002457 if p.Wait() == 0:
2458 out = p.stdout
2459 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002460 return out[:-1].split('\0') # pylint: disable=W1401
2461 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002462 return []
2463
2464 def DiffZ(self, name, *args):
2465 cmd = [name]
2466 cmd.append('-z')
2467 cmd.extend(args)
2468 p = GitCommand(self._project,
2469 cmd,
David James8d201162013-10-11 17:03:19 -07002470 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002471 bare=False,
2472 capture_stdout=True,
2473 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002474 try:
2475 out = p.process.stdout.read()
2476 r = {}
2477 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002478 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002479 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002480 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002481 info = next(out)
2482 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002483 except StopIteration:
2484 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002485
2486 class _Info(object):
2487 def __init__(self, path, omode, nmode, oid, nid, state):
2488 self.path = path
2489 self.src_path = None
2490 self.old_mode = omode
2491 self.new_mode = nmode
2492 self.old_id = oid
2493 self.new_id = nid
2494
2495 if len(state) == 1:
2496 self.status = state
2497 self.level = None
2498 else:
2499 self.status = state[:1]
2500 self.level = state[1:]
2501 while self.level.startswith('0'):
2502 self.level = self.level[1:]
2503
2504 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002505 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002506 if info.status in ('R', 'C'):
2507 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002508 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002509 r[info.path] = info
2510 return r
2511 finally:
2512 p.Wait()
2513
2514 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002515 if self._bare:
2516 path = os.path.join(self._project.gitdir, HEAD)
2517 else:
2518 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002519 try:
2520 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002521 except IOError as e:
2522 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002523 try:
2524 line = fd.read()
2525 finally:
2526 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302527 try:
2528 line = line.decode()
2529 except AttributeError:
2530 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002531 if line.startswith('ref: '):
2532 return line[5:-1]
2533 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002534
2535 def SetHead(self, ref, message=None):
2536 cmdv = []
2537 if message is not None:
2538 cmdv.extend(['-m', message])
2539 cmdv.append(HEAD)
2540 cmdv.append(ref)
2541 self.symbolic_ref(*cmdv)
2542
2543 def DetachHead(self, new, message=None):
2544 cmdv = ['--no-deref']
2545 if message is not None:
2546 cmdv.extend(['-m', message])
2547 cmdv.append(HEAD)
2548 cmdv.append(new)
2549 self.update_ref(*cmdv)
2550
2551 def UpdateRef(self, name, new, old=None,
2552 message=None,
2553 detach=False):
2554 cmdv = []
2555 if message is not None:
2556 cmdv.extend(['-m', message])
2557 if detach:
2558 cmdv.append('--no-deref')
2559 cmdv.append(name)
2560 cmdv.append(new)
2561 if old is not None:
2562 cmdv.append(old)
2563 self.update_ref(*cmdv)
2564
2565 def DeleteRef(self, name, old=None):
2566 if not old:
2567 old = self.rev_parse(name)
2568 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002569 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002570
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002571 def rev_list(self, *args, **kw):
2572 if 'format' in kw:
2573 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2574 else:
2575 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002576 cmdv.extend(args)
2577 p = GitCommand(self._project,
2578 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002579 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002580 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002581 capture_stdout=True,
2582 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002583 r = []
2584 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002585 if line[-1] == '\n':
2586 line = line[:-1]
2587 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002588 if p.Wait() != 0:
2589 raise GitError('%s rev-list %s: %s' % (
2590 self._project.name,
2591 str(args),
2592 p.stderr))
2593 return r
2594
2595 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002596 """Allow arbitrary git commands using pythonic syntax.
2597
2598 This allows you to do things like:
2599 git_obj.rev_parse('HEAD')
2600
2601 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2602 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002603 Any other positional arguments will be passed to the git command, and the
2604 following keyword arguments are supported:
2605 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002606
2607 Args:
2608 name: The name of the git command to call. Any '_' characters will
2609 be replaced with '-'.
2610
2611 Returns:
2612 A callable object that will try to call git with the named command.
2613 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002614 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002615 def runner(*args, **kwargs):
2616 cmdv = []
2617 config = kwargs.pop('config', None)
2618 for k in kwargs:
2619 raise TypeError('%s() got an unexpected keyword argument %r'
2620 % (name, k))
2621 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002622 if not git_require((1, 7, 2)):
2623 raise ValueError('cannot set config on command line for %s()'
2624 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302625 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002626 cmdv.append('-c')
2627 cmdv.append('%s=%s' % (k, v))
2628 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002629 cmdv.extend(args)
2630 p = GitCommand(self._project,
2631 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002632 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002633 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002634 capture_stdout=True,
2635 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002636 if p.Wait() != 0:
2637 raise GitError('%s %s: %s' % (
2638 self._project.name,
2639 name,
2640 p.stderr))
2641 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302642 try:
Conley Owensedd01512013-09-26 12:59:58 -07002643 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302644 except AttributeError:
2645 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002646 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2647 return r[:-1]
2648 return r
2649 return runner
2650
2651
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002652class _PriorSyncFailedError(Exception):
2653 def __str__(self):
2654 return 'prior sync failed; rebase still in progress'
2655
2656class _DirtyError(Exception):
2657 def __str__(self):
2658 return 'contains uncommitted changes'
2659
2660class _InfoMessage(object):
2661 def __init__(self, project, text):
2662 self.project = project
2663 self.text = text
2664
2665 def Print(self, syncbuf):
2666 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2667 syncbuf.out.nl()
2668
2669class _Failure(object):
2670 def __init__(self, project, why):
2671 self.project = project
2672 self.why = why
2673
2674 def Print(self, syncbuf):
2675 syncbuf.out.fail('error: %s/: %s',
2676 self.project.relpath,
2677 str(self.why))
2678 syncbuf.out.nl()
2679
2680class _Later(object):
2681 def __init__(self, project, action):
2682 self.project = project
2683 self.action = action
2684
2685 def Run(self, syncbuf):
2686 out = syncbuf.out
2687 out.project('project %s/', self.project.relpath)
2688 out.nl()
2689 try:
2690 self.action()
2691 out.nl()
2692 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002693 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002694 out.nl()
2695 return False
2696
2697class _SyncColoring(Coloring):
2698 def __init__(self, config):
2699 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002700 self.project = self.printer('header', attr='bold')
2701 self.info = self.printer('info')
2702 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002703
2704class SyncBuffer(object):
2705 def __init__(self, config, detach_head=False):
2706 self._messages = []
2707 self._failures = []
2708 self._later_queue1 = []
2709 self._later_queue2 = []
2710
2711 self.out = _SyncColoring(config)
2712 self.out.redirect(sys.stderr)
2713
2714 self.detach_head = detach_head
2715 self.clean = True
2716
2717 def info(self, project, fmt, *args):
2718 self._messages.append(_InfoMessage(project, fmt % args))
2719
2720 def fail(self, project, err=None):
2721 self._failures.append(_Failure(project, err))
2722 self.clean = False
2723
2724 def later1(self, project, what):
2725 self._later_queue1.append(_Later(project, what))
2726
2727 def later2(self, project, what):
2728 self._later_queue2.append(_Later(project, what))
2729
2730 def Finish(self):
2731 self._PrintMessages()
2732 self._RunLater()
2733 self._PrintMessages()
2734 return self.clean
2735
2736 def _RunLater(self):
2737 for q in ['_later_queue1', '_later_queue2']:
2738 if not self._RunQueue(q):
2739 return
2740
2741 def _RunQueue(self, queue):
2742 for m in getattr(self, queue):
2743 if not m.Run(self):
2744 self.clean = False
2745 return False
2746 setattr(self, queue, [])
2747 return True
2748
2749 def _PrintMessages(self):
2750 for m in self._messages:
2751 m.Print(self)
2752 for m in self._failures:
2753 m.Print(self)
2754
2755 self._messages = []
2756 self._failures = []
2757
2758
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002759class MetaProject(Project):
2760 """A special project housed under .repo.
2761 """
2762 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002763 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002764 manifest=manifest,
2765 name=name,
2766 gitdir=gitdir,
2767 objdir=gitdir,
2768 worktree=worktree,
2769 remote=RemoteSpec('origin'),
2770 relpath='.repo/%s' % name,
2771 revisionExpr='refs/heads/master',
2772 revisionId=None,
2773 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002774
2775 def PreSync(self):
2776 if self.Exists:
2777 cb = self.CurrentBranch
2778 if cb:
2779 base = self.GetBranch(cb).merge
2780 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002781 self.revisionExpr = base
2782 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002783
Anthony King7bdac712014-07-16 12:56:40 +01002784 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002785 """ Prepare MetaProject for manifest branch switch
2786 """
2787
2788 # detach and delete manifest branch, allowing a new
2789 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002790 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002791 self.Sync_LocalHalf(syncbuf)
2792 syncbuf.Finish()
2793
2794 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002795 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002796 capture_stdout=True,
2797 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002798
2799
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002800 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002801 def LastFetch(self):
2802 try:
2803 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2804 return os.path.getmtime(fh)
2805 except OSError:
2806 return 0
2807
2808 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002809 def HasChanges(self):
2810 """Has the remote received new commits not yet checked out?
2811 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002812 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002813 return False
2814
David Pursehouse8a68ff92012-09-24 12:15:13 +09002815 all_refs = self.bare_ref.all
2816 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002817 head = self.work_git.GetHead()
2818 if head.startswith(R_HEADS):
2819 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002820 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002821 except KeyError:
2822 head = None
2823
2824 if revid == head:
2825 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002826 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002827 return True
2828 return False