blob: 12be924fe8f0e1aea06513425d2f8bb6f7b8484e [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']
natalie.chene8996f92015-12-29 10:53:30 +0800552 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn', 'lfs']
Kevin Degi384b3c52014-10-16 16:02:58 -0600553 # 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,
natalie.chene8996f92015-12-29 10:53:30 +0800576 old_revision=None,
577 lfs_fetch=False):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800578 """Init a Project object.
579
580 Args:
581 manifest: The XmlManifest object.
582 name: The `name` attribute of manifest.xml's project element.
583 remote: RemoteSpec object specifying its remote's properties.
584 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700585 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800586 worktree: Absolute path of git working tree.
587 relpath: Relative path of git working tree to repo's top directory.
588 revisionExpr: The `revision` attribute of manifest.xml's project element.
589 revisionId: git commit id for checking out.
590 rebase: The `rebase` attribute of manifest.xml's project element.
591 groups: The `groups` attribute of manifest.xml's project element.
592 sync_c: The `sync-c` attribute of manifest.xml's project element.
593 sync_s: The `sync-s` attribute of manifest.xml's project element.
594 upstream: The `upstream` attribute of manifest.xml's project element.
595 parent: The parent Project object.
596 is_derived: False if the project was explicitly defined in the manifest;
597 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400598 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900599 optimized_fetch: If True, when a project is set to a sha1 revision, only
600 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700601 old_revision: saved git commit id for open GITC projects.
natalie.chene8996f92015-12-29 10:53:30 +0800602 lfs_fetch: git lfs fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800603 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700604 self.manifest = manifest
605 self.name = name
606 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800607 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700608 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800609 if worktree:
610 self.worktree = worktree.replace('\\', '/')
611 else:
612 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700613 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700614 self.revisionExpr = revisionExpr
615
616 if revisionId is None \
617 and revisionExpr \
618 and IsId(revisionExpr):
619 self.revisionId = revisionExpr
620 else:
621 self.revisionId = revisionId
622
Mike Pontillod3153822012-02-28 11:53:24 -0800623 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700624 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700625 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800626 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900627 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700628 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800629 self.parent = parent
630 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900631 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800632 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800633
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700634 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500636 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500637 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700638 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100639 gitdir=self.gitdir,
640 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700641
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800642 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700643 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800644 else:
645 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700646 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700647 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700648 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400649 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700650 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700651
Doug Anderson37282b42011-03-04 11:54:18 -0800652 # This will be filled in if a project is later identified to be the
653 # project containing repo hooks.
654 self.enabled_repo_hooks = []
natalie.chene8996f92015-12-29 10:53:30 +0800655 self.lfs_fetch = lfs_fetch
Doug Anderson37282b42011-03-04 11:54:18 -0800656
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700657 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800658 def Derived(self):
659 return self.is_derived
660
661 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700662 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600663 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700664
665 @property
666 def CurrentBranch(self):
667 """Obtain the name of the currently checked out branch.
668 The branch name omits the 'refs/heads/' prefix.
669 None is returned if the project is on a detached HEAD.
670 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700671 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700672 if b.startswith(R_HEADS):
673 return b[len(R_HEADS):]
674 return None
675
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700676 def IsRebaseInProgress(self):
677 w = self.worktree
678 g = os.path.join(w, '.git')
679 return os.path.exists(os.path.join(g, 'rebase-apply')) \
680 or os.path.exists(os.path.join(g, 'rebase-merge')) \
681 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200682
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700683 def IsDirty(self, consider_untracked=True):
684 """Is the working directory modified in some way?
685 """
686 self.work_git.update_index('-q',
687 '--unmerged',
688 '--ignore-missing',
689 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900690 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700691 return True
692 if self.work_git.DiffZ('diff-files'):
693 return True
694 if consider_untracked and self.work_git.LsOthers():
695 return True
696 return False
697
698 _userident_name = None
699 _userident_email = None
700
701 @property
702 def UserName(self):
703 """Obtain the user's personal name.
704 """
705 if self._userident_name is None:
706 self._LoadUserIdentity()
707 return self._userident_name
708
709 @property
710 def UserEmail(self):
711 """Obtain the user's email address. This is very likely
712 to be their Gerrit login.
713 """
714 if self._userident_email is None:
715 self._LoadUserIdentity()
716 return self._userident_email
717
718 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900719 u = self.bare_git.var('GIT_COMMITTER_IDENT')
720 m = re.compile("^(.*) <([^>]*)> ").match(u)
721 if m:
722 self._userident_name = m.group(1)
723 self._userident_email = m.group(2)
724 else:
725 self._userident_name = ''
726 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700727
728 def GetRemote(self, name):
729 """Get the configuration for a single remote.
730 """
731 return self.config.GetRemote(name)
732
733 def GetBranch(self, name):
734 """Get the configuration for a single branch.
735 """
736 return self.config.GetBranch(name)
737
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700738 def GetBranches(self):
739 """Get all existing local branches.
740 """
741 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900742 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700743 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700744
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530745 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700746 if name.startswith(R_HEADS):
747 name = name[len(R_HEADS):]
748 b = self.GetBranch(name)
749 b.current = name == current
750 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900751 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700752 heads[name] = b
753
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530754 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700755 if name.startswith(R_PUB):
756 name = name[len(R_PUB):]
757 b = heads.get(name)
758 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900759 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700760
761 return heads
762
Colin Cross5acde752012-03-28 20:15:45 -0700763 def MatchesGroups(self, manifest_groups):
764 """Returns true if the manifest groups specified at init should cause
765 this project to be synced.
766 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700767 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700768
Conley Owens971de8e2012-04-16 10:36:08 -0700769 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700770 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700771 manifest_groups: "-group1,group2"
772 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500773
774 The special manifest group "default" will match any project that
775 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700776 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500777 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700778 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500779 if not 'notdefault' in expanded_project_groups:
780 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700781
Conley Owens971de8e2012-04-16 10:36:08 -0700782 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700783 for group in expanded_manifest_groups:
784 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700785 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700786 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700787 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700788
Conley Owens971de8e2012-04-16 10:36:08 -0700789 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700790
791## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700792 def UncommitedFiles(self, get_all=True):
793 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700795 Args:
796 get_all: a boolean, if True - get information about all different
797 uncommitted files. If False - return as soon as any kind of
798 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500799 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700800 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500801 self.work_git.update_index('-q',
802 '--unmerged',
803 '--ignore-missing',
804 '--refresh')
805 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700806 details.append("rebase in progress")
807 if not get_all:
808 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500809
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700810 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
811 if changes:
812 details.extend(changes)
813 if not get_all:
814 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500815
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700816 changes = self.work_git.DiffZ('diff-files').keys()
817 if changes:
818 details.extend(changes)
819 if not get_all:
820 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500821
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700822 changes = self.work_git.LsOthers()
823 if changes:
824 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500825
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700826 return details
827
828 def HasChanges(self):
829 """Returns true if there are uncommitted changes.
830 """
831 if self.UncommitedFiles(get_all=False):
832 return True
833 else:
834 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500835
Terence Haddock4655e812011-03-31 12:33:34 +0200836 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700837 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200838
839 Args:
840 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841 """
842 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200843 if output_redir == None:
844 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700845 print(file=output_redir)
846 print('project %s/' % self.relpath, file=output_redir)
847 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848 return
849
850 self.work_git.update_index('-q',
851 '--unmerged',
852 '--ignore-missing',
853 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700854 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700855 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
856 df = self.work_git.DiffZ('diff-files')
857 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100858 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700859 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700860
861 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200862 if not output_redir == None:
863 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700864 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865
866 branch = self.CurrentBranch
867 if branch is None:
868 out.nobranch('(*** NO BRANCH ***)')
869 else:
870 out.branch('branch %s', branch)
871 out.nl()
872
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700873 if rb:
874 out.important('prior sync failed; rebase still in progress')
875 out.nl()
876
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877 paths = list()
878 paths.extend(di.keys())
879 paths.extend(df.keys())
880 paths.extend(do)
881
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530882 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900883 try:
884 i = di[p]
885 except KeyError:
886 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700887
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900888 try:
889 f = df[p]
890 except KeyError:
891 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200892
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900893 if i:
894 i_status = i.status.upper()
895 else:
896 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700897
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900898 if f:
899 f_status = f.status.lower()
900 else:
901 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902
903 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800904 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905 i.src_path, p, i.level)
906 else:
907 line = ' %s%s\t%s' % (i_status, f_status, p)
908
909 if i and not f:
910 out.added('%s', line)
911 elif (i and f) or (not i and f):
912 out.changed('%s', line)
913 elif not i and not f:
914 out.untracked('%s', line)
915 else:
916 out.write('%s', line)
917 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200918
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700919 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920
pelyad67872d2012-03-28 14:49:58 +0300921 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 """Prints the status of the repository to stdout.
923 """
924 out = DiffColoring(self.config)
925 cmd = ['diff']
926 if out.is_on:
927 cmd.append('--color')
928 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300929 if absolute_paths:
930 cmd.append('--src-prefix=a/%s/' % self.relpath)
931 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932 cmd.append('--')
933 p = GitCommand(self,
934 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100935 capture_stdout=True,
936 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937 has_diff = False
938 for line in p.process.stdout:
939 if not has_diff:
940 out.nl()
941 out.project('project %s/' % self.relpath)
942 out.nl()
943 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700944 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945 p.Wait()
946
947
948## Publish / Upload ##
949
David Pursehouse8a68ff92012-09-24 12:15:13 +0900950 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951 """Was the branch published (uploaded) for code review?
952 If so, returns the SHA-1 hash of the last published
953 state for the branch.
954 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700955 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900956 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700957 try:
958 return self.bare_git.rev_parse(key)
959 except GitError:
960 return None
961 else:
962 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900963 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700964 except KeyError:
965 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966
David Pursehouse8a68ff92012-09-24 12:15:13 +0900967 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968 """Prunes any stale published refs.
969 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900970 if all_refs is None:
971 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 heads = set()
973 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530974 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700975 if name.startswith(R_HEADS):
976 heads.add(name)
977 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900978 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530980 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 n = name[len(R_PUB):]
982 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900983 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700985 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 """List any branches which can be uploaded for review.
987 """
988 heads = {}
989 pubed = {}
990
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530991 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700992 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900993 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700994 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900995 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700996
997 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530998 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900999 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001001 if selected_branch and branch != selected_branch:
1002 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001004 rb = self.GetUploadableBranch(branch)
1005 if rb:
1006 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001007 return ready
1008
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001009 def GetUploadableBranch(self, branch_name):
1010 """Get a single uploadable branch, or None.
1011 """
1012 branch = self.GetBranch(branch_name)
1013 base = branch.LocalMerge
1014 if branch.LocalMerge:
1015 rb = ReviewableBranch(self, branch, base)
1016 if rb.commits:
1017 return rb
1018 return None
1019
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001020 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001021 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001022 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001023 draft=False,
1024 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001025 """Uploads the named branch for code review.
1026 """
1027 if branch is None:
1028 branch = self.CurrentBranch
1029 if branch is None:
1030 raise GitError('not currently on a branch')
1031
1032 branch = self.GetBranch(branch)
1033 if not branch.LocalMerge:
1034 raise GitError('branch %s does not track a remote' % branch.name)
1035 if not branch.remote.review:
1036 raise GitError('remote %s has no review url' % branch.remote.name)
1037
Bryan Jacobsf609f912013-05-06 13:36:24 -04001038 if dest_branch is None:
1039 dest_branch = self.dest_branch
1040 if dest_branch is None:
1041 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 if not dest_branch.startswith(R_HEADS):
1043 dest_branch = R_HEADS + dest_branch
1044
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001045 if not branch.remote.projectname:
1046 branch.remote.projectname = self.name
1047 branch.remote.Save()
1048
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001049 url = branch.remote.ReviewUrl(self.UserEmail)
1050 if url is None:
1051 raise UploadError('review not configured')
1052 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001053
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001054 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001055 rp = ['gerrit receive-pack']
1056 for e in people[0]:
1057 rp.append('--reviewer=%s' % sq(e))
1058 for e in people[1]:
1059 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001060 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001061
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001062 cmd.append(url)
1063
1064 if dest_branch.startswith(R_HEADS):
1065 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001066
1067 upload_type = 'for'
1068 if draft:
1069 upload_type = 'drafts'
1070
1071 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1072 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001073 if auto_topic:
1074 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001075 if not url.startswith('ssh://'):
1076 rp = ['r=%s' % p for p in people[0]] + \
1077 ['cc=%s' % p for p in people[1]]
1078 if rp:
1079 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001080 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001081
Anthony King7bdac712014-07-16 12:56:40 +01001082 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001083 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001084
1085 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1086 self.bare_git.UpdateRef(R_PUB + branch.name,
1087 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001088 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089
1090
1091## Sync ##
natalie.chene8996f92015-12-29 10:53:30 +08001092 def __FetchLfsObjects(self, name, refs):
1093 if 'refs/heads/*' in refs:
1094 refs = []
1095 for ref in GitRefs(self.objdir).all:
1096 if ref.startswith(R_HEADS):
1097 refs.append(ref)
1098 cmd = ['lfs', 'fetch']
1099 cmd.append(name)
1100 cmd.extend(refs)
1101 gitcmd = GitCommand(self, cmd, bare=True)
1102 ret = gitcmd.Wait()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001103
Julien Campergue335f5ef2013-10-16 11:02:35 +02001104 def _ExtractArchive(self, tarpath, path=None):
1105 """Extract the given tar on its current location
1106
1107 Args:
1108 - tarpath: The path to the actual tar file
1109
1110 """
1111 try:
1112 with tarfile.open(tarpath, 'r') as tar:
1113 tar.extractall(path=path)
1114 return True
1115 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001116 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001117 return False
1118
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001119 def Sync_NetworkHalf(self,
1120 quiet=False,
1121 is_new=None,
1122 current_branch_only=False,
Kevin Degiabaa7f32014-11-12 11:27:45 -07001123 force_sync=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001124 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001125 no_tags=False,
David Pursehouseb1553542014-09-04 21:28:09 +09001126 archive=False,
1127 optimized_fetch=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001128 """Perform only the network IO portion of the sync process.
1129 Local working directory/branch state is not affected.
1130 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001131 if archive and not isinstance(self, MetaProject):
1132 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001133 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001134 return False
1135
1136 name = self.relpath.replace('\\', '/')
1137 name = name.replace('/', '_')
1138 tarpath = '%s.tar' % name
1139 topdir = self.manifest.topdir
1140
1141 try:
1142 self._FetchArchive(tarpath, cwd=topdir)
1143 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001144 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001145 return False
1146
1147 # From now on, we only need absolute tarpath
1148 tarpath = os.path.join(topdir, tarpath)
1149
1150 if not self._ExtractArchive(tarpath, path=topdir):
1151 return False
1152 try:
1153 os.remove(tarpath)
1154 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001155 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001156 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001157 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001158 if is_new is None:
1159 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001160 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001161 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001162 else:
1163 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001164 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001165
1166 if is_new:
1167 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1168 try:
1169 fd = open(alt, 'rb')
1170 try:
1171 alt_dir = fd.readline().rstrip()
1172 finally:
1173 fd.close()
1174 except IOError:
1175 alt_dir = None
1176 else:
1177 alt_dir = None
1178
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001179 if clone_bundle \
1180 and alt_dir is None \
1181 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001182 is_new = False
1183
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001184 if not current_branch_only:
1185 if self.sync_c:
1186 current_branch_only = True
1187 elif not self.manifest._loaded:
1188 # Manifest cannot check defaults until it syncs.
1189 current_branch_only = False
1190 elif self.manifest.default.sync_c:
1191 current_branch_only = True
1192
David Pursehouseb1553542014-09-04 21:28:09 +09001193 need_to_fetch = not (optimized_fetch and \
1194 (ID_RE.match(self.revisionExpr) and self._CheckForSha1()))
1195 if (need_to_fetch
Conley Owens666d5342014-05-01 13:09:57 -07001196 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1197 current_branch_only=current_branch_only,
1198 no_tags=no_tags)):
Anthony King7bdac712014-07-16 12:56:40 +01001199 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001200
1201 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001202 self._InitMRef()
1203 else:
1204 self._InitMirrorHead()
1205 try:
1206 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1207 except OSError:
1208 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001209 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001210
1211 def PostRepoUpgrade(self):
1212 self._InitHooks()
1213
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001214 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001215 if self.manifest.isGitcClient:
1216 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001217 for copyfile in self.copyfiles:
1218 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001219 for linkfile in self.linkfiles:
1220 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001221
Julien Camperguedd654222014-01-09 16:21:37 +01001222 def GetCommitRevisionId(self):
1223 """Get revisionId of a commit.
1224
1225 Use this method instead of GetRevisionId to get the id of the commit rather
1226 than the id of the current git object (for example, a tag)
1227
1228 """
1229 if not self.revisionExpr.startswith(R_TAGS):
1230 return self.GetRevisionId(self._allrefs)
1231
1232 try:
1233 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1234 except GitError:
1235 raise ManifestInvalidRevisionError(
1236 'revision %s in %s not found' % (self.revisionExpr,
1237 self.name))
1238
David Pursehouse8a68ff92012-09-24 12:15:13 +09001239 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001240 if self.revisionId:
1241 return self.revisionId
1242
1243 rem = self.GetRemote(self.remote.name)
1244 rev = rem.ToLocal(self.revisionExpr)
1245
David Pursehouse8a68ff92012-09-24 12:15:13 +09001246 if all_refs is not None and rev in all_refs:
1247 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001248
1249 try:
1250 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1251 except GitError:
1252 raise ManifestInvalidRevisionError(
1253 'revision %s in %s not found' % (self.revisionExpr,
1254 self.name))
1255
Kevin Degiabaa7f32014-11-12 11:27:45 -07001256 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001257 """Perform only the local IO portion of the sync process.
1258 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001259 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001260 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001261 all_refs = self.bare_ref.all
1262 self.CleanPublishedCache(all_refs)
1263 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001264
David Pursehouse1d947b32012-10-25 12:23:11 +09001265 def _doff():
1266 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001267 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001268
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001269 head = self.work_git.GetHead()
1270 if head.startswith(R_HEADS):
1271 branch = head[len(R_HEADS):]
1272 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001273 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001274 except KeyError:
1275 head = None
1276 else:
1277 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001278
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001279 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280 # Currently on a detached HEAD. The user is assumed to
1281 # not have any local modifications worth worrying about.
1282 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001283 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001284 syncbuf.fail(self, _PriorSyncFailedError())
1285 return
1286
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001287 if head == revid:
1288 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001289 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001290 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001291 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001292 # The copy/linkfile config may have changed.
1293 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001294 return
1295 else:
1296 lost = self._revlist(not_rev(revid), HEAD)
1297 if lost:
1298 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001299
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001301 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001302 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001303 syncbuf.fail(self, e)
1304 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001305 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001306 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001308 if head == revid:
1309 # No changes; don't do anything further.
1310 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001311 # The copy/linkfile config may have changed.
1312 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001313 return
1314
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001317 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001319 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001321 syncbuf.info(self,
1322 "leaving %s; does not track upstream",
1323 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001325 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001326 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001327 syncbuf.fail(self, e)
1328 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001329 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001330 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001331
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001332 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001333 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001334 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001335 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001336 if not_merged:
1337 if upstream_gain:
1338 # The user has published this branch and some of those
1339 # commits are not yet merged upstream. We do not want
1340 # to rewrite the published commits so we punt.
1341 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001342 syncbuf.fail(self,
1343 "branch %s is published (but not merged) and is now %d commits behind"
1344 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001345 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001346 elif pub == head:
1347 # All published commits are merged, and thus we are a
1348 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001349 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001350 syncbuf.later1(self, _doff)
1351 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001352
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001353 # Examine the local commits not in the remote. Find the
1354 # last one attributed to this user, if any.
1355 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001356 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001357 last_mine = None
1358 cnt_mine = 0
1359 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301360 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001361 if committer_email == self.UserEmail:
1362 last_mine = commit_id
1363 cnt_mine += 1
1364
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001365 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001366 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367
1368 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001369 syncbuf.fail(self, _DirtyError())
1370 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001371
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001372 # If the upstream switched on us, warn the user.
1373 #
1374 if branch.merge != self.revisionExpr:
1375 if branch.merge and self.revisionExpr:
1376 syncbuf.info(self,
1377 'manifest switched %s...%s',
1378 branch.merge,
1379 self.revisionExpr)
1380 elif branch.merge:
1381 syncbuf.info(self,
1382 'manifest no longer tracks %s',
1383 branch.merge)
1384
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001385 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001387 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001388 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001389 syncbuf.info(self,
1390 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001391 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001392
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001393 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001394 if not ID_RE.match(self.revisionExpr):
1395 # in case of manifest sync the revisionExpr might be a SHA1
1396 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001397 if not branch.merge.startswith('refs/'):
1398 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399 branch.Save()
1400
Mike Pontillod3153822012-02-28 11:53:24 -08001401 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001402 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001403 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001404 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001405 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001406 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001408 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001409 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001410 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001411 syncbuf.fail(self, e)
1412 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001413 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001414 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001415
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001416 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001417 # dest should already be an absolute path, but src is project relative
1418 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001419 abssrc = os.path.join(self.worktree, src)
1420 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001421
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001422 def AddLinkFile(self, src, dest, absdest):
1423 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001424 # make src relative path to dest
1425 absdestdir = os.path.dirname(absdest)
1426 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001427 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001428
James W. Mills24c13082012-04-12 15:04:13 -05001429 def AddAnnotation(self, name, value, keep):
1430 self.annotations.append(_Annotation(name, value, keep))
1431
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001432 def DownloadPatchSet(self, change_id, patch_id):
1433 """Download a single patch set of a single change to FETCH_HEAD.
1434 """
1435 remote = self.GetRemote(self.remote.name)
1436
1437 cmd = ['fetch', remote.name]
1438 cmd.append('refs/changes/%2.2d/%d/%d' \
1439 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001440 if GitCommand(self, cmd, bare=True).Wait() != 0:
1441 return None
1442 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001443 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001444 change_id,
1445 patch_id,
1446 self.bare_git.rev_parse('FETCH_HEAD'))
1447
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001448
1449## Branch Management ##
1450
Simran Basib9a1b732015-08-20 12:19:28 -07001451 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001452 """Create a new branch off the manifest's revision.
1453 """
Simran Basib9a1b732015-08-20 12:19:28 -07001454 if not branch_merge:
1455 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001456 head = self.work_git.GetHead()
1457 if head == (R_HEADS + name):
1458 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001459
David Pursehouse8a68ff92012-09-24 12:15:13 +09001460 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001461 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001462 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001463 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001464 capture_stdout=True,
1465 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001466
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001467 branch = self.GetBranch(name)
1468 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001469 branch.merge = branch_merge
1470 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1471 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001472 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001473
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001474 if head.startswith(R_HEADS):
1475 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001476 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001477 except KeyError:
1478 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001479 if revid and head and revid == head:
1480 ref = os.path.join(self.gitdir, R_HEADS + name)
1481 try:
1482 os.makedirs(os.path.dirname(ref))
1483 except OSError:
1484 pass
1485 _lwrite(ref, '%s\n' % revid)
1486 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1487 'ref: %s%s\n' % (R_HEADS, name))
1488 branch.Save()
1489 return True
1490
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001491 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001492 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001493 capture_stdout=True,
1494 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001495 branch.Save()
1496 return True
1497 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001498
Wink Saville02d79452009-04-10 13:01:24 -07001499 def CheckoutBranch(self, name):
1500 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001501
1502 Args:
1503 name: The name of the branch to checkout.
1504
1505 Returns:
1506 True if the checkout succeeded; False if it didn't; None if the branch
1507 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001508 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001509 rev = R_HEADS + name
1510 head = self.work_git.GetHead()
1511 if head == rev:
1512 # Already on the branch
1513 #
1514 return True
Wink Saville02d79452009-04-10 13:01:24 -07001515
David Pursehouse8a68ff92012-09-24 12:15:13 +09001516 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001517 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001518 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001519 except KeyError:
1520 # Branch does not exist in this project
1521 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001522 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001523
1524 if head.startswith(R_HEADS):
1525 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001526 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001527 except KeyError:
1528 head = None
1529
1530 if head == revid:
1531 # Same revision; just update HEAD to point to the new
1532 # target branch, but otherwise take no other action.
1533 #
1534 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1535 'ref: %s%s\n' % (R_HEADS, name))
1536 return True
Wink Saville02d79452009-04-10 13:01:24 -07001537
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001538 return GitCommand(self,
1539 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001540 capture_stdout=True,
1541 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001542
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001543 def AbandonBranch(self, name):
1544 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001545
1546 Args:
1547 name: The name of the branch to abandon.
1548
1549 Returns:
1550 True if the abandon succeeded; False if it didn't; None if the branch
1551 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001552 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001553 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001554 all_refs = self.bare_ref.all
1555 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001556 # Doesn't exist
1557 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001558
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001559 head = self.work_git.GetHead()
1560 if head == rev:
1561 # We can't destroy the branch while we are sitting
1562 # on it. Switch to a detached HEAD.
1563 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001564 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001565
David Pursehouse8a68ff92012-09-24 12:15:13 +09001566 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001567 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001568 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1569 '%s\n' % revid)
1570 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001571 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001572
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001573 return GitCommand(self,
1574 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001575 capture_stdout=True,
1576 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001577
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001578 def PruneHeads(self):
1579 """Prune any topic branches already merged into upstream.
1580 """
1581 cb = self.CurrentBranch
1582 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001583 left = self._allrefs
1584 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001585 if name.startswith(R_HEADS):
1586 name = name[len(R_HEADS):]
1587 if cb is None or name != cb:
1588 kill.append(name)
1589
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001590 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001591 if cb is not None \
1592 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001593 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001594 self.work_git.DetachHead(HEAD)
1595 kill.append(cb)
1596
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001597 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001598 old = self.bare_git.GetHead()
1599 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001600 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1601
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001602 try:
1603 self.bare_git.DetachHead(rev)
1604
1605 b = ['branch', '-d']
1606 b.extend(kill)
1607 b = GitCommand(self, b, bare=True,
1608 capture_stdout=True,
1609 capture_stderr=True)
1610 b.Wait()
1611 finally:
1612 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001613 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001614
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001615 for branch in kill:
1616 if (R_HEADS + branch) not in left:
1617 self.CleanPublishedCache()
1618 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001619
1620 if cb and cb not in kill:
1621 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001622 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001623
1624 kept = []
1625 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001626 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001627 branch = self.GetBranch(branch)
1628 base = branch.LocalMerge
1629 if not base:
1630 base = rev
1631 kept.append(ReviewableBranch(self, branch, base))
1632 return kept
1633
1634
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001635## Submodule Management ##
1636
1637 def GetRegisteredSubprojects(self):
1638 result = []
1639 def rec(subprojects):
1640 if not subprojects:
1641 return
1642 result.extend(subprojects)
1643 for p in subprojects:
1644 rec(p.subprojects)
1645 rec(self.subprojects)
1646 return result
1647
1648 def _GetSubmodules(self):
1649 # Unfortunately we cannot call `git submodule status --recursive` here
1650 # because the working tree might not exist yet, and it cannot be used
1651 # without a working tree in its current implementation.
1652
1653 def get_submodules(gitdir, rev):
1654 # Parse .gitmodules for submodule sub_paths and sub_urls
1655 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1656 if not sub_paths:
1657 return []
1658 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1659 # revision of submodule repository
1660 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1661 submodules = []
1662 for sub_path, sub_url in zip(sub_paths, sub_urls):
1663 try:
1664 sub_rev = sub_revs[sub_path]
1665 except KeyError:
1666 # Ignore non-exist submodules
1667 continue
1668 submodules.append((sub_rev, sub_path, sub_url))
1669 return submodules
1670
1671 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1672 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1673 def parse_gitmodules(gitdir, rev):
1674 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1675 try:
Anthony King7bdac712014-07-16 12:56:40 +01001676 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1677 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001678 except GitError:
1679 return [], []
1680 if p.Wait() != 0:
1681 return [], []
1682
1683 gitmodules_lines = []
1684 fd, temp_gitmodules_path = tempfile.mkstemp()
1685 try:
1686 os.write(fd, p.stdout)
1687 os.close(fd)
1688 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001689 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1690 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001691 if p.Wait() != 0:
1692 return [], []
1693 gitmodules_lines = p.stdout.split('\n')
1694 except GitError:
1695 return [], []
1696 finally:
1697 os.remove(temp_gitmodules_path)
1698
1699 names = set()
1700 paths = {}
1701 urls = {}
1702 for line in gitmodules_lines:
1703 if not line:
1704 continue
1705 m = re_path.match(line)
1706 if m:
1707 names.add(m.group(1))
1708 paths[m.group(1)] = m.group(2)
1709 continue
1710 m = re_url.match(line)
1711 if m:
1712 names.add(m.group(1))
1713 urls[m.group(1)] = m.group(2)
1714 continue
1715 names = sorted(names)
1716 return ([paths.get(name, '') for name in names],
1717 [urls.get(name, '') for name in names])
1718
1719 def git_ls_tree(gitdir, rev, paths):
1720 cmd = ['ls-tree', rev, '--']
1721 cmd.extend(paths)
1722 try:
Anthony King7bdac712014-07-16 12:56:40 +01001723 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1724 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001725 except GitError:
1726 return []
1727 if p.Wait() != 0:
1728 return []
1729 objects = {}
1730 for line in p.stdout.split('\n'):
1731 if not line.strip():
1732 continue
1733 object_rev, object_path = line.split()[2:4]
1734 objects[object_path] = object_rev
1735 return objects
1736
1737 try:
1738 rev = self.GetRevisionId()
1739 except GitError:
1740 return []
1741 return get_submodules(self.gitdir, rev)
1742
1743 def GetDerivedSubprojects(self):
1744 result = []
1745 if not self.Exists:
1746 # If git repo does not exist yet, querying its submodules will
1747 # mess up its states; so return here.
1748 return result
1749 for rev, path, url in self._GetSubmodules():
1750 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001751 relpath, worktree, gitdir, objdir = \
1752 self.manifest.GetSubprojectPaths(self, name, path)
1753 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001754 if project:
1755 result.extend(project.GetDerivedSubprojects())
1756 continue
David James8d201162013-10-11 17:03:19 -07001757
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001758 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001759 url=url,
1760 review=self.remote.review,
1761 revision=self.remote.revision)
1762 subproject = Project(manifest=self.manifest,
1763 name=name,
1764 remote=remote,
1765 gitdir=gitdir,
1766 objdir=objdir,
1767 worktree=worktree,
1768 relpath=relpath,
1769 revisionExpr=self.revisionExpr,
1770 revisionId=rev,
1771 rebase=self.rebase,
1772 groups=self.groups,
1773 sync_c=self.sync_c,
1774 sync_s=self.sync_s,
1775 parent=self,
1776 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001777 result.append(subproject)
1778 result.extend(subproject.GetDerivedSubprojects())
1779 return result
1780
1781
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001782## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001783 def _CheckForSha1(self):
1784 try:
1785 # if revision (sha or tag) is not present then following function
1786 # throws an error.
1787 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1788 return True
1789 except GitError:
1790 # There is no such persistent revision. We have to fetch it.
1791 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001792
Julien Campergue335f5ef2013-10-16 11:02:35 +02001793 def _FetchArchive(self, tarpath, cwd=None):
1794 cmd = ['archive', '-v', '-o', tarpath]
1795 cmd.append('--remote=%s' % self.remote.url)
1796 cmd.append('--prefix=%s/' % self.relpath)
1797 cmd.append(self.revisionExpr)
1798
1799 command = GitCommand(self, cmd, cwd=cwd,
1800 capture_stdout=True,
1801 capture_stderr=True)
1802
1803 if command.Wait() != 0:
1804 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1805
Conley Owens80b87fe2014-05-09 17:13:44 -07001806
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001807 def _RemoteFetch(self, name=None,
1808 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001809 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001810 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001811 alt_dir=None,
1812 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001813
1814 is_sha1 = False
1815 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001816 depth = None
1817
1818 # The depth should not be used when fetching to a mirror because
1819 # it will result in a shallow repository that cannot be cloned or
1820 # fetched from.
1821 if not self.manifest.IsMirror:
1822 if self.clone_depth:
1823 depth = self.clone_depth
1824 else:
1825 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001826 # The repo project should never be synced with partial depth
1827 if self.relpath == '.repo/repo':
1828 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001829
Shawn Pearce69e04d82014-01-29 12:48:54 -08001830 if depth:
1831 current_branch_only = True
1832
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001833 if ID_RE.match(self.revisionExpr) is not None:
1834 is_sha1 = True
1835
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001836 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001837 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001838 # this is a tag and its sha1 value should never change
1839 tag_name = self.revisionExpr[len(R_TAGS):]
1840
1841 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001842 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001843 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001844 if is_sha1 and not depth:
1845 # When syncing a specific commit and --depth is not set:
1846 # * if upstream is explicitly specified and is not a sha1, fetch only
1847 # upstream as users expect only upstream to be fetch.
1848 # Note: The commit might not be in upstream in which case the sync
1849 # will fail.
1850 # * otherwise, fetch all branches to make sure we end up with the
1851 # specific commit.
1852 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001853
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001854 if not name:
1855 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001856
1857 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001858 remote = self.GetRemote(name)
1859 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001860 ssh_proxy = True
1861
Shawn O. Pearce88443382010-10-08 10:02:09 +02001862 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001863 if alt_dir and 'objects' == os.path.basename(alt_dir):
1864 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001865 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1866 remote = self.GetRemote(name)
1867
David Pursehouse8a68ff92012-09-24 12:15:13 +09001868 all_refs = self.bare_ref.all
1869 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001870 tmp = set()
1871
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301872 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001873 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001874 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001875 all_refs[r] = ref_id
1876 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001877 continue
1878
David Pursehouse8a68ff92012-09-24 12:15:13 +09001879 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001880 continue
1881
David Pursehouse8a68ff92012-09-24 12:15:13 +09001882 r = 'refs/_alt/%s' % ref_id
1883 all_refs[r] = ref_id
1884 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001885 tmp.add(r)
1886
Shawn O. Pearce88443382010-10-08 10:02:09 +02001887 tmp_packed = ''
1888 old_packed = ''
1889
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301890 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001891 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001892 tmp_packed += line
1893 if r not in tmp:
1894 old_packed += line
1895
1896 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001897 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001898 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001899
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001900 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001901
Conley Owensf97e8382015-01-21 11:12:46 -08001902 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001903 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07001904 else:
1905 # If this repo has shallow objects, then we don't know which refs have
1906 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
1907 # do this with projects that don't have shallow objects, since it is less
1908 # efficient.
1909 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
1910 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07001911
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001912 if quiet:
1913 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001914 if not self.worktree:
1915 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001916 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001917
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001918 # If using depth then we should not get all the tags since they may
1919 # be outside of the depth.
1920 if no_tags or depth:
1921 cmd.append('--no-tags')
1922 else:
1923 cmd.append('--tags')
1924
natalie.chene8996f92015-12-29 10:53:30 +08001925 refs = []
Conley Owens80b87fe2014-05-09 17:13:44 -07001926 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001927 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001928 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001929 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
natalie.chene8996f92015-12-29 10:53:30 +08001930 if self.manifest.IsMirror: refs.append('refs/heads/*')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001931 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001932 spec.append('tag')
1933 spec.append(tag_name)
natalie.chene8996f92015-12-29 10:53:30 +08001934 refs.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001935
David Pursehouse403b64e2015-04-27 10:41:33 +09001936 if not self.manifest.IsMirror:
1937 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06001938 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001939 # Shallow checkout of a specific commit, fetch from that commit and not
1940 # the heads only as the commit might be deeper in the history.
natalie.chene8996f92015-12-29 10:53:30 +08001941 spec.append(branch)
David Pursehouse403b64e2015-04-27 10:41:33 +09001942 else:
1943 if is_sha1:
1944 branch = self.upstream
1945 if branch is not None and branch.strip():
1946 if not branch.startswith('refs/'):
1947 branch = R_HEADS + branch
1948 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
natalie.chene8996f92015-12-29 10:53:30 +08001949 refs.append(remote.ToLocal(branch))
Conley Owens80b87fe2014-05-09 17:13:44 -07001950 cmd.extend(spec)
1951
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001952 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001953 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001954 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001955 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001956 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001957 ok = True
1958 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001959 # If needed, run the 'git remote prune' the first time through the loop
1960 elif (not _i and
1961 "error:" in gitcmd.stderr and
1962 "git remote prune" in gitcmd.stderr):
1963 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001964 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001965 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001966 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001967 break
1968 continue
Brian Harring14a66742012-09-28 20:21:57 -07001969 elif current_branch_only and is_sha1 and ret == 128:
1970 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1971 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1972 # abort the optimization attempt and do a full sync.
1973 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001974 elif ret < 0:
1975 # Git died with a signal, exit immediately
1976 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001977 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001978
1979 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001980 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001981 if old_packed != '':
1982 _lwrite(packed_refs, old_packed)
1983 else:
1984 os.remove(packed_refs)
1985 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001986
1987 if is_sha1 and current_branch_only and self.upstream:
1988 # We just synced the upstream given branch; verify we
1989 # got what we wanted, else trigger a second run of all
1990 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001991 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06001992 if not depth:
1993 # Avoid infinite recursion when depth is True (since depth implies
1994 # current_branch_only)
1995 return self._RemoteFetch(name=name, current_branch_only=False,
1996 initial=False, quiet=quiet, alt_dir=alt_dir)
1997 if self.clone_depth:
1998 self.clone_depth = None
1999 return self._RemoteFetch(name=name, current_branch_only=current_branch_only,
2000 initial=False, quiet=quiet, alt_dir=alt_dir)
natalie.chene8996f92015-12-29 10:53:30 +08002001 if self.lfs_fetch:
2002 self.__FetchLfsObjects(name, refs)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002003 return ok
2004
2005 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09002006 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002007 return False
2008
2009 remote = self.GetRemote(self.remote.name)
2010 bundle_url = remote.url + '/clone.bundle'
2011 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002012 if GetSchemeFromUrl(bundle_url) not in (
2013 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002014 return False
2015
2016 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2017 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002018
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002019 exist_dst = os.path.exists(bundle_dst)
2020 exist_tmp = os.path.exists(bundle_tmp)
2021
2022 if not initial and not exist_dst and not exist_tmp:
2023 return False
2024
2025 if not exist_dst:
2026 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2027 if not exist_dst:
2028 return False
2029
2030 cmd = ['fetch']
2031 if quiet:
2032 cmd.append('--quiet')
2033 if not self.worktree:
2034 cmd.append('--update-head-ok')
2035 cmd.append(bundle_dst)
2036 for f in remote.fetch:
2037 cmd.append(str(f))
2038 cmd.append('refs/tags/*:refs/tags/*')
2039
2040 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002041 if os.path.exists(bundle_dst):
2042 os.remove(bundle_dst)
2043 if os.path.exists(bundle_tmp):
2044 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002045 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002046
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002047 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002048 if os.path.exists(dstPath):
2049 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07002050
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002051 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002052 if quiet:
2053 cmd += ['--silent']
2054 if os.path.exists(tmpPath):
2055 size = os.stat(tmpPath).st_size
2056 if size >= 1024:
2057 cmd += ['--continue-at', '%d' % (size,)]
2058 else:
2059 os.remove(tmpPath)
2060 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2061 cmd += ['--proxy', os.environ['http_proxy']]
Dan Willemsen0745bb22015-08-17 13:41:45 -07002062 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002063 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002064 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002065 if srcUrl.startswith('persistent-'):
2066 srcUrl = srcUrl[len('persistent-'):]
2067 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002068
Dave Borowitz137d0132015-01-02 11:12:54 -08002069 if IsTrace():
2070 Trace('%s', ' '.join(cmd))
2071 try:
2072 proc = subprocess.Popen(cmd)
2073 except OSError:
2074 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002075
Dave Borowitz137d0132015-01-02 11:12:54 -08002076 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002077
Dave Borowitz137d0132015-01-02 11:12:54 -08002078 if curlret == 22:
2079 # From curl man page:
2080 # 22: HTTP page not retrieved. The requested url was not found or
2081 # returned another error with the HTTP error code being 400 or above.
2082 # This return code only appears if -f, --fail is used.
2083 if not quiet:
2084 print("Server does not provide clone.bundle; ignoring.",
2085 file=sys.stderr)
2086 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002087
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002088 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002089 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002090 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002091 return True
2092 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002093 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002094 return False
2095 else:
2096 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002097
Kris Giesingc8d882a2014-12-23 13:02:32 -08002098 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002099 try:
2100 with open(path) as f:
2101 if f.read(16) == '# v2 git bundle\n':
2102 return True
2103 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002104 if not quiet:
2105 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002106 return False
2107 except OSError:
2108 return False
2109
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002110 def _Checkout(self, rev, quiet=False):
2111 cmd = ['checkout']
2112 if quiet:
2113 cmd.append('-q')
2114 cmd.append(rev)
2115 cmd.append('--')
2116 if GitCommand(self, cmd).Wait() != 0:
2117 if self._allrefs:
2118 raise GitError('%s checkout %s ' % (self.name, rev))
2119
Anthony King7bdac712014-07-16 12:56:40 +01002120 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002121 cmd = ['cherry-pick']
2122 cmd.append(rev)
2123 cmd.append('--')
2124 if GitCommand(self, cmd).Wait() != 0:
2125 if self._allrefs:
2126 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2127
Anthony King7bdac712014-07-16 12:56:40 +01002128 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002129 cmd = ['revert']
2130 cmd.append('--no-edit')
2131 cmd.append(rev)
2132 cmd.append('--')
2133 if GitCommand(self, cmd).Wait() != 0:
2134 if self._allrefs:
2135 raise GitError('%s revert %s ' % (self.name, rev))
2136
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002137 def _ResetHard(self, rev, quiet=True):
2138 cmd = ['reset', '--hard']
2139 if quiet:
2140 cmd.append('-q')
2141 cmd.append(rev)
2142 if GitCommand(self, cmd).Wait() != 0:
2143 raise GitError('%s reset --hard %s ' % (self.name, rev))
2144
Anthony King7bdac712014-07-16 12:56:40 +01002145 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002146 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002147 if onto is not None:
2148 cmd.extend(['--onto', onto])
2149 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002150 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002151 raise GitError('%s rebase %s ' % (self.name, upstream))
2152
Pierre Tardy3d125942012-05-04 12:18:12 +02002153 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002154 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002155 if ffonly:
2156 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002157 if GitCommand(self, cmd).Wait() != 0:
2158 raise GitError('%s merge %s ' % (self.name, head))
2159
Kevin Degiabaa7f32014-11-12 11:27:45 -07002160 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002161 init_git_dir = not os.path.exists(self.gitdir)
2162 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002163 try:
2164 # Initialize the bare repository, which contains all of the objects.
2165 if init_obj_dir:
2166 os.makedirs(self.objdir)
2167 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002168
Kevin Degib1a07b82015-07-27 13:33:43 -06002169 # If we have a separate directory to hold refs, initialize it as well.
2170 if self.objdir != self.gitdir:
2171 if init_git_dir:
2172 os.makedirs(self.gitdir)
Kevin Degi384b3c52014-10-16 16:02:58 -06002173
Kevin Degib1a07b82015-07-27 13:33:43 -06002174 if init_obj_dir or init_git_dir:
2175 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2176 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002177 try:
2178 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2179 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002180 if force_sync:
David Pursehouse25857b82015-08-19 18:06:22 +09002181 print("Retrying clone after deleting %s" % self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002182 try:
2183 shutil.rmtree(os.path.realpath(self.gitdir))
2184 if self.worktree and os.path.exists(
2185 os.path.realpath(self.worktree)):
2186 shutil.rmtree(os.path.realpath(self.worktree))
2187 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2188 except:
2189 raise e
2190 raise e
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002191
Kevin Degib1a07b82015-07-27 13:33:43 -06002192 if init_git_dir:
2193 mp = self.manifest.manifestProject
2194 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002195
Kevin Degib1a07b82015-07-27 13:33:43 -06002196 if ref_dir or mirror_git:
2197 if not mirror_git:
2198 mirror_git = os.path.join(ref_dir, self.name + '.git')
2199 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2200 self.relpath + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002201
Kevin Degib1a07b82015-07-27 13:33:43 -06002202 if os.path.exists(mirror_git):
2203 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002204
Kevin Degib1a07b82015-07-27 13:33:43 -06002205 elif os.path.exists(repo_git):
2206 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002207
Kevin Degib1a07b82015-07-27 13:33:43 -06002208 else:
2209 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002210
Kevin Degib1a07b82015-07-27 13:33:43 -06002211 if ref_dir:
2212 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2213 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002214
Kevin Degib1a07b82015-07-27 13:33:43 -06002215 self._UpdateHooks()
Jimmie Westera0444582012-10-24 13:44:42 +02002216
Kevin Degib1a07b82015-07-27 13:33:43 -06002217 m = self.manifest.manifestProject.config
2218 for key in ['user.name', 'user.email']:
2219 if m.Has(key, include_defaults=False):
2220 self.config.SetString(key, m.GetString(key))
2221 if self.manifest.IsMirror:
2222 self.config.SetString('core.bare', 'true')
2223 else:
2224 self.config.SetString('core.bare', None)
natalie.chene8996f92015-12-29 10:53:30 +08002225
2226 if self.lfs_fetch:
2227 self.config.SetString('filter.lfs.clean', 'git-lfs clean %f')
2228 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge %f')
2229 self.config.SetString('filter.lfs.required', 'true')
2230
Kevin Degib1a07b82015-07-27 13:33:43 -06002231 except Exception:
2232 if init_obj_dir and os.path.exists(self.objdir):
2233 shutil.rmtree(self.objdir)
2234 if init_git_dir and os.path.exists(self.gitdir):
2235 shutil.rmtree(self.gitdir)
2236 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002237
Jimmie Westera0444582012-10-24 13:44:42 +02002238 def _UpdateHooks(self):
2239 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002240 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002241
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002242 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002243 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002244 if not os.path.exists(hooks):
2245 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002246 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002247 name = os.path.basename(stock_hook)
2248
Victor Boivie65e0f352011-04-18 11:23:29 +02002249 if name in ('commit-msg',) and not self.remote.review \
2250 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002251 # Don't install a Gerrit Code Review hook if this
2252 # project does not appear to use it for reviews.
2253 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002254 # Since the manifest project is one of those, but also
2255 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002256 continue
2257
2258 dst = os.path.join(hooks, name)
2259 if os.path.islink(dst):
2260 continue
2261 if os.path.exists(dst):
2262 if filecmp.cmp(stock_hook, dst, shallow=False):
2263 os.remove(dst)
2264 else:
David Pursehousedc2545c2015-08-24 14:43:45 +09002265 _warn("%s: Not replacing locally modified %s hook", self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002266 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002267 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002268 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002269 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002270 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002271 raise GitError('filesystem must support symlinks')
2272 else:
2273 raise
2274
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002275 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002276 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002277 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002278 remote.url = self.remote.url
2279 remote.review = self.remote.review
2280 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002281
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002282 if self.worktree:
2283 remote.ResetFetch(mirror=False)
2284 else:
2285 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002286 remote.Save()
2287
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288 def _InitMRef(self):
2289 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002290 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002291
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002292 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002293 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002294
2295 def _InitAnyMRef(self, ref):
2296 cur = self.bare_ref.symref(ref)
2297
2298 if self.revisionId:
2299 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2300 msg = 'manifest set to %s' % self.revisionId
2301 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002302 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002303 else:
2304 remote = self.GetRemote(self.remote.name)
2305 dst = remote.ToLocal(self.revisionExpr)
2306 if cur != dst:
2307 msg = 'manifest set to %s' % self.revisionExpr
2308 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002309
Kevin Degi384b3c52014-10-16 16:02:58 -06002310 def _CheckDirReference(self, srcdir, destdir, share_refs):
2311 symlink_files = self.shareable_files
2312 symlink_dirs = self.shareable_dirs
2313 if share_refs:
2314 symlink_files += self.working_tree_files
2315 symlink_dirs += self.working_tree_dirs
2316 to_symlink = symlink_files + symlink_dirs
2317 for name in set(to_symlink):
2318 dst = os.path.realpath(os.path.join(destdir, name))
2319 if os.path.lexists(dst):
2320 src = os.path.realpath(os.path.join(srcdir, name))
2321 # Fail if the links are pointing to the wrong place
2322 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002323 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002324 'work tree. If you\'re comfortable with the '
2325 'possibility of losing the work tree\'s git metadata,'
2326 ' use `repo sync --force-sync {0}` to '
2327 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002328
David James8d201162013-10-11 17:03:19 -07002329 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2330 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2331
2332 Args:
2333 gitdir: The bare git repository. Must already be initialized.
2334 dotgit: The repository you would like to initialize.
2335 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2336 Only one work tree can store refs under a given |gitdir|.
2337 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2338 This saves you the effort of initializing |dotgit| yourself.
2339 """
Kevin Degi384b3c52014-10-16 16:02:58 -06002340 symlink_files = self.shareable_files
2341 symlink_dirs = self.shareable_dirs
David James8d201162013-10-11 17:03:19 -07002342 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002343 symlink_files += self.working_tree_files
2344 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002345 to_symlink = symlink_files + symlink_dirs
2346
2347 to_copy = []
2348 if copy_all:
2349 to_copy = os.listdir(gitdir)
2350
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002351 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002352 for name in set(to_copy).union(to_symlink):
2353 try:
2354 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002355 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002356
Kevin Degi384b3c52014-10-16 16:02:58 -06002357 if os.path.lexists(dst):
2358 continue
David James8d201162013-10-11 17:03:19 -07002359
2360 # If the source dir doesn't exist, create an empty dir.
2361 if name in symlink_dirs and not os.path.lexists(src):
2362 os.makedirs(src)
2363
Conley Owens80b87fe2014-05-09 17:13:44 -07002364 # If the source file doesn't exist, ensure the destination
2365 # file doesn't either.
2366 if name in symlink_files and not os.path.lexists(src):
2367 try:
2368 os.remove(dst)
2369 except OSError:
2370 pass
2371
David James8d201162013-10-11 17:03:19 -07002372 if name in to_symlink:
2373 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2374 elif copy_all and not os.path.islink(dst):
2375 if os.path.isdir(src):
2376 shutil.copytree(src, dst)
2377 elif os.path.isfile(src):
2378 shutil.copy(src, dst)
2379 except OSError as e:
2380 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002381 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002382 else:
2383 raise
2384
Kevin Degiabaa7f32014-11-12 11:27:45 -07002385 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002386 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002387 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002388 try:
2389 if init_dotgit:
2390 os.makedirs(dotgit)
2391 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2392 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002393
Kevin Degiabaa7f32014-11-12 11:27:45 -07002394 try:
2395 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2396 except GitError as e:
2397 if force_sync:
2398 try:
2399 shutil.rmtree(dotgit)
2400 return self._InitWorkTree(force_sync=False)
2401 except:
2402 raise e
2403 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002404
Kevin Degib1a07b82015-07-27 13:33:43 -06002405 if init_dotgit:
2406 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002407
Kevin Degib1a07b82015-07-27 13:33:43 -06002408 cmd = ['read-tree', '--reset', '-u']
2409 cmd.append('-v')
2410 cmd.append(HEAD)
2411 if GitCommand(self, cmd).Wait() != 0:
2412 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002413
Kevin Degib1a07b82015-07-27 13:33:43 -06002414 self._CopyAndLinkFiles()
2415 except Exception:
2416 if init_dotgit:
2417 shutil.rmtree(dotgit)
2418 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002419
2420 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002421 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002422
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002423 def _revlist(self, *args, **kw):
2424 a = []
2425 a.extend(args)
2426 a.append('--')
2427 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002428
2429 @property
2430 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002431 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002432
Julien Camperguedd654222014-01-09 16:21:37 +01002433 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2434 """Get logs between two revisions of this project."""
2435 comp = '..'
2436 if rev1:
2437 revs = [rev1]
2438 if rev2:
2439 revs.extend([comp, rev2])
2440 cmd = ['log', ''.join(revs)]
2441 out = DiffColoring(self.config)
2442 if out.is_on and color:
2443 cmd.append('--color')
2444 if oneline:
2445 cmd.append('--oneline')
2446
2447 try:
2448 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2449 if log.Wait() == 0:
2450 return log.stdout
2451 except GitError:
2452 # worktree may not exist if groups changed for example. In that case,
2453 # try in gitdir instead.
2454 if not os.path.exists(self.worktree):
2455 return self.bare_git.log(*cmd[1:])
2456 else:
2457 raise
2458 return None
2459
2460 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2461 """Get the list of logs from this revision to given revisionId"""
2462 logs = {}
2463 selfId = self.GetRevisionId(self._allrefs)
2464 toId = toProject.GetRevisionId(toProject._allrefs)
2465
2466 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2467 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2468 return logs
2469
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002470 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002471 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002472 self._project = project
2473 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002474 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002475
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002476 def LsOthers(self):
2477 p = GitCommand(self._project,
2478 ['ls-files',
2479 '-z',
2480 '--others',
2481 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002482 bare=False,
David James8d201162013-10-11 17:03:19 -07002483 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002484 capture_stdout=True,
2485 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002486 if p.Wait() == 0:
2487 out = p.stdout
2488 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002489 return out[:-1].split('\0') # pylint: disable=W1401
2490 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002491 return []
2492
2493 def DiffZ(self, name, *args):
2494 cmd = [name]
2495 cmd.append('-z')
2496 cmd.extend(args)
2497 p = GitCommand(self._project,
2498 cmd,
David James8d201162013-10-11 17:03:19 -07002499 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002500 bare=False,
2501 capture_stdout=True,
2502 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002503 try:
2504 out = p.process.stdout.read()
2505 r = {}
2506 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002507 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002508 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002509 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002510 info = next(out)
2511 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002512 except StopIteration:
2513 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002514
2515 class _Info(object):
2516 def __init__(self, path, omode, nmode, oid, nid, state):
2517 self.path = path
2518 self.src_path = None
2519 self.old_mode = omode
2520 self.new_mode = nmode
2521 self.old_id = oid
2522 self.new_id = nid
2523
2524 if len(state) == 1:
2525 self.status = state
2526 self.level = None
2527 else:
2528 self.status = state[:1]
2529 self.level = state[1:]
2530 while self.level.startswith('0'):
2531 self.level = self.level[1:]
2532
2533 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002534 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002535 if info.status in ('R', 'C'):
2536 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002537 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002538 r[info.path] = info
2539 return r
2540 finally:
2541 p.Wait()
2542
2543 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002544 if self._bare:
2545 path = os.path.join(self._project.gitdir, HEAD)
2546 else:
2547 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002548 try:
2549 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002550 except IOError as e:
2551 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002552 try:
2553 line = fd.read()
2554 finally:
2555 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302556 try:
2557 line = line.decode()
2558 except AttributeError:
2559 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002560 if line.startswith('ref: '):
2561 return line[5:-1]
2562 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002563
2564 def SetHead(self, ref, message=None):
2565 cmdv = []
2566 if message is not None:
2567 cmdv.extend(['-m', message])
2568 cmdv.append(HEAD)
2569 cmdv.append(ref)
2570 self.symbolic_ref(*cmdv)
2571
2572 def DetachHead(self, new, message=None):
2573 cmdv = ['--no-deref']
2574 if message is not None:
2575 cmdv.extend(['-m', message])
2576 cmdv.append(HEAD)
2577 cmdv.append(new)
2578 self.update_ref(*cmdv)
2579
2580 def UpdateRef(self, name, new, old=None,
2581 message=None,
2582 detach=False):
2583 cmdv = []
2584 if message is not None:
2585 cmdv.extend(['-m', message])
2586 if detach:
2587 cmdv.append('--no-deref')
2588 cmdv.append(name)
2589 cmdv.append(new)
2590 if old is not None:
2591 cmdv.append(old)
2592 self.update_ref(*cmdv)
2593
2594 def DeleteRef(self, name, old=None):
2595 if not old:
2596 old = self.rev_parse(name)
2597 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002598 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002599
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002600 def rev_list(self, *args, **kw):
2601 if 'format' in kw:
2602 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2603 else:
2604 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002605 cmdv.extend(args)
2606 p = GitCommand(self._project,
2607 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002608 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002609 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002610 capture_stdout=True,
2611 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002612 r = []
2613 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002614 if line[-1] == '\n':
2615 line = line[:-1]
2616 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002617 if p.Wait() != 0:
2618 raise GitError('%s rev-list %s: %s' % (
2619 self._project.name,
2620 str(args),
2621 p.stderr))
2622 return r
2623
2624 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002625 """Allow arbitrary git commands using pythonic syntax.
2626
2627 This allows you to do things like:
2628 git_obj.rev_parse('HEAD')
2629
2630 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2631 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002632 Any other positional arguments will be passed to the git command, and the
2633 following keyword arguments are supported:
2634 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002635
2636 Args:
2637 name: The name of the git command to call. Any '_' characters will
2638 be replaced with '-'.
2639
2640 Returns:
2641 A callable object that will try to call git with the named command.
2642 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002643 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002644 def runner(*args, **kwargs):
2645 cmdv = []
2646 config = kwargs.pop('config', None)
2647 for k in kwargs:
2648 raise TypeError('%s() got an unexpected keyword argument %r'
2649 % (name, k))
2650 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002651 if not git_require((1, 7, 2)):
2652 raise ValueError('cannot set config on command line for %s()'
2653 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302654 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002655 cmdv.append('-c')
2656 cmdv.append('%s=%s' % (k, v))
2657 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002658 cmdv.extend(args)
2659 p = GitCommand(self._project,
2660 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002661 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002662 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002663 capture_stdout=True,
2664 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002665 if p.Wait() != 0:
2666 raise GitError('%s %s: %s' % (
2667 self._project.name,
2668 name,
2669 p.stderr))
2670 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302671 try:
Conley Owensedd01512013-09-26 12:59:58 -07002672 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302673 except AttributeError:
2674 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002675 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2676 return r[:-1]
2677 return r
2678 return runner
2679
2680
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002681class _PriorSyncFailedError(Exception):
2682 def __str__(self):
2683 return 'prior sync failed; rebase still in progress'
2684
2685class _DirtyError(Exception):
2686 def __str__(self):
2687 return 'contains uncommitted changes'
2688
2689class _InfoMessage(object):
2690 def __init__(self, project, text):
2691 self.project = project
2692 self.text = text
2693
2694 def Print(self, syncbuf):
2695 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2696 syncbuf.out.nl()
2697
2698class _Failure(object):
2699 def __init__(self, project, why):
2700 self.project = project
2701 self.why = why
2702
2703 def Print(self, syncbuf):
2704 syncbuf.out.fail('error: %s/: %s',
2705 self.project.relpath,
2706 str(self.why))
2707 syncbuf.out.nl()
2708
2709class _Later(object):
2710 def __init__(self, project, action):
2711 self.project = project
2712 self.action = action
2713
2714 def Run(self, syncbuf):
2715 out = syncbuf.out
2716 out.project('project %s/', self.project.relpath)
2717 out.nl()
2718 try:
2719 self.action()
2720 out.nl()
2721 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002722 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002723 out.nl()
2724 return False
2725
2726class _SyncColoring(Coloring):
2727 def __init__(self, config):
2728 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002729 self.project = self.printer('header', attr='bold')
2730 self.info = self.printer('info')
2731 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002732
2733class SyncBuffer(object):
2734 def __init__(self, config, detach_head=False):
2735 self._messages = []
2736 self._failures = []
2737 self._later_queue1 = []
2738 self._later_queue2 = []
2739
2740 self.out = _SyncColoring(config)
2741 self.out.redirect(sys.stderr)
2742
2743 self.detach_head = detach_head
2744 self.clean = True
2745
2746 def info(self, project, fmt, *args):
2747 self._messages.append(_InfoMessage(project, fmt % args))
2748
2749 def fail(self, project, err=None):
2750 self._failures.append(_Failure(project, err))
2751 self.clean = False
2752
2753 def later1(self, project, what):
2754 self._later_queue1.append(_Later(project, what))
2755
2756 def later2(self, project, what):
2757 self._later_queue2.append(_Later(project, what))
2758
2759 def Finish(self):
2760 self._PrintMessages()
2761 self._RunLater()
2762 self._PrintMessages()
2763 return self.clean
2764
2765 def _RunLater(self):
2766 for q in ['_later_queue1', '_later_queue2']:
2767 if not self._RunQueue(q):
2768 return
2769
2770 def _RunQueue(self, queue):
2771 for m in getattr(self, queue):
2772 if not m.Run(self):
2773 self.clean = False
2774 return False
2775 setattr(self, queue, [])
2776 return True
2777
2778 def _PrintMessages(self):
2779 for m in self._messages:
2780 m.Print(self)
2781 for m in self._failures:
2782 m.Print(self)
2783
2784 self._messages = []
2785 self._failures = []
2786
2787
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002788class MetaProject(Project):
2789 """A special project housed under .repo.
2790 """
2791 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002792 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002793 manifest=manifest,
2794 name=name,
2795 gitdir=gitdir,
2796 objdir=gitdir,
2797 worktree=worktree,
2798 remote=RemoteSpec('origin'),
2799 relpath='.repo/%s' % name,
2800 revisionExpr='refs/heads/master',
2801 revisionId=None,
2802 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002803
2804 def PreSync(self):
2805 if self.Exists:
2806 cb = self.CurrentBranch
2807 if cb:
2808 base = self.GetBranch(cb).merge
2809 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002810 self.revisionExpr = base
2811 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002812
Anthony King7bdac712014-07-16 12:56:40 +01002813 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002814 """ Prepare MetaProject for manifest branch switch
2815 """
2816
2817 # detach and delete manifest branch, allowing a new
2818 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002819 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002820 self.Sync_LocalHalf(syncbuf)
2821 syncbuf.Finish()
2822
2823 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002824 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002825 capture_stdout=True,
2826 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002827
2828
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002829 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002830 def LastFetch(self):
2831 try:
2832 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2833 return os.path.getmtime(fh)
2834 except OSError:
2835 return 0
2836
2837 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002838 def HasChanges(self):
2839 """Has the remote received new commits not yet checked out?
2840 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002841 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002842 return False
2843
David Pursehouse8a68ff92012-09-24 12:15:13 +09002844 all_refs = self.bare_ref.all
2845 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002846 head = self.work_git.GetHead()
2847 if head.startswith(R_HEADS):
2848 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002849 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002850 except KeyError:
2851 head = None
2852
2853 if revid == head:
2854 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002855 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002856 return True
2857 return False