blob: 003489aa67bcabed4d4b60ef883100ba34b6253c [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Dave Borowitz137d0132015-01-02 11:12:54 -080016import contextlib
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080017import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import filecmp
19import 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
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090034from error import GitError, HookError, UploadError
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
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070066def not_rev(r):
67 return '^' + r
68
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080069def sq(r):
70 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080071
Jonathan Nieder93719792015-03-17 11:29:58 -070072_project_hook_list = None
73def _ProjectHooks():
74 """List the hooks present in the 'hooks' directory.
75
76 These hooks are project hooks and are copied to the '.git/hooks' directory
77 of all subprojects.
78
79 This function caches the list of hooks (based on the contents of the
80 'repo/hooks' directory) on the first call.
81
82 Returns:
83 A list of absolute paths to all of the files in the hooks directory.
84 """
85 global _project_hook_list
86 if _project_hook_list is None:
87 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
88 d = os.path.join(d, 'hooks')
89 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
90 return _project_hook_list
91
92
Shawn O. Pearce632768b2008-10-23 11:58:52 -070093class DownloadedChange(object):
94 _commit_cache = None
95
96 def __init__(self, project, base, change_id, ps_id, commit):
97 self.project = project
98 self.base = base
99 self.change_id = change_id
100 self.ps_id = ps_id
101 self.commit = commit
102
103 @property
104 def commits(self):
105 if self._commit_cache is None:
106 self._commit_cache = self.project.bare_git.rev_list(
107 '--abbrev=8',
108 '--abbrev-commit',
109 '--pretty=oneline',
110 '--reverse',
111 '--date-order',
112 not_rev(self.base),
113 self.commit,
114 '--')
115 return self._commit_cache
116
117
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700118class ReviewableBranch(object):
119 _commit_cache = None
120
121 def __init__(self, project, branch, base):
122 self.project = project
123 self.branch = branch
124 self.base = base
125
126 @property
127 def name(self):
128 return self.branch.name
129
130 @property
131 def commits(self):
132 if self._commit_cache is None:
133 self._commit_cache = self.project.bare_git.rev_list(
134 '--abbrev=8',
135 '--abbrev-commit',
136 '--pretty=oneline',
137 '--reverse',
138 '--date-order',
139 not_rev(self.base),
140 R_HEADS + self.name,
141 '--')
142 return self._commit_cache
143
144 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800145 def unabbrev_commits(self):
146 r = dict()
147 for commit in self.project.bare_git.rev_list(
148 not_rev(self.base),
149 R_HEADS + self.name,
150 '--'):
151 r[commit[0:8]] = commit
152 return r
153
154 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700155 def date(self):
156 return self.project.bare_git.log(
157 '--pretty=format:%cd',
158 '-n', '1',
159 R_HEADS + self.name,
160 '--')
161
Bryan Jacobsf609f912013-05-06 13:36:24 -0400162 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800163 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700164 people,
Brian Harring435370c2012-07-28 15:37:04 -0700165 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400166 draft=draft,
167 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700168
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700169 def GetPublishedRefs(self):
170 refs = {}
171 output = self.project.bare_git.ls_remote(
172 self.branch.remote.SshReviewUrl(self.project.UserEmail),
173 'refs/changes/*')
174 for line in output.split('\n'):
175 try:
176 (sha, ref) = line.split()
177 refs[sha] = ref
178 except ValueError:
179 pass
180
181 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182
183class StatusColoring(Coloring):
184 def __init__(self, config):
185 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100186 self.project = self.printer('header', attr='bold')
187 self.branch = self.printer('header', attr='bold')
188 self.nobranch = self.printer('nobranch', fg='red')
189 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700190
Anthony King7bdac712014-07-16 12:56:40 +0100191 self.added = self.printer('added', fg='green')
192 self.changed = self.printer('changed', fg='red')
193 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700194
195
196class DiffColoring(Coloring):
197 def __init__(self, config):
198 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100199 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700200
Anthony King7bdac712014-07-16 12:56:40 +0100201class _Annotation(object):
James W. Mills24c13082012-04-12 15:04:13 -0500202 def __init__(self, name, value, keep):
203 self.name = name
204 self.value = value
205 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206
Anthony King7bdac712014-07-16 12:56:40 +0100207class _CopyFile(object):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800208 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700209 self.src = src
210 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800211 self.abs_src = abssrc
212 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213
214 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800215 src = self.abs_src
216 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 # copy file if it does not exist or is out of date
218 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
219 try:
220 # remove existing file first, since it might be read-only
221 if os.path.exists(dest):
222 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400223 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200224 dest_dir = os.path.dirname(dest)
225 if not os.path.isdir(dest_dir):
226 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227 shutil.copy(src, dest)
228 # make the file read-only
229 mode = os.stat(dest)[stat.ST_MODE]
230 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
231 os.chmod(dest, mode)
232 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700233 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234
Anthony King7bdac712014-07-16 12:56:40 +0100235class _LinkFile(object):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500236 def __init__(self, src, dest, abssrc, absdest):
237 self.src = src
238 self.dest = dest
239 self.abs_src = abssrc
240 self.abs_dest = absdest
241
242 def _Link(self):
243 src = self.abs_src
244 dest = self.abs_dest
245 # link file if it does not exist or is out of date
246 if not os.path.islink(dest) or os.readlink(dest) != src:
247 try:
248 # remove existing file first, since it might be read-only
249 if os.path.exists(dest):
250 os.remove(dest)
251 else:
252 dest_dir = os.path.dirname(dest)
253 if not os.path.isdir(dest_dir):
254 os.makedirs(dest_dir)
255 os.symlink(src, dest)
256 except IOError:
257 _error('Cannot link file %s to %s', src, dest)
258
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700259class RemoteSpec(object):
260 def __init__(self,
261 name,
Anthony King7bdac712014-07-16 12:56:40 +0100262 url=None,
263 review=None,
264 revision=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700265 self.name = name
266 self.url = url
267 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100268 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700269
Doug Anderson37282b42011-03-04 11:54:18 -0800270class RepoHook(object):
271 """A RepoHook contains information about a script to run as a hook.
272
273 Hooks are used to run a python script before running an upload (for instance,
274 to run presubmit checks). Eventually, we may have hooks for other actions.
275
276 This shouldn't be confused with files in the 'repo/hooks' directory. Those
277 files are copied into each '.git/hooks' folder for each project. Repo-level
278 hooks are associated instead with repo actions.
279
280 Hooks are always python. When a hook is run, we will load the hook into the
281 interpreter and execute its main() function.
282 """
283 def __init__(self,
284 hook_type,
285 hooks_project,
286 topdir,
287 abort_if_user_denies=False):
288 """RepoHook constructor.
289
290 Params:
291 hook_type: A string representing the type of hook. This is also used
292 to figure out the name of the file containing the hook. For
293 example: 'pre-upload'.
294 hooks_project: The project containing the repo hooks. If you have a
295 manifest, this is manifest.repo_hooks_project. OK if this is None,
296 which will make the hook a no-op.
297 topdir: Repo's top directory (the one containing the .repo directory).
298 Scripts will run with CWD as this directory. If you have a manifest,
299 this is manifest.topdir
300 abort_if_user_denies: If True, we'll throw a HookError() if the user
301 doesn't allow us to run the hook.
302 """
303 self._hook_type = hook_type
304 self._hooks_project = hooks_project
305 self._topdir = topdir
306 self._abort_if_user_denies = abort_if_user_denies
307
308 # Store the full path to the script for convenience.
309 if self._hooks_project:
310 self._script_fullpath = os.path.join(self._hooks_project.worktree,
311 self._hook_type + '.py')
312 else:
313 self._script_fullpath = None
314
315 def _GetHash(self):
316 """Return a hash of the contents of the hooks directory.
317
318 We'll just use git to do this. This hash has the property that if anything
319 changes in the directory we will return a different has.
320
321 SECURITY CONSIDERATION:
322 This hash only represents the contents of files in the hook directory, not
323 any other files imported or called by hooks. Changes to imported files
324 can change the script behavior without affecting the hash.
325
326 Returns:
327 A string representing the hash. This will always be ASCII so that it can
328 be printed to the user easily.
329 """
330 assert self._hooks_project, "Must have hooks to calculate their hash."
331
332 # We will use the work_git object rather than just calling GetRevisionId().
333 # That gives us a hash of the latest checked in version of the files that
334 # the user will actually be executing. Specifically, GetRevisionId()
335 # doesn't appear to change even if a user checks out a different version
336 # of the hooks repo (via git checkout) nor if a user commits their own revs.
337 #
338 # NOTE: Local (non-committed) changes will not be factored into this hash.
339 # I think this is OK, since we're really only worried about warning the user
340 # about upstream changes.
341 return self._hooks_project.work_git.rev_parse('HEAD')
342
343 def _GetMustVerb(self):
344 """Return 'must' if the hook is required; 'should' if not."""
345 if self._abort_if_user_denies:
346 return 'must'
347 else:
348 return 'should'
349
350 def _CheckForHookApproval(self):
351 """Check to see whether this hook has been approved.
352
353 We'll look at the hash of all of the hooks. If this matches the hash that
354 the user last approved, we're done. If it doesn't, we'll ask the user
355 about approval.
356
357 Note that we ask permission for each individual hook even though we use
358 the hash of all hooks when detecting changes. We'd like the user to be
359 able to approve / deny each hook individually. We only use the hash of all
360 hooks because there is no other easy way to detect changes to local imports.
361
362 Returns:
363 True if this hook is approved to run; False otherwise.
364
365 Raises:
366 HookError: Raised if the user doesn't approve and abort_if_user_denies
367 was passed to the consturctor.
368 """
Doug Anderson37282b42011-03-04 11:54:18 -0800369 hooks_config = self._hooks_project.config
370 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
371
372 # Get the last hash that the user approved for this hook; may be None.
373 old_hash = hooks_config.GetString(git_approval_key)
374
375 # Get the current hash so we can tell if scripts changed since approval.
376 new_hash = self._GetHash()
377
378 if old_hash is not None:
379 # User previously approved hook and asked not to be prompted again.
380 if new_hash == old_hash:
381 # Approval matched. We're done.
382 return True
383 else:
384 # Give the user a reason why we're prompting, since they last told
385 # us to "never ask again".
386 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
387 self._hook_type)
388 else:
389 prompt = ''
390
391 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
392 if sys.stdout.isatty():
393 prompt += ('Repo %s run the script:\n'
394 ' %s\n'
395 '\n'
396 'Do you want to allow this script to run '
397 '(yes/yes-never-ask-again/NO)? ') % (
398 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530399 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900400 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800401
402 # User is doing a one-time approval.
403 if response in ('y', 'yes'):
404 return True
405 elif response == 'yes-never-ask-again':
406 hooks_config.SetString(git_approval_key, new_hash)
407 return True
408
409 # For anything else, we'll assume no approval.
410 if self._abort_if_user_denies:
411 raise HookError('You must allow the %s hook or use --no-verify.' %
412 self._hook_type)
413
414 return False
415
416 def _ExecuteHook(self, **kwargs):
417 """Actually execute the given hook.
418
419 This will run the hook's 'main' function in our python interpreter.
420
421 Args:
422 kwargs: Keyword arguments to pass to the hook. These are often specific
423 to the hook type. For instance, pre-upload hooks will contain
424 a project_list.
425 """
426 # Keep sys.path and CWD stashed away so that we can always restore them
427 # upon function exit.
428 orig_path = os.getcwd()
429 orig_syspath = sys.path
430
431 try:
432 # Always run hooks with CWD as topdir.
433 os.chdir(self._topdir)
434
435 # Put the hook dir as the first item of sys.path so hooks can do
436 # relative imports. We want to replace the repo dir as [0] so
437 # hooks can't import repo files.
438 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
439
440 # Exec, storing global context in the context dict. We catch exceptions
441 # and convert to a HookError w/ just the failing traceback.
442 context = {}
443 try:
Anthony King70f68902014-05-05 21:15:34 +0100444 exec(compile(open(self._script_fullpath).read(),
445 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800446 except Exception:
447 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
448 traceback.format_exc(), self._hook_type))
449
450 # Running the script should have defined a main() function.
451 if 'main' not in context:
452 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
453
454
455 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
456 # We don't actually want hooks to define their main with this argument--
457 # it's there to remind them that their hook should always take **kwargs.
458 # For instance, a pre-upload hook should be defined like:
459 # def main(project_list, **kwargs):
460 #
461 # This allows us to later expand the API without breaking old hooks.
462 kwargs = kwargs.copy()
463 kwargs['hook_should_take_kwargs'] = True
464
465 # Call the main function in the hook. If the hook should cause the
466 # build to fail, it will raise an Exception. We'll catch that convert
467 # to a HookError w/ just the failing traceback.
468 try:
469 context['main'](**kwargs)
470 except Exception:
471 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
472 'above.' % (
473 traceback.format_exc(), self._hook_type))
474 finally:
475 # Restore sys.path and CWD.
476 sys.path = orig_syspath
477 os.chdir(orig_path)
478
479 def Run(self, user_allows_all_hooks, **kwargs):
480 """Run the hook.
481
482 If the hook doesn't exist (because there is no hooks project or because
483 this particular hook is not enabled), this is a no-op.
484
485 Args:
486 user_allows_all_hooks: If True, we will never prompt about running the
487 hook--we'll just assume it's OK to run it.
488 kwargs: Keyword arguments to pass to the hook. These are often specific
489 to the hook type. For instance, pre-upload hooks will contain
490 a project_list.
491
492 Raises:
493 HookError: If there was a problem finding the hook or the user declined
494 to run a required hook (from _CheckForHookApproval).
495 """
496 # No-op if there is no hooks project or if hook is disabled.
497 if ((not self._hooks_project) or
498 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
499 return
500
501 # Bail with a nice error if we can't find the hook.
502 if not os.path.isfile(self._script_fullpath):
503 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
504
505 # Make sure the user is OK with running the hook.
506 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
507 return
508
509 # Run the hook with the same version of python we're using.
510 self._ExecuteHook(**kwargs)
511
512
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700513class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600514 # These objects can be shared between several working trees.
515 shareable_files = ['description', 'info']
516 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
517 # These objects can only be used by a single working tree.
518 working_tree_files = ['config', 'packed-refs', 'shallow']
519 working_tree_dirs = ['logs', 'refs']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700520 def __init__(self,
521 manifest,
522 name,
523 remote,
524 gitdir,
David James8d201162013-10-11 17:03:19 -0700525 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700526 worktree,
527 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700528 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800529 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100530 rebase=True,
531 groups=None,
532 sync_c=False,
533 sync_s=False,
534 clone_depth=None,
535 upstream=None,
536 parent=None,
537 is_derived=False,
538 dest_branch=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800539 """Init a Project object.
540
541 Args:
542 manifest: The XmlManifest object.
543 name: The `name` attribute of manifest.xml's project element.
544 remote: RemoteSpec object specifying its remote's properties.
545 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700546 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800547 worktree: Absolute path of git working tree.
548 relpath: Relative path of git working tree to repo's top directory.
549 revisionExpr: The `revision` attribute of manifest.xml's project element.
550 revisionId: git commit id for checking out.
551 rebase: The `rebase` attribute of manifest.xml's project element.
552 groups: The `groups` attribute of manifest.xml's project element.
553 sync_c: The `sync-c` attribute of manifest.xml's project element.
554 sync_s: The `sync-s` attribute of manifest.xml's project element.
555 upstream: The `upstream` attribute of manifest.xml's project element.
556 parent: The parent Project object.
557 is_derived: False if the project was explicitly defined in the manifest;
558 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400559 dest_branch: The branch to which to push changes for review by default.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800560 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700561 self.manifest = manifest
562 self.name = name
563 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800564 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700565 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800566 if worktree:
567 self.worktree = worktree.replace('\\', '/')
568 else:
569 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700570 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700571 self.revisionExpr = revisionExpr
572
573 if revisionId is None \
574 and revisionExpr \
575 and IsId(revisionExpr):
576 self.revisionId = revisionExpr
577 else:
578 self.revisionId = revisionId
579
Mike Pontillod3153822012-02-28 11:53:24 -0800580 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700581 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700582 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800583 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900584 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700585 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800586 self.parent = parent
587 self.is_derived = is_derived
588 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800589
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700590 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700591 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500592 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500593 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700594 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100595 gitdir=self.gitdir,
596 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700597
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800598 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700599 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800600 else:
601 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700602 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700603 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700604 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400605 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700606
Doug Anderson37282b42011-03-04 11:54:18 -0800607 # This will be filled in if a project is later identified to be the
608 # project containing repo hooks.
609 self.enabled_repo_hooks = []
610
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700611 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800612 def Derived(self):
613 return self.is_derived
614
615 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700616 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600617 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700618
619 @property
620 def CurrentBranch(self):
621 """Obtain the name of the currently checked out branch.
622 The branch name omits the 'refs/heads/' prefix.
623 None is returned if the project is on a detached HEAD.
624 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700625 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700626 if b.startswith(R_HEADS):
627 return b[len(R_HEADS):]
628 return None
629
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700630 def IsRebaseInProgress(self):
631 w = self.worktree
632 g = os.path.join(w, '.git')
633 return os.path.exists(os.path.join(g, 'rebase-apply')) \
634 or os.path.exists(os.path.join(g, 'rebase-merge')) \
635 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200636
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700637 def IsDirty(self, consider_untracked=True):
638 """Is the working directory modified in some way?
639 """
640 self.work_git.update_index('-q',
641 '--unmerged',
642 '--ignore-missing',
643 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900644 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645 return True
646 if self.work_git.DiffZ('diff-files'):
647 return True
648 if consider_untracked and self.work_git.LsOthers():
649 return True
650 return False
651
652 _userident_name = None
653 _userident_email = None
654
655 @property
656 def UserName(self):
657 """Obtain the user's personal name.
658 """
659 if self._userident_name is None:
660 self._LoadUserIdentity()
661 return self._userident_name
662
663 @property
664 def UserEmail(self):
665 """Obtain the user's email address. This is very likely
666 to be their Gerrit login.
667 """
668 if self._userident_email is None:
669 self._LoadUserIdentity()
670 return self._userident_email
671
672 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900673 u = self.bare_git.var('GIT_COMMITTER_IDENT')
674 m = re.compile("^(.*) <([^>]*)> ").match(u)
675 if m:
676 self._userident_name = m.group(1)
677 self._userident_email = m.group(2)
678 else:
679 self._userident_name = ''
680 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700681
682 def GetRemote(self, name):
683 """Get the configuration for a single remote.
684 """
685 return self.config.GetRemote(name)
686
687 def GetBranch(self, name):
688 """Get the configuration for a single branch.
689 """
690 return self.config.GetBranch(name)
691
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700692 def GetBranches(self):
693 """Get all existing local branches.
694 """
695 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900696 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700697 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700698
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530699 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700700 if name.startswith(R_HEADS):
701 name = name[len(R_HEADS):]
702 b = self.GetBranch(name)
703 b.current = name == current
704 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900705 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700706 heads[name] = b
707
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530708 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700709 if name.startswith(R_PUB):
710 name = name[len(R_PUB):]
711 b = heads.get(name)
712 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900713 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700714
715 return heads
716
Colin Cross5acde752012-03-28 20:15:45 -0700717 def MatchesGroups(self, manifest_groups):
718 """Returns true if the manifest groups specified at init should cause
719 this project to be synced.
720 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700721 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700722
723 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700724 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700725 manifest_groups: "-group1,group2"
726 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500727
728 The special manifest group "default" will match any project that
729 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700730 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500731 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700732 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500733 if not 'notdefault' in expanded_project_groups:
734 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700735
Conley Owens971de8e2012-04-16 10:36:08 -0700736 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700737 for group in expanded_manifest_groups:
738 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700739 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700740 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700741 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700742
Conley Owens971de8e2012-04-16 10:36:08 -0700743 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744
745## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700746 def UncommitedFiles(self, get_all=True):
747 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700749 Args:
750 get_all: a boolean, if True - get information about all different
751 uncommitted files. If False - return as soon as any kind of
752 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500753 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700754 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500755 self.work_git.update_index('-q',
756 '--unmerged',
757 '--ignore-missing',
758 '--refresh')
759 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700760 details.append("rebase in progress")
761 if not get_all:
762 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500763
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700764 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
765 if changes:
766 details.extend(changes)
767 if not get_all:
768 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500769
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700770 changes = self.work_git.DiffZ('diff-files').keys()
771 if changes:
772 details.extend(changes)
773 if not get_all:
774 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500775
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700776 changes = self.work_git.LsOthers()
777 if changes:
778 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500779
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700780 return details
781
782 def HasChanges(self):
783 """Returns true if there are uncommitted changes.
784 """
785 if self.UncommitedFiles(get_all=False):
786 return True
787 else:
788 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500789
Terence Haddock4655e812011-03-31 12:33:34 +0200790 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200792
793 Args:
794 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700795 """
796 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200797 if output_redir == None:
798 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700799 print(file=output_redir)
800 print('project %s/' % self.relpath, file=output_redir)
801 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700802 return
803
804 self.work_git.update_index('-q',
805 '--unmerged',
806 '--ignore-missing',
807 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700808 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
810 df = self.work_git.DiffZ('diff-files')
811 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100812 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700813 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814
815 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200816 if not output_redir == None:
817 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700818 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700819
820 branch = self.CurrentBranch
821 if branch is None:
822 out.nobranch('(*** NO BRANCH ***)')
823 else:
824 out.branch('branch %s', branch)
825 out.nl()
826
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700827 if rb:
828 out.important('prior sync failed; rebase still in progress')
829 out.nl()
830
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700831 paths = list()
832 paths.extend(di.keys())
833 paths.extend(df.keys())
834 paths.extend(do)
835
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530836 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900837 try:
838 i = di[p]
839 except KeyError:
840 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900842 try:
843 f = df[p]
844 except KeyError:
845 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200846
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900847 if i:
848 i_status = i.status.upper()
849 else:
850 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700851
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900852 if f:
853 f_status = f.status.lower()
854 else:
855 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700856
857 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800858 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700859 i.src_path, p, i.level)
860 else:
861 line = ' %s%s\t%s' % (i_status, f_status, p)
862
863 if i and not f:
864 out.added('%s', line)
865 elif (i and f) or (not i and f):
866 out.changed('%s', line)
867 elif not i and not f:
868 out.untracked('%s', line)
869 else:
870 out.write('%s', line)
871 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200872
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700873 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874
pelyad67872d2012-03-28 14:49:58 +0300875 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700876 """Prints the status of the repository to stdout.
877 """
878 out = DiffColoring(self.config)
879 cmd = ['diff']
880 if out.is_on:
881 cmd.append('--color')
882 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300883 if absolute_paths:
884 cmd.append('--src-prefix=a/%s/' % self.relpath)
885 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886 cmd.append('--')
887 p = GitCommand(self,
888 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100889 capture_stdout=True,
890 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700891 has_diff = False
892 for line in p.process.stdout:
893 if not has_diff:
894 out.nl()
895 out.project('project %s/' % self.relpath)
896 out.nl()
897 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700898 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899 p.Wait()
900
901
902## Publish / Upload ##
903
David Pursehouse8a68ff92012-09-24 12:15:13 +0900904 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905 """Was the branch published (uploaded) for code review?
906 If so, returns the SHA-1 hash of the last published
907 state for the branch.
908 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700909 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900910 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700911 try:
912 return self.bare_git.rev_parse(key)
913 except GitError:
914 return None
915 else:
916 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900917 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700918 except KeyError:
919 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920
David Pursehouse8a68ff92012-09-24 12:15:13 +0900921 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 """Prunes any stale published refs.
923 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900924 if all_refs is None:
925 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926 heads = set()
927 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530928 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929 if name.startswith(R_HEADS):
930 heads.add(name)
931 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900932 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530934 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700935 n = name[len(R_PUB):]
936 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900937 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700938
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700939 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700940 """List any branches which can be uploaded for review.
941 """
942 heads = {}
943 pubed = {}
944
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530945 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900947 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900949 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950
951 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530952 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900953 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700955 if selected_branch and branch != selected_branch:
956 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800958 rb = self.GetUploadableBranch(branch)
959 if rb:
960 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700961 return ready
962
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800963 def GetUploadableBranch(self, branch_name):
964 """Get a single uploadable branch, or None.
965 """
966 branch = self.GetBranch(branch_name)
967 base = branch.LocalMerge
968 if branch.LocalMerge:
969 rb = ReviewableBranch(self, branch, base)
970 if rb.commits:
971 return rb
972 return None
973
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700974 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100975 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -0700976 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400977 draft=False,
978 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 """Uploads the named branch for code review.
980 """
981 if branch is None:
982 branch = self.CurrentBranch
983 if branch is None:
984 raise GitError('not currently on a branch')
985
986 branch = self.GetBranch(branch)
987 if not branch.LocalMerge:
988 raise GitError('branch %s does not track a remote' % branch.name)
989 if not branch.remote.review:
990 raise GitError('remote %s has no review url' % branch.remote.name)
991
Bryan Jacobsf609f912013-05-06 13:36:24 -0400992 if dest_branch is None:
993 dest_branch = self.dest_branch
994 if dest_branch is None:
995 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700996 if not dest_branch.startswith(R_HEADS):
997 dest_branch = R_HEADS + dest_branch
998
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800999 if not branch.remote.projectname:
1000 branch.remote.projectname = self.name
1001 branch.remote.Save()
1002
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001003 url = branch.remote.ReviewUrl(self.UserEmail)
1004 if url is None:
1005 raise UploadError('review not configured')
1006 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001007
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001008 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001009 rp = ['gerrit receive-pack']
1010 for e in people[0]:
1011 rp.append('--reviewer=%s' % sq(e))
1012 for e in people[1]:
1013 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001014 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001015
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001016 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001017
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001018 if dest_branch.startswith(R_HEADS):
1019 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001020
1021 upload_type = 'for'
1022 if draft:
1023 upload_type = 'drafts'
1024
1025 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1026 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001027 if auto_topic:
1028 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001029 if not url.startswith('ssh://'):
1030 rp = ['r=%s' % p for p in people[0]] + \
1031 ['cc=%s' % p for p in people[1]]
1032 if rp:
1033 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001034 cmd.append(ref_spec)
1035
Anthony King7bdac712014-07-16 12:56:40 +01001036 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001037 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038
1039 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1040 self.bare_git.UpdateRef(R_PUB + branch.name,
1041 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001042 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001043
1044
1045## Sync ##
1046
Julien Campergue335f5ef2013-10-16 11:02:35 +02001047 def _ExtractArchive(self, tarpath, path=None):
1048 """Extract the given tar on its current location
1049
1050 Args:
1051 - tarpath: The path to the actual tar file
1052
1053 """
1054 try:
1055 with tarfile.open(tarpath, 'r') as tar:
1056 tar.extractall(path=path)
1057 return True
1058 except (IOError, tarfile.TarError) as e:
1059 print("error: Cannot extract archive %s: "
1060 "%s" % (tarpath, str(e)), file=sys.stderr)
1061 return False
1062
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001063 def Sync_NetworkHalf(self,
1064 quiet=False,
1065 is_new=None,
1066 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001067 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001068 no_tags=False,
1069 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 """Perform only the network IO portion of the sync process.
1071 Local working directory/branch state is not affected.
1072 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001073 if archive and not isinstance(self, MetaProject):
1074 if self.remote.url.startswith(('http://', 'https://')):
1075 print("error: %s: Cannot fetch archives from http/https "
1076 "remotes." % self.name, file=sys.stderr)
1077 return False
1078
1079 name = self.relpath.replace('\\', '/')
1080 name = name.replace('/', '_')
1081 tarpath = '%s.tar' % name
1082 topdir = self.manifest.topdir
1083
1084 try:
1085 self._FetchArchive(tarpath, cwd=topdir)
1086 except GitError as e:
1087 print('error: %s' % str(e), file=sys.stderr)
1088 return False
1089
1090 # From now on, we only need absolute tarpath
1091 tarpath = os.path.join(topdir, tarpath)
1092
1093 if not self._ExtractArchive(tarpath, path=topdir):
1094 return False
1095 try:
1096 os.remove(tarpath)
1097 except OSError as e:
1098 print("warn: Cannot remove archive %s: "
1099 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001100 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001101 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001102 if is_new is None:
1103 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001104 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001105 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001106 else:
1107 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001109
1110 if is_new:
1111 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1112 try:
1113 fd = open(alt, 'rb')
1114 try:
1115 alt_dir = fd.readline().rstrip()
1116 finally:
1117 fd.close()
1118 except IOError:
1119 alt_dir = None
1120 else:
1121 alt_dir = None
1122
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001123 if clone_bundle \
1124 and alt_dir is None \
1125 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001126 is_new = False
1127
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001128 if not current_branch_only:
1129 if self.sync_c:
1130 current_branch_only = True
1131 elif not self.manifest._loaded:
1132 # Manifest cannot check defaults until it syncs.
1133 current_branch_only = False
1134 elif self.manifest.default.sync_c:
1135 current_branch_only = True
1136
Conley Owens666d5342014-05-01 13:09:57 -07001137 has_sha1 = ID_RE.match(self.revisionExpr) and self._CheckForSha1()
1138 if (not has_sha1 #Need to fetch since we don't already have this revision
1139 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1140 current_branch_only=current_branch_only,
1141 no_tags=no_tags)):
Anthony King7bdac712014-07-16 12:56:40 +01001142 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001143
1144 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001145 self._InitMRef()
1146 else:
1147 self._InitMirrorHead()
1148 try:
1149 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1150 except OSError:
1151 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001152 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001153
1154 def PostRepoUpgrade(self):
1155 self._InitHooks()
1156
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001157 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001158 for copyfile in self.copyfiles:
1159 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001160 for linkfile in self.linkfiles:
1161 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162
Julien Camperguedd654222014-01-09 16:21:37 +01001163 def GetCommitRevisionId(self):
1164 """Get revisionId of a commit.
1165
1166 Use this method instead of GetRevisionId to get the id of the commit rather
1167 than the id of the current git object (for example, a tag)
1168
1169 """
1170 if not self.revisionExpr.startswith(R_TAGS):
1171 return self.GetRevisionId(self._allrefs)
1172
1173 try:
1174 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1175 except GitError:
1176 raise ManifestInvalidRevisionError(
1177 'revision %s in %s not found' % (self.revisionExpr,
1178 self.name))
1179
David Pursehouse8a68ff92012-09-24 12:15:13 +09001180 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001181 if self.revisionId:
1182 return self.revisionId
1183
1184 rem = self.GetRemote(self.remote.name)
1185 rev = rem.ToLocal(self.revisionExpr)
1186
David Pursehouse8a68ff92012-09-24 12:15:13 +09001187 if all_refs is not None and rev in all_refs:
1188 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001189
1190 try:
1191 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1192 except GitError:
1193 raise ManifestInvalidRevisionError(
1194 'revision %s in %s not found' % (self.revisionExpr,
1195 self.name))
1196
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001197 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001198 """Perform only the local IO portion of the sync process.
1199 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200 """
David James8d201162013-10-11 17:03:19 -07001201 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001202 all_refs = self.bare_ref.all
1203 self.CleanPublishedCache(all_refs)
1204 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001205
David Pursehouse1d947b32012-10-25 12:23:11 +09001206 def _doff():
1207 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001208 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001209
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001210 head = self.work_git.GetHead()
1211 if head.startswith(R_HEADS):
1212 branch = head[len(R_HEADS):]
1213 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001214 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001215 except KeyError:
1216 head = None
1217 else:
1218 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001219
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001220 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001221 # Currently on a detached HEAD. The user is assumed to
1222 # not have any local modifications worth worrying about.
1223 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001224 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001225 syncbuf.fail(self, _PriorSyncFailedError())
1226 return
1227
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001228 if head == revid:
1229 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001230 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001231 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001232 if not syncbuf.detach_head:
1233 return
1234 else:
1235 lost = self._revlist(not_rev(revid), HEAD)
1236 if lost:
1237 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001239 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001240 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001241 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001242 syncbuf.fail(self, e)
1243 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001244 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001245 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001246
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001247 if head == revid:
1248 # No changes; don't do anything further.
1249 #
1250 return
1251
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001252 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001253
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001254 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001255 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001256 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001257 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001258 syncbuf.info(self,
1259 "leaving %s; does not track upstream",
1260 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001261 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001262 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001263 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001264 syncbuf.fail(self, e)
1265 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001266 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001267 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001268
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001269 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001270 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001272 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001273 if not_merged:
1274 if upstream_gain:
1275 # The user has published this branch and some of those
1276 # commits are not yet merged upstream. We do not want
1277 # to rewrite the published commits so we punt.
1278 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001279 syncbuf.fail(self,
1280 "branch %s is published (but not merged) and is now %d commits behind"
1281 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001282 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001283 elif pub == head:
1284 # All published commits are merged, and thus we are a
1285 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001286 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001287 syncbuf.later1(self, _doff)
1288 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001289
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001290 # Examine the local commits not in the remote. Find the
1291 # last one attributed to this user, if any.
1292 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001293 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001294 last_mine = None
1295 cnt_mine = 0
1296 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301297 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001298 if committer_email == self.UserEmail:
1299 last_mine = commit_id
1300 cnt_mine += 1
1301
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001302 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001303 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001304
1305 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001306 syncbuf.fail(self, _DirtyError())
1307 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001309 # If the upstream switched on us, warn the user.
1310 #
1311 if branch.merge != self.revisionExpr:
1312 if branch.merge and self.revisionExpr:
1313 syncbuf.info(self,
1314 'manifest switched %s...%s',
1315 branch.merge,
1316 self.revisionExpr)
1317 elif branch.merge:
1318 syncbuf.info(self,
1319 'manifest no longer tracks %s',
1320 branch.merge)
1321
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001322 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001324 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001325 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001326 syncbuf.info(self,
1327 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001328 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001330 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001331 if not ID_RE.match(self.revisionExpr):
1332 # in case of manifest sync the revisionExpr might be a SHA1
1333 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001334 if not branch.merge.startswith('refs/'):
1335 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001336 branch.Save()
1337
Mike Pontillod3153822012-02-28 11:53:24 -08001338 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001339 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001340 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001341 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001342 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001343 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001345 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001346 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001347 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001348 syncbuf.fail(self, e)
1349 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001351 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001352
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001353 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001354 # dest should already be an absolute path, but src is project relative
1355 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001356 abssrc = os.path.join(self.worktree, src)
1357 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001359 def AddLinkFile(self, src, dest, absdest):
1360 # dest should already be an absolute path, but src is project relative
1361 # make src an absolute path
1362 abssrc = os.path.join(self.worktree, src)
1363 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1364
James W. Mills24c13082012-04-12 15:04:13 -05001365 def AddAnnotation(self, name, value, keep):
1366 self.annotations.append(_Annotation(name, value, keep))
1367
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001368 def DownloadPatchSet(self, change_id, patch_id):
1369 """Download a single patch set of a single change to FETCH_HEAD.
1370 """
1371 remote = self.GetRemote(self.remote.name)
1372
1373 cmd = ['fetch', remote.name]
1374 cmd.append('refs/changes/%2.2d/%d/%d' \
1375 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001376 if GitCommand(self, cmd, bare=True).Wait() != 0:
1377 return None
1378 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001379 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001380 change_id,
1381 patch_id,
1382 self.bare_git.rev_parse('FETCH_HEAD'))
1383
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001384
1385## Branch Management ##
1386
1387 def StartBranch(self, name):
1388 """Create a new branch off the manifest's revision.
1389 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001390 head = self.work_git.GetHead()
1391 if head == (R_HEADS + name):
1392 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393
David Pursehouse8a68ff92012-09-24 12:15:13 +09001394 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001395 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001396 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001397 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001398 capture_stdout=True,
1399 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001400
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001401 branch = self.GetBranch(name)
1402 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001403 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001404 if not branch.merge.startswith('refs/'):
1405 branch.merge = R_HEADS + self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001406 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001407
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001408 if head.startswith(R_HEADS):
1409 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001410 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001411 except KeyError:
1412 head = None
1413
1414 if revid and head and revid == head:
1415 ref = os.path.join(self.gitdir, R_HEADS + name)
1416 try:
1417 os.makedirs(os.path.dirname(ref))
1418 except OSError:
1419 pass
1420 _lwrite(ref, '%s\n' % revid)
1421 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1422 'ref: %s%s\n' % (R_HEADS, name))
1423 branch.Save()
1424 return True
1425
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001426 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001427 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001428 capture_stdout=True,
1429 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001430 branch.Save()
1431 return True
1432 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001433
Wink Saville02d79452009-04-10 13:01:24 -07001434 def CheckoutBranch(self, name):
1435 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001436
1437 Args:
1438 name: The name of the branch to checkout.
1439
1440 Returns:
1441 True if the checkout succeeded; False if it didn't; None if the branch
1442 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001443 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001444 rev = R_HEADS + name
1445 head = self.work_git.GetHead()
1446 if head == rev:
1447 # Already on the branch
1448 #
1449 return True
Wink Saville02d79452009-04-10 13:01:24 -07001450
David Pursehouse8a68ff92012-09-24 12:15:13 +09001451 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001452 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001453 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001454 except KeyError:
1455 # Branch does not exist in this project
1456 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001457 return None
Wink Saville02d79452009-04-10 13:01:24 -07001458
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001459 if head.startswith(R_HEADS):
1460 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001461 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001462 except KeyError:
1463 head = None
1464
1465 if head == revid:
1466 # Same revision; just update HEAD to point to the new
1467 # target branch, but otherwise take no other action.
1468 #
1469 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1470 'ref: %s%s\n' % (R_HEADS, name))
1471 return True
1472
1473 return GitCommand(self,
1474 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001475 capture_stdout=True,
1476 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001477
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001478 def AbandonBranch(self, name):
1479 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001480
1481 Args:
1482 name: The name of the branch to abandon.
1483
1484 Returns:
1485 True if the abandon succeeded; False if it didn't; None if the branch
1486 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001487 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001488 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001489 all_refs = self.bare_ref.all
1490 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001491 # Doesn't exist
1492 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001493
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001494 head = self.work_git.GetHead()
1495 if head == rev:
1496 # We can't destroy the branch while we are sitting
1497 # on it. Switch to a detached HEAD.
1498 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001499 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001500
David Pursehouse8a68ff92012-09-24 12:15:13 +09001501 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001502 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001503 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1504 '%s\n' % revid)
1505 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001506 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001507
1508 return GitCommand(self,
1509 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001510 capture_stdout=True,
1511 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001512
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001513 def PruneHeads(self):
1514 """Prune any topic branches already merged into upstream.
1515 """
1516 cb = self.CurrentBranch
1517 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001518 left = self._allrefs
1519 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001520 if name.startswith(R_HEADS):
1521 name = name[len(R_HEADS):]
1522 if cb is None or name != cb:
1523 kill.append(name)
1524
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001525 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001526 if cb is not None \
1527 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001528 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001529 self.work_git.DetachHead(HEAD)
1530 kill.append(cb)
1531
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001532 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001533 old = self.bare_git.GetHead()
1534 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001535 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1536
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001537 try:
1538 self.bare_git.DetachHead(rev)
1539
1540 b = ['branch', '-d']
1541 b.extend(kill)
1542 b = GitCommand(self, b, bare=True,
1543 capture_stdout=True,
1544 capture_stderr=True)
1545 b.Wait()
1546 finally:
1547 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001548 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001549
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001550 for branch in kill:
1551 if (R_HEADS + branch) not in left:
1552 self.CleanPublishedCache()
1553 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001554
1555 if cb and cb not in kill:
1556 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001557 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001558
1559 kept = []
1560 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001561 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001562 branch = self.GetBranch(branch)
1563 base = branch.LocalMerge
1564 if not base:
1565 base = rev
1566 kept.append(ReviewableBranch(self, branch, base))
1567 return kept
1568
1569
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001570## Submodule Management ##
1571
1572 def GetRegisteredSubprojects(self):
1573 result = []
1574 def rec(subprojects):
1575 if not subprojects:
1576 return
1577 result.extend(subprojects)
1578 for p in subprojects:
1579 rec(p.subprojects)
1580 rec(self.subprojects)
1581 return result
1582
1583 def _GetSubmodules(self):
1584 # Unfortunately we cannot call `git submodule status --recursive` here
1585 # because the working tree might not exist yet, and it cannot be used
1586 # without a working tree in its current implementation.
1587
1588 def get_submodules(gitdir, rev):
1589 # Parse .gitmodules for submodule sub_paths and sub_urls
1590 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1591 if not sub_paths:
1592 return []
1593 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1594 # revision of submodule repository
1595 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1596 submodules = []
1597 for sub_path, sub_url in zip(sub_paths, sub_urls):
1598 try:
1599 sub_rev = sub_revs[sub_path]
1600 except KeyError:
1601 # Ignore non-exist submodules
1602 continue
1603 submodules.append((sub_rev, sub_path, sub_url))
1604 return submodules
1605
1606 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1607 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1608 def parse_gitmodules(gitdir, rev):
1609 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1610 try:
Anthony King7bdac712014-07-16 12:56:40 +01001611 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1612 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001613 except GitError:
1614 return [], []
1615 if p.Wait() != 0:
1616 return [], []
1617
1618 gitmodules_lines = []
1619 fd, temp_gitmodules_path = tempfile.mkstemp()
1620 try:
1621 os.write(fd, p.stdout)
1622 os.close(fd)
1623 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001624 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1625 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001626 if p.Wait() != 0:
1627 return [], []
1628 gitmodules_lines = p.stdout.split('\n')
1629 except GitError:
1630 return [], []
1631 finally:
1632 os.remove(temp_gitmodules_path)
1633
1634 names = set()
1635 paths = {}
1636 urls = {}
1637 for line in gitmodules_lines:
1638 if not line:
1639 continue
1640 m = re_path.match(line)
1641 if m:
1642 names.add(m.group(1))
1643 paths[m.group(1)] = m.group(2)
1644 continue
1645 m = re_url.match(line)
1646 if m:
1647 names.add(m.group(1))
1648 urls[m.group(1)] = m.group(2)
1649 continue
1650 names = sorted(names)
1651 return ([paths.get(name, '') for name in names],
1652 [urls.get(name, '') for name in names])
1653
1654 def git_ls_tree(gitdir, rev, paths):
1655 cmd = ['ls-tree', rev, '--']
1656 cmd.extend(paths)
1657 try:
Anthony King7bdac712014-07-16 12:56:40 +01001658 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1659 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001660 except GitError:
1661 return []
1662 if p.Wait() != 0:
1663 return []
1664 objects = {}
1665 for line in p.stdout.split('\n'):
1666 if not line.strip():
1667 continue
1668 object_rev, object_path = line.split()[2:4]
1669 objects[object_path] = object_rev
1670 return objects
1671
1672 try:
1673 rev = self.GetRevisionId()
1674 except GitError:
1675 return []
1676 return get_submodules(self.gitdir, rev)
1677
1678 def GetDerivedSubprojects(self):
1679 result = []
1680 if not self.Exists:
1681 # If git repo does not exist yet, querying its submodules will
1682 # mess up its states; so return here.
1683 return result
1684 for rev, path, url in self._GetSubmodules():
1685 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001686 relpath, worktree, gitdir, objdir = \
1687 self.manifest.GetSubprojectPaths(self, name, path)
1688 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001689 if project:
1690 result.extend(project.GetDerivedSubprojects())
1691 continue
David James8d201162013-10-11 17:03:19 -07001692
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001693 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001694 url=url,
1695 review=self.remote.review,
1696 revision=self.remote.revision)
1697 subproject = Project(manifest=self.manifest,
1698 name=name,
1699 remote=remote,
1700 gitdir=gitdir,
1701 objdir=objdir,
1702 worktree=worktree,
1703 relpath=relpath,
1704 revisionExpr=self.revisionExpr,
1705 revisionId=rev,
1706 rebase=self.rebase,
1707 groups=self.groups,
1708 sync_c=self.sync_c,
1709 sync_s=self.sync_s,
1710 parent=self,
1711 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001712 result.append(subproject)
1713 result.extend(subproject.GetDerivedSubprojects())
1714 return result
1715
1716
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001717## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001718 def _CheckForSha1(self):
1719 try:
1720 # if revision (sha or tag) is not present then following function
1721 # throws an error.
1722 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1723 return True
1724 except GitError:
1725 # There is no such persistent revision. We have to fetch it.
1726 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001727
Julien Campergue335f5ef2013-10-16 11:02:35 +02001728 def _FetchArchive(self, tarpath, cwd=None):
1729 cmd = ['archive', '-v', '-o', tarpath]
1730 cmd.append('--remote=%s' % self.remote.url)
1731 cmd.append('--prefix=%s/' % self.relpath)
1732 cmd.append(self.revisionExpr)
1733
1734 command = GitCommand(self, cmd, cwd=cwd,
1735 capture_stdout=True,
1736 capture_stderr=True)
1737
1738 if command.Wait() != 0:
1739 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1740
Conley Owens80b87fe2014-05-09 17:13:44 -07001741
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001742 def _RemoteFetch(self, name=None,
1743 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001744 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001745 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001746 alt_dir=None,
1747 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001748
1749 is_sha1 = False
1750 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001751 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001752
David Pursehouse9bc422f2014-04-15 10:28:56 +09001753 # The depth should not be used when fetching to a mirror because
1754 # it will result in a shallow repository that cannot be cloned or
1755 # fetched from.
1756 if not self.manifest.IsMirror:
1757 if self.clone_depth:
1758 depth = self.clone_depth
1759 else:
1760 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001761 # The repo project should never be synced with partial depth
1762 if self.relpath == '.repo/repo':
1763 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001764
Shawn Pearce69e04d82014-01-29 12:48:54 -08001765 if depth:
1766 current_branch_only = True
1767
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001768 if ID_RE.match(self.revisionExpr) is not None:
1769 is_sha1 = True
1770
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001771 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001772 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001773 # this is a tag and its sha1 value should never change
1774 tag_name = self.revisionExpr[len(R_TAGS):]
1775
1776 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001777 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001778 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001779 if is_sha1 and not depth:
1780 # When syncing a specific commit and --depth is not set:
1781 # * if upstream is explicitly specified and is not a sha1, fetch only
1782 # upstream as users expect only upstream to be fetch.
1783 # Note: The commit might not be in upstream in which case the sync
1784 # will fail.
1785 # * otherwise, fetch all branches to make sure we end up with the
1786 # specific commit.
1787 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001788
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001789 if not name:
1790 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001791
1792 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001793 remote = self.GetRemote(name)
1794 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001795 ssh_proxy = True
1796
Shawn O. Pearce88443382010-10-08 10:02:09 +02001797 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001798 if alt_dir and 'objects' == os.path.basename(alt_dir):
1799 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001800 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1801 remote = self.GetRemote(name)
1802
David Pursehouse8a68ff92012-09-24 12:15:13 +09001803 all_refs = self.bare_ref.all
1804 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001805 tmp = set()
1806
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301807 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001808 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001809 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001810 all_refs[r] = ref_id
1811 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001812 continue
1813
David Pursehouse8a68ff92012-09-24 12:15:13 +09001814 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001815 continue
1816
David Pursehouse8a68ff92012-09-24 12:15:13 +09001817 r = 'refs/_alt/%s' % ref_id
1818 all_refs[r] = ref_id
1819 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001820 tmp.add(r)
1821
Shawn O. Pearce88443382010-10-08 10:02:09 +02001822 tmp_packed = ''
1823 old_packed = ''
1824
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301825 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001826 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001827 tmp_packed += line
1828 if r not in tmp:
1829 old_packed += line
1830
1831 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001832 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001833 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001834
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001835 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001836
Conley Owensf97e8382015-01-21 11:12:46 -08001837 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001838 cmd.append('--depth=%s' % depth)
1839
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001840 if quiet:
1841 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001842 if not self.worktree:
1843 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001844 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001845
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001846 # If using depth then we should not get all the tags since they may
1847 # be outside of the depth.
1848 if no_tags or depth:
1849 cmd.append('--no-tags')
1850 else:
1851 cmd.append('--tags')
1852
Conley Owens80b87fe2014-05-09 17:13:44 -07001853 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001854 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001855 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001856 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001857 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001858 spec.append('tag')
1859 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001860
David Pursehouse403b64e2015-04-27 10:41:33 +09001861 if not self.manifest.IsMirror:
1862 branch = self.revisionExpr
1863 if is_sha1 and depth:
1864 # Shallow checkout of a specific commit, fetch from that commit and not
1865 # the heads only as the commit might be deeper in the history.
1866 spec.append(branch)
1867 else:
1868 if is_sha1:
1869 branch = self.upstream
1870 if branch is not None and branch.strip():
1871 if not branch.startswith('refs/'):
1872 branch = R_HEADS + branch
1873 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001874 cmd.extend(spec)
1875
1876 shallowfetch = self.config.GetString('repo.shallowfetch')
1877 if shallowfetch and shallowfetch != ' '.join(spec):
Anthony King23ff7df2015-03-28 19:42:39 +00001878 GitCommand(self, ['fetch', '--depth=2147483647', name]
1879 + shallowfetch.split(),
Conley Owens80b87fe2014-05-09 17:13:44 -07001880 bare=True, ssh_proxy=ssh_proxy).Wait()
1881 if depth:
Anthony King7bdac712014-07-16 12:56:40 +01001882 self.config.SetString('repo.shallowfetch', ' '.join(spec))
Conley Owens80b87fe2014-05-09 17:13:44 -07001883 else:
Anthony King7bdac712014-07-16 12:56:40 +01001884 self.config.SetString('repo.shallowfetch', None)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001885
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001886 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001887 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001888 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001889 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001890 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001891 ok = True
1892 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001893 # If needed, run the 'git remote prune' the first time through the loop
1894 elif (not _i and
1895 "error:" in gitcmd.stderr and
1896 "git remote prune" in gitcmd.stderr):
1897 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001898 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001899 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001900 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001901 break
1902 continue
Brian Harring14a66742012-09-28 20:21:57 -07001903 elif current_branch_only and is_sha1 and ret == 128:
1904 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1905 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1906 # abort the optimization attempt and do a full sync.
1907 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001908 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001909
1910 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001911 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001912 if old_packed != '':
1913 _lwrite(packed_refs, old_packed)
1914 else:
1915 os.remove(packed_refs)
1916 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001917
1918 if is_sha1 and current_branch_only and self.upstream:
1919 # We just synced the upstream given branch; verify we
1920 # got what we wanted, else trigger a second run of all
1921 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001922 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001923 return self._RemoteFetch(name=name, current_branch_only=False,
1924 initial=False, quiet=quiet, alt_dir=alt_dir)
1925
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001926 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001927
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001928 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001929 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001930 return False
1931
1932 remote = self.GetRemote(self.remote.name)
1933 bundle_url = remote.url + '/clone.bundle'
1934 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001935 if GetSchemeFromUrl(bundle_url) not in (
1936 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001937 return False
1938
1939 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1940 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1941
1942 exist_dst = os.path.exists(bundle_dst)
1943 exist_tmp = os.path.exists(bundle_tmp)
1944
1945 if not initial and not exist_dst and not exist_tmp:
1946 return False
1947
1948 if not exist_dst:
1949 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1950 if not exist_dst:
1951 return False
1952
1953 cmd = ['fetch']
1954 if quiet:
1955 cmd.append('--quiet')
1956 if not self.worktree:
1957 cmd.append('--update-head-ok')
1958 cmd.append(bundle_dst)
1959 for f in remote.fetch:
1960 cmd.append(str(f))
1961 cmd.append('refs/tags/*:refs/tags/*')
1962
1963 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001964 if os.path.exists(bundle_dst):
1965 os.remove(bundle_dst)
1966 if os.path.exists(bundle_tmp):
1967 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001968 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001969
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001970 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001971 if os.path.exists(dstPath):
1972 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001973
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001974 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001975 if quiet:
1976 cmd += ['--silent']
1977 if os.path.exists(tmpPath):
1978 size = os.stat(tmpPath).st_size
1979 if size >= 1024:
1980 cmd += ['--continue-at', '%d' % (size,)]
1981 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001982 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001983 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1984 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz497bde42015-01-02 13:58:05 -08001985 with self._GetBundleCookieFile(srcUrl, quiet) as cookiefile:
Dave Borowitz137d0132015-01-02 11:12:54 -08001986 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08001987 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08001988 if srcUrl.startswith('persistent-'):
1989 srcUrl = srcUrl[len('persistent-'):]
1990 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001991
Dave Borowitz137d0132015-01-02 11:12:54 -08001992 if IsTrace():
1993 Trace('%s', ' '.join(cmd))
1994 try:
1995 proc = subprocess.Popen(cmd)
1996 except OSError:
1997 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001998
Dave Borowitz137d0132015-01-02 11:12:54 -08001999 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002000
Dave Borowitz137d0132015-01-02 11:12:54 -08002001 if curlret == 22:
2002 # From curl man page:
2003 # 22: HTTP page not retrieved. The requested url was not found or
2004 # returned another error with the HTTP error code being 400 or above.
2005 # This return code only appears if -f, --fail is used.
2006 if not quiet:
2007 print("Server does not provide clone.bundle; ignoring.",
2008 file=sys.stderr)
2009 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002010
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002011 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002012 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002013 os.rename(tmpPath, dstPath)
2014 return True
2015 else:
2016 os.remove(tmpPath)
2017 return False
2018 else:
2019 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002020
Kris Giesingc8d882a2014-12-23 13:02:32 -08002021 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002022 try:
2023 with open(path) as f:
2024 if f.read(16) == '# v2 git bundle\n':
2025 return True
2026 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002027 if not quiet:
2028 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002029 return False
2030 except OSError:
2031 return False
2032
Dave Borowitz137d0132015-01-02 11:12:54 -08002033 @contextlib.contextmanager
Dave Borowitz497bde42015-01-02 13:58:05 -08002034 def _GetBundleCookieFile(self, url, quiet):
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002035 if url.startswith('persistent-'):
2036 try:
2037 p = subprocess.Popen(
2038 ['git-remote-persistent-https', '-print_config', url],
2039 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2040 stderr=subprocess.PIPE)
Dave Borowitz137d0132015-01-02 11:12:54 -08002041 try:
2042 prefix = 'http.cookiefile='
2043 cookiefile = None
2044 for line in p.stdout:
2045 line = line.strip()
2046 if line.startswith(prefix):
2047 cookiefile = line[len(prefix):]
2048 break
2049 # Leave subprocess open, as cookie file may be transient.
2050 if cookiefile:
2051 yield cookiefile
2052 return
2053 finally:
2054 p.stdin.close()
2055 if p.wait():
2056 err_msg = p.stderr.read()
2057 if ' -print_config' in err_msg:
2058 pass # Persistent proxy doesn't support -print_config.
Dave Borowitz497bde42015-01-02 13:58:05 -08002059 elif not quiet:
Dave Borowitz137d0132015-01-02 11:12:54 -08002060 print(err_msg, file=sys.stderr)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002061 except OSError as e:
2062 if e.errno == errno.ENOENT:
2063 pass # No persistent proxy.
2064 raise
Dave Borowitz137d0132015-01-02 11:12:54 -08002065 yield GitConfig.ForUser().GetString('http.cookiefile')
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002066
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002067 def _Checkout(self, rev, quiet=False):
2068 cmd = ['checkout']
2069 if quiet:
2070 cmd.append('-q')
2071 cmd.append(rev)
2072 cmd.append('--')
2073 if GitCommand(self, cmd).Wait() != 0:
2074 if self._allrefs:
2075 raise GitError('%s checkout %s ' % (self.name, rev))
2076
Anthony King7bdac712014-07-16 12:56:40 +01002077 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002078 cmd = ['cherry-pick']
2079 cmd.append(rev)
2080 cmd.append('--')
2081 if GitCommand(self, cmd).Wait() != 0:
2082 if self._allrefs:
2083 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2084
Anthony King7bdac712014-07-16 12:56:40 +01002085 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002086 cmd = ['revert']
2087 cmd.append('--no-edit')
2088 cmd.append(rev)
2089 cmd.append('--')
2090 if GitCommand(self, cmd).Wait() != 0:
2091 if self._allrefs:
2092 raise GitError('%s revert %s ' % (self.name, rev))
2093
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002094 def _ResetHard(self, rev, quiet=True):
2095 cmd = ['reset', '--hard']
2096 if quiet:
2097 cmd.append('-q')
2098 cmd.append(rev)
2099 if GitCommand(self, cmd).Wait() != 0:
2100 raise GitError('%s reset --hard %s ' % (self.name, rev))
2101
Anthony King7bdac712014-07-16 12:56:40 +01002102 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002103 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002104 if onto is not None:
2105 cmd.extend(['--onto', onto])
2106 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002107 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002108 raise GitError('%s rebase %s ' % (self.name, upstream))
2109
Pierre Tardy3d125942012-05-04 12:18:12 +02002110 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002111 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002112 if ffonly:
2113 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002114 if GitCommand(self, cmd).Wait() != 0:
2115 raise GitError('%s merge %s ' % (self.name, head))
2116
Jonathan Nieder93719792015-03-17 11:29:58 -07002117 def _InitGitDir(self, mirror_git=None):
Kevin Degi384b3c52014-10-16 16:02:58 -06002118 init_git_dir = not os.path.exists(self.gitdir)
2119 init_obj_dir = not os.path.exists(self.objdir)
2120 # Initialize the bare repository, which contains all of the objects.
2121 if init_obj_dir:
2122 os.makedirs(self.objdir)
2123 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002124
Kevin Degi384b3c52014-10-16 16:02:58 -06002125 # If we have a separate directory to hold refs, initialize it as well.
2126 if self.objdir != self.gitdir:
2127 if init_git_dir:
David James8d201162013-10-11 17:03:19 -07002128 os.makedirs(self.gitdir)
Kevin Degi384b3c52014-10-16 16:02:58 -06002129
2130 if init_obj_dir or init_git_dir:
David James8d201162013-10-11 17:03:19 -07002131 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2132 copy_all=True)
Kevin Degi384b3c52014-10-16 16:02:58 -06002133 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002134
Kevin Degi384b3c52014-10-16 16:02:58 -06002135 if init_git_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002136 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002137 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002138
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002139 if ref_dir or mirror_git:
2140 if not mirror_git:
2141 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002142 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2143 self.relpath + '.git')
2144
2145 if os.path.exists(mirror_git):
2146 ref_dir = mirror_git
2147
2148 elif os.path.exists(repo_git):
2149 ref_dir = repo_git
2150
2151 else:
2152 ref_dir = None
2153
2154 if ref_dir:
2155 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2156 os.path.join(ref_dir, 'objects') + '\n')
2157
Jimmie Westera0444582012-10-24 13:44:42 +02002158 self._UpdateHooks()
2159
2160 m = self.manifest.manifestProject.config
2161 for key in ['user.name', 'user.email']:
Anthony King7bdac712014-07-16 12:56:40 +01002162 if m.Has(key, include_defaults=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002163 self.config.SetString(key, m.GetString(key))
Jonathan Nieder93719792015-03-17 11:29:58 -07002164 if self.manifest.IsMirror:
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002165 self.config.SetString('core.bare', 'true')
2166 else:
2167 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002168
Jimmie Westera0444582012-10-24 13:44:42 +02002169 def _UpdateHooks(self):
2170 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002171 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002172
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002173 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002174 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002175 if not os.path.exists(hooks):
2176 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002177 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002178 name = os.path.basename(stock_hook)
2179
Victor Boivie65e0f352011-04-18 11:23:29 +02002180 if name in ('commit-msg',) and not self.remote.review \
2181 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002182 # Don't install a Gerrit Code Review hook if this
2183 # project does not appear to use it for reviews.
2184 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002185 # Since the manifest project is one of those, but also
2186 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002187 continue
2188
2189 dst = os.path.join(hooks, name)
2190 if os.path.islink(dst):
2191 continue
2192 if os.path.exists(dst):
2193 if filecmp.cmp(stock_hook, dst, shallow=False):
2194 os.remove(dst)
2195 else:
2196 _error("%s: Not replacing %s hook", self.relpath, name)
2197 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002198 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002199 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002200 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002201 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002202 raise GitError('filesystem must support symlinks')
2203 else:
2204 raise
2205
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002206 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002207 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002208 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002209 remote.url = self.remote.url
2210 remote.review = self.remote.review
2211 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002212
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002213 if self.worktree:
2214 remote.ResetFetch(mirror=False)
2215 else:
2216 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002217 remote.Save()
2218
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002219 def _InitMRef(self):
2220 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002221 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002222
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002223 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002224 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002225
2226 def _InitAnyMRef(self, ref):
2227 cur = self.bare_ref.symref(ref)
2228
2229 if self.revisionId:
2230 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2231 msg = 'manifest set to %s' % self.revisionId
2232 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002233 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002234 else:
2235 remote = self.GetRemote(self.remote.name)
2236 dst = remote.ToLocal(self.revisionExpr)
2237 if cur != dst:
2238 msg = 'manifest set to %s' % self.revisionExpr
2239 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002240
Kevin Degi384b3c52014-10-16 16:02:58 -06002241 def _CheckDirReference(self, srcdir, destdir, share_refs):
2242 symlink_files = self.shareable_files
2243 symlink_dirs = self.shareable_dirs
2244 if share_refs:
2245 symlink_files += self.working_tree_files
2246 symlink_dirs += self.working_tree_dirs
2247 to_symlink = symlink_files + symlink_dirs
2248 for name in set(to_symlink):
2249 dst = os.path.realpath(os.path.join(destdir, name))
2250 if os.path.lexists(dst):
2251 src = os.path.realpath(os.path.join(srcdir, name))
2252 # Fail if the links are pointing to the wrong place
2253 if src != dst:
2254 raise GitError('cannot overwrite a local work tree')
2255
David James8d201162013-10-11 17:03:19 -07002256 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2257 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2258
2259 Args:
2260 gitdir: The bare git repository. Must already be initialized.
2261 dotgit: The repository you would like to initialize.
2262 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2263 Only one work tree can store refs under a given |gitdir|.
2264 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2265 This saves you the effort of initializing |dotgit| yourself.
2266 """
Kevin Degi384b3c52014-10-16 16:02:58 -06002267 symlink_files = self.shareable_files
2268 symlink_dirs = self.shareable_dirs
David James8d201162013-10-11 17:03:19 -07002269 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002270 symlink_files += self.working_tree_files
2271 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002272 to_symlink = symlink_files + symlink_dirs
2273
2274 to_copy = []
2275 if copy_all:
2276 to_copy = os.listdir(gitdir)
2277
2278 for name in set(to_copy).union(to_symlink):
2279 try:
2280 src = os.path.realpath(os.path.join(gitdir, name))
2281 dst = os.path.realpath(os.path.join(dotgit, name))
2282
Kevin Degi384b3c52014-10-16 16:02:58 -06002283 if os.path.lexists(dst):
2284 continue
David James8d201162013-10-11 17:03:19 -07002285
2286 # If the source dir doesn't exist, create an empty dir.
2287 if name in symlink_dirs and not os.path.lexists(src):
2288 os.makedirs(src)
2289
Conley Owens80b87fe2014-05-09 17:13:44 -07002290 # If the source file doesn't exist, ensure the destination
2291 # file doesn't either.
2292 if name in symlink_files and not os.path.lexists(src):
2293 try:
2294 os.remove(dst)
2295 except OSError:
2296 pass
2297
David James8d201162013-10-11 17:03:19 -07002298 if name in to_symlink:
2299 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2300 elif copy_all and not os.path.islink(dst):
2301 if os.path.isdir(src):
2302 shutil.copytree(src, dst)
2303 elif os.path.isfile(src):
2304 shutil.copy(src, dst)
2305 except OSError as e:
2306 if e.errno == errno.EPERM:
2307 raise GitError('filesystem must support symlinks')
2308 else:
2309 raise
2310
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002311 def _InitWorkTree(self):
2312 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002313 init_dotgit = not os.path.exists(dotgit)
2314 if init_dotgit:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002315 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002316 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2317 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002318
Kevin Degi384b3c52014-10-16 16:02:58 -06002319 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2320
2321 if init_dotgit:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002322 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002323
2324 cmd = ['read-tree', '--reset', '-u']
2325 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002326 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002327 if GitCommand(self, cmd).Wait() != 0:
2328 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002329
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002330 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002331
2332 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002333 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002334
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002335 def _revlist(self, *args, **kw):
2336 a = []
2337 a.extend(args)
2338 a.append('--')
2339 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002340
2341 @property
2342 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002343 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002344
Julien Camperguedd654222014-01-09 16:21:37 +01002345 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2346 """Get logs between two revisions of this project."""
2347 comp = '..'
2348 if rev1:
2349 revs = [rev1]
2350 if rev2:
2351 revs.extend([comp, rev2])
2352 cmd = ['log', ''.join(revs)]
2353 out = DiffColoring(self.config)
2354 if out.is_on and color:
2355 cmd.append('--color')
2356 if oneline:
2357 cmd.append('--oneline')
2358
2359 try:
2360 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2361 if log.Wait() == 0:
2362 return log.stdout
2363 except GitError:
2364 # worktree may not exist if groups changed for example. In that case,
2365 # try in gitdir instead.
2366 if not os.path.exists(self.worktree):
2367 return self.bare_git.log(*cmd[1:])
2368 else:
2369 raise
2370 return None
2371
2372 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2373 """Get the list of logs from this revision to given revisionId"""
2374 logs = {}
2375 selfId = self.GetRevisionId(self._allrefs)
2376 toId = toProject.GetRevisionId(toProject._allrefs)
2377
2378 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2379 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2380 return logs
2381
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002382 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002383 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002384 self._project = project
2385 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002386 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002387
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002388 def LsOthers(self):
2389 p = GitCommand(self._project,
2390 ['ls-files',
2391 '-z',
2392 '--others',
2393 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002394 bare=False,
David James8d201162013-10-11 17:03:19 -07002395 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002396 capture_stdout=True,
2397 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002398 if p.Wait() == 0:
2399 out = p.stdout
2400 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002401 return out[:-1].split('\0') # pylint: disable=W1401
2402 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002403 return []
2404
2405 def DiffZ(self, name, *args):
2406 cmd = [name]
2407 cmd.append('-z')
2408 cmd.extend(args)
2409 p = GitCommand(self._project,
2410 cmd,
David James8d201162013-10-11 17:03:19 -07002411 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002412 bare=False,
2413 capture_stdout=True,
2414 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002415 try:
2416 out = p.process.stdout.read()
2417 r = {}
2418 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002419 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002420 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002421 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002422 info = next(out)
2423 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002424 except StopIteration:
2425 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002426
2427 class _Info(object):
2428 def __init__(self, path, omode, nmode, oid, nid, state):
2429 self.path = path
2430 self.src_path = None
2431 self.old_mode = omode
2432 self.new_mode = nmode
2433 self.old_id = oid
2434 self.new_id = nid
2435
2436 if len(state) == 1:
2437 self.status = state
2438 self.level = None
2439 else:
2440 self.status = state[:1]
2441 self.level = state[1:]
2442 while self.level.startswith('0'):
2443 self.level = self.level[1:]
2444
2445 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002446 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002447 if info.status in ('R', 'C'):
2448 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002449 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002450 r[info.path] = info
2451 return r
2452 finally:
2453 p.Wait()
2454
2455 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002456 if self._bare:
2457 path = os.path.join(self._project.gitdir, HEAD)
2458 else:
2459 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002460 try:
2461 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002462 except IOError as e:
2463 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002464 try:
2465 line = fd.read()
2466 finally:
2467 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302468 try:
2469 line = line.decode()
2470 except AttributeError:
2471 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002472 if line.startswith('ref: '):
2473 return line[5:-1]
2474 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002475
2476 def SetHead(self, ref, message=None):
2477 cmdv = []
2478 if message is not None:
2479 cmdv.extend(['-m', message])
2480 cmdv.append(HEAD)
2481 cmdv.append(ref)
2482 self.symbolic_ref(*cmdv)
2483
2484 def DetachHead(self, new, message=None):
2485 cmdv = ['--no-deref']
2486 if message is not None:
2487 cmdv.extend(['-m', message])
2488 cmdv.append(HEAD)
2489 cmdv.append(new)
2490 self.update_ref(*cmdv)
2491
2492 def UpdateRef(self, name, new, old=None,
2493 message=None,
2494 detach=False):
2495 cmdv = []
2496 if message is not None:
2497 cmdv.extend(['-m', message])
2498 if detach:
2499 cmdv.append('--no-deref')
2500 cmdv.append(name)
2501 cmdv.append(new)
2502 if old is not None:
2503 cmdv.append(old)
2504 self.update_ref(*cmdv)
2505
2506 def DeleteRef(self, name, old=None):
2507 if not old:
2508 old = self.rev_parse(name)
2509 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002510 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002511
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002512 def rev_list(self, *args, **kw):
2513 if 'format' in kw:
2514 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2515 else:
2516 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002517 cmdv.extend(args)
2518 p = GitCommand(self._project,
2519 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002520 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002521 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002522 capture_stdout=True,
2523 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002524 r = []
2525 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002526 if line[-1] == '\n':
2527 line = line[:-1]
2528 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002529 if p.Wait() != 0:
2530 raise GitError('%s rev-list %s: %s' % (
2531 self._project.name,
2532 str(args),
2533 p.stderr))
2534 return r
2535
2536 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002537 """Allow arbitrary git commands using pythonic syntax.
2538
2539 This allows you to do things like:
2540 git_obj.rev_parse('HEAD')
2541
2542 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2543 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002544 Any other positional arguments will be passed to the git command, and the
2545 following keyword arguments are supported:
2546 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002547
2548 Args:
2549 name: The name of the git command to call. Any '_' characters will
2550 be replaced with '-'.
2551
2552 Returns:
2553 A callable object that will try to call git with the named command.
2554 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002555 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002556 def runner(*args, **kwargs):
2557 cmdv = []
2558 config = kwargs.pop('config', None)
2559 for k in kwargs:
2560 raise TypeError('%s() got an unexpected keyword argument %r'
2561 % (name, k))
2562 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002563 if not git_require((1, 7, 2)):
2564 raise ValueError('cannot set config on command line for %s()'
2565 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302566 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002567 cmdv.append('-c')
2568 cmdv.append('%s=%s' % (k, v))
2569 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002570 cmdv.extend(args)
2571 p = GitCommand(self._project,
2572 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002573 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002574 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002575 capture_stdout=True,
2576 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002577 if p.Wait() != 0:
2578 raise GitError('%s %s: %s' % (
2579 self._project.name,
2580 name,
2581 p.stderr))
2582 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302583 try:
Conley Owensedd01512013-09-26 12:59:58 -07002584 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302585 except AttributeError:
2586 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002587 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2588 return r[:-1]
2589 return r
2590 return runner
2591
2592
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002593class _PriorSyncFailedError(Exception):
2594 def __str__(self):
2595 return 'prior sync failed; rebase still in progress'
2596
2597class _DirtyError(Exception):
2598 def __str__(self):
2599 return 'contains uncommitted changes'
2600
2601class _InfoMessage(object):
2602 def __init__(self, project, text):
2603 self.project = project
2604 self.text = text
2605
2606 def Print(self, syncbuf):
2607 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2608 syncbuf.out.nl()
2609
2610class _Failure(object):
2611 def __init__(self, project, why):
2612 self.project = project
2613 self.why = why
2614
2615 def Print(self, syncbuf):
2616 syncbuf.out.fail('error: %s/: %s',
2617 self.project.relpath,
2618 str(self.why))
2619 syncbuf.out.nl()
2620
2621class _Later(object):
2622 def __init__(self, project, action):
2623 self.project = project
2624 self.action = action
2625
2626 def Run(self, syncbuf):
2627 out = syncbuf.out
2628 out.project('project %s/', self.project.relpath)
2629 out.nl()
2630 try:
2631 self.action()
2632 out.nl()
2633 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002634 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002635 out.nl()
2636 return False
2637
2638class _SyncColoring(Coloring):
2639 def __init__(self, config):
2640 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002641 self.project = self.printer('header', attr='bold')
2642 self.info = self.printer('info')
2643 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002644
2645class SyncBuffer(object):
2646 def __init__(self, config, detach_head=False):
2647 self._messages = []
2648 self._failures = []
2649 self._later_queue1 = []
2650 self._later_queue2 = []
2651
2652 self.out = _SyncColoring(config)
2653 self.out.redirect(sys.stderr)
2654
2655 self.detach_head = detach_head
2656 self.clean = True
2657
2658 def info(self, project, fmt, *args):
2659 self._messages.append(_InfoMessage(project, fmt % args))
2660
2661 def fail(self, project, err=None):
2662 self._failures.append(_Failure(project, err))
2663 self.clean = False
2664
2665 def later1(self, project, what):
2666 self._later_queue1.append(_Later(project, what))
2667
2668 def later2(self, project, what):
2669 self._later_queue2.append(_Later(project, what))
2670
2671 def Finish(self):
2672 self._PrintMessages()
2673 self._RunLater()
2674 self._PrintMessages()
2675 return self.clean
2676
2677 def _RunLater(self):
2678 for q in ['_later_queue1', '_later_queue2']:
2679 if not self._RunQueue(q):
2680 return
2681
2682 def _RunQueue(self, queue):
2683 for m in getattr(self, queue):
2684 if not m.Run(self):
2685 self.clean = False
2686 return False
2687 setattr(self, queue, [])
2688 return True
2689
2690 def _PrintMessages(self):
2691 for m in self._messages:
2692 m.Print(self)
2693 for m in self._failures:
2694 m.Print(self)
2695
2696 self._messages = []
2697 self._failures = []
2698
2699
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002700class MetaProject(Project):
2701 """A special project housed under .repo.
2702 """
2703 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002704 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002705 manifest=manifest,
2706 name=name,
2707 gitdir=gitdir,
2708 objdir=gitdir,
2709 worktree=worktree,
2710 remote=RemoteSpec('origin'),
2711 relpath='.repo/%s' % name,
2712 revisionExpr='refs/heads/master',
2713 revisionId=None,
2714 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002715
2716 def PreSync(self):
2717 if self.Exists:
2718 cb = self.CurrentBranch
2719 if cb:
2720 base = self.GetBranch(cb).merge
2721 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002722 self.revisionExpr = base
2723 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002724
Anthony King7bdac712014-07-16 12:56:40 +01002725 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002726 """ Prepare MetaProject for manifest branch switch
2727 """
2728
2729 # detach and delete manifest branch, allowing a new
2730 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002731 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002732 self.Sync_LocalHalf(syncbuf)
2733 syncbuf.Finish()
2734
2735 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002736 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002737 capture_stdout=True,
2738 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002739
2740
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002741 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002742 def LastFetch(self):
2743 try:
2744 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2745 return os.path.getmtime(fh)
2746 except OSError:
2747 return 0
2748
2749 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002750 def HasChanges(self):
2751 """Has the remote received new commits not yet checked out?
2752 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002753 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002754 return False
2755
David Pursehouse8a68ff92012-09-24 12:15:13 +09002756 all_refs = self.bare_ref.all
2757 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002758 head = self.work_git.GetHead()
2759 if head.startswith(R_HEADS):
2760 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002761 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002762 except KeyError:
2763 head = None
2764
2765 if revid == head:
2766 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002767 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002768 return True
2769 return False