blob: 61fddacce2a4e89029440fa2e5dab7dad65d5a0a [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):
514 def __init__(self,
515 manifest,
516 name,
517 remote,
518 gitdir,
David James8d201162013-10-11 17:03:19 -0700519 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700520 worktree,
521 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700522 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800523 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100524 rebase=True,
525 groups=None,
526 sync_c=False,
527 sync_s=False,
528 clone_depth=None,
529 upstream=None,
530 parent=None,
531 is_derived=False,
532 dest_branch=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800533 """Init a Project object.
534
535 Args:
536 manifest: The XmlManifest object.
537 name: The `name` attribute of manifest.xml's project element.
538 remote: RemoteSpec object specifying its remote's properties.
539 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700540 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800541 worktree: Absolute path of git working tree.
542 relpath: Relative path of git working tree to repo's top directory.
543 revisionExpr: The `revision` attribute of manifest.xml's project element.
544 revisionId: git commit id for checking out.
545 rebase: The `rebase` attribute of manifest.xml's project element.
546 groups: The `groups` attribute of manifest.xml's project element.
547 sync_c: The `sync-c` attribute of manifest.xml's project element.
548 sync_s: The `sync-s` attribute of manifest.xml's project element.
549 upstream: The `upstream` attribute of manifest.xml's project element.
550 parent: The parent Project object.
551 is_derived: False if the project was explicitly defined in the manifest;
552 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400553 dest_branch: The branch to which to push changes for review by default.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800554 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700555 self.manifest = manifest
556 self.name = name
557 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800558 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700559 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800560 if worktree:
561 self.worktree = worktree.replace('\\', '/')
562 else:
563 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700564 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700565 self.revisionExpr = revisionExpr
566
567 if revisionId is None \
568 and revisionExpr \
569 and IsId(revisionExpr):
570 self.revisionId = revisionExpr
571 else:
572 self.revisionId = revisionId
573
Mike Pontillod3153822012-02-28 11:53:24 -0800574 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700575 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700576 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800577 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900578 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700579 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800580 self.parent = parent
581 self.is_derived = is_derived
582 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800583
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700584 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700585 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500586 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500587 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700588 self.config = GitConfig.ForRepository(
Anthony King7bdac712014-07-16 12:56:40 +0100589 gitdir=self.gitdir,
590 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700591
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800592 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700593 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800594 else:
595 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700596 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700597 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700598 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400599 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700600
Doug Anderson37282b42011-03-04 11:54:18 -0800601 # This will be filled in if a project is later identified to be the
602 # project containing repo hooks.
603 self.enabled_repo_hooks = []
604
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700605 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800606 def Derived(self):
607 return self.is_derived
608
609 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700610 def Exists(self):
611 return os.path.isdir(self.gitdir)
612
613 @property
614 def CurrentBranch(self):
615 """Obtain the name of the currently checked out branch.
616 The branch name omits the 'refs/heads/' prefix.
617 None is returned if the project is on a detached HEAD.
618 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700619 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700620 if b.startswith(R_HEADS):
621 return b[len(R_HEADS):]
622 return None
623
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700624 def IsRebaseInProgress(self):
625 w = self.worktree
626 g = os.path.join(w, '.git')
627 return os.path.exists(os.path.join(g, 'rebase-apply')) \
628 or os.path.exists(os.path.join(g, 'rebase-merge')) \
629 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200630
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700631 def IsDirty(self, consider_untracked=True):
632 """Is the working directory modified in some way?
633 """
634 self.work_git.update_index('-q',
635 '--unmerged',
636 '--ignore-missing',
637 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900638 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700639 return True
640 if self.work_git.DiffZ('diff-files'):
641 return True
642 if consider_untracked and self.work_git.LsOthers():
643 return True
644 return False
645
646 _userident_name = None
647 _userident_email = None
648
649 @property
650 def UserName(self):
651 """Obtain the user's personal name.
652 """
653 if self._userident_name is None:
654 self._LoadUserIdentity()
655 return self._userident_name
656
657 @property
658 def UserEmail(self):
659 """Obtain the user's email address. This is very likely
660 to be their Gerrit login.
661 """
662 if self._userident_email is None:
663 self._LoadUserIdentity()
664 return self._userident_email
665
666 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900667 u = self.bare_git.var('GIT_COMMITTER_IDENT')
668 m = re.compile("^(.*) <([^>]*)> ").match(u)
669 if m:
670 self._userident_name = m.group(1)
671 self._userident_email = m.group(2)
672 else:
673 self._userident_name = ''
674 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700675
676 def GetRemote(self, name):
677 """Get the configuration for a single remote.
678 """
679 return self.config.GetRemote(name)
680
681 def GetBranch(self, name):
682 """Get the configuration for a single branch.
683 """
684 return self.config.GetBranch(name)
685
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700686 def GetBranches(self):
687 """Get all existing local branches.
688 """
689 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900690 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700691 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700692
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530693 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700694 if name.startswith(R_HEADS):
695 name = name[len(R_HEADS):]
696 b = self.GetBranch(name)
697 b.current = name == current
698 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900699 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700700 heads[name] = b
701
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530702 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700703 if name.startswith(R_PUB):
704 name = name[len(R_PUB):]
705 b = heads.get(name)
706 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900707 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700708
709 return heads
710
Colin Cross5acde752012-03-28 20:15:45 -0700711 def MatchesGroups(self, manifest_groups):
712 """Returns true if the manifest groups specified at init should cause
713 this project to be synced.
714 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700715 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700716
Conley Owens971de8e2012-04-16 10:36:08 -0700717 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700718 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700719 manifest_groups: "-group1,group2"
720 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500721
722 The special manifest group "default" will match any project that
723 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700724 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500725 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700726 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500727 if not 'notdefault' in expanded_project_groups:
728 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700729
Conley Owens971de8e2012-04-16 10:36:08 -0700730 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700731 for group in expanded_manifest_groups:
732 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700733 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700734 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700735 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700736
Conley Owens971de8e2012-04-16 10:36:08 -0700737 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700738
739## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700740 def UncommitedFiles(self, get_all=True):
741 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700743 Args:
744 get_all: a boolean, if True - get information about all different
745 uncommitted files. If False - return as soon as any kind of
746 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500747 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700748 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500749 self.work_git.update_index('-q',
750 '--unmerged',
751 '--ignore-missing',
752 '--refresh')
753 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700754 details.append("rebase in progress")
755 if not get_all:
756 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500757
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700758 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
759 if changes:
760 details.extend(changes)
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-files').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.LsOthers()
771 if changes:
772 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500773
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700774 return details
775
776 def HasChanges(self):
777 """Returns true if there are uncommitted changes.
778 """
779 if self.UncommitedFiles(get_all=False):
780 return True
781 else:
782 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500783
Terence Haddock4655e812011-03-31 12:33:34 +0200784 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700785 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200786
787 Args:
788 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700789 """
790 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200791 if output_redir == None:
792 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700793 print(file=output_redir)
794 print('project %s/' % self.relpath, file=output_redir)
795 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700796 return
797
798 self.work_git.update_index('-q',
799 '--unmerged',
800 '--ignore-missing',
801 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700802 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
804 df = self.work_git.DiffZ('diff-files')
805 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100806 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700807 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700808
809 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200810 if not output_redir == None:
811 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700812 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700813
814 branch = self.CurrentBranch
815 if branch is None:
816 out.nobranch('(*** NO BRANCH ***)')
817 else:
818 out.branch('branch %s', branch)
819 out.nl()
820
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700821 if rb:
822 out.important('prior sync failed; rebase still in progress')
823 out.nl()
824
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700825 paths = list()
826 paths.extend(di.keys())
827 paths.extend(df.keys())
828 paths.extend(do)
829
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530830 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900831 try:
832 i = di[p]
833 except KeyError:
834 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900836 try:
837 f = df[p]
838 except KeyError:
839 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200840
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900841 if i:
842 i_status = i.status.upper()
843 else:
844 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900846 if f:
847 f_status = f.status.lower()
848 else:
849 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700850
851 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800852 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853 i.src_path, p, i.level)
854 else:
855 line = ' %s%s\t%s' % (i_status, f_status, p)
856
857 if i and not f:
858 out.added('%s', line)
859 elif (i and f) or (not i and f):
860 out.changed('%s', line)
861 elif not i and not f:
862 out.untracked('%s', line)
863 else:
864 out.write('%s', line)
865 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200866
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700867 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868
pelyad67872d2012-03-28 14:49:58 +0300869 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700870 """Prints the status of the repository to stdout.
871 """
872 out = DiffColoring(self.config)
873 cmd = ['diff']
874 if out.is_on:
875 cmd.append('--color')
876 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300877 if absolute_paths:
878 cmd.append('--src-prefix=a/%s/' % self.relpath)
879 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700880 cmd.append('--')
881 p = GitCommand(self,
882 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100883 capture_stdout=True,
884 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700885 has_diff = False
886 for line in p.process.stdout:
887 if not has_diff:
888 out.nl()
889 out.project('project %s/' % self.relpath)
890 out.nl()
891 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700892 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893 p.Wait()
894
895
896## Publish / Upload ##
897
David Pursehouse8a68ff92012-09-24 12:15:13 +0900898 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899 """Was the branch published (uploaded) for code review?
900 If so, returns the SHA-1 hash of the last published
901 state for the branch.
902 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700903 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900904 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700905 try:
906 return self.bare_git.rev_parse(key)
907 except GitError:
908 return None
909 else:
910 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900911 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700912 except KeyError:
913 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914
David Pursehouse8a68ff92012-09-24 12:15:13 +0900915 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916 """Prunes any stale published refs.
917 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900918 if all_refs is None:
919 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920 heads = set()
921 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530922 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923 if name.startswith(R_HEADS):
924 heads.add(name)
925 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900926 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530928 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929 n = name[len(R_PUB):]
930 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900931 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700933 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934 """List any branches which can be uploaded for review.
935 """
936 heads = {}
937 pubed = {}
938
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530939 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700940 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900941 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900943 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944
945 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530946 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900947 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700949 if selected_branch and branch != selected_branch:
950 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800952 rb = self.GetUploadableBranch(branch)
953 if rb:
954 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700955 return ready
956
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800957 def GetUploadableBranch(self, branch_name):
958 """Get a single uploadable branch, or None.
959 """
960 branch = self.GetBranch(branch_name)
961 base = branch.LocalMerge
962 if branch.LocalMerge:
963 rb = ReviewableBranch(self, branch, base)
964 if rb.commits:
965 return rb
966 return None
967
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700968 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100969 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -0700970 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400971 draft=False,
972 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973 """Uploads the named branch for code review.
974 """
975 if branch is None:
976 branch = self.CurrentBranch
977 if branch is None:
978 raise GitError('not currently on a branch')
979
980 branch = self.GetBranch(branch)
981 if not branch.LocalMerge:
982 raise GitError('branch %s does not track a remote' % branch.name)
983 if not branch.remote.review:
984 raise GitError('remote %s has no review url' % branch.remote.name)
985
Bryan Jacobsf609f912013-05-06 13:36:24 -0400986 if dest_branch is None:
987 dest_branch = self.dest_branch
988 if dest_branch is None:
989 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990 if not dest_branch.startswith(R_HEADS):
991 dest_branch = R_HEADS + dest_branch
992
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800993 if not branch.remote.projectname:
994 branch.remote.projectname = self.name
995 branch.remote.Save()
996
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800997 url = branch.remote.ReviewUrl(self.UserEmail)
998 if url is None:
999 raise UploadError('review not configured')
1000 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001001
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001002 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001003 rp = ['gerrit receive-pack']
1004 for e in people[0]:
1005 rp.append('--reviewer=%s' % sq(e))
1006 for e in people[1]:
1007 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001008 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001009
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001010 cmd.append(url)
1011
1012 if dest_branch.startswith(R_HEADS):
1013 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001014
1015 upload_type = 'for'
1016 if draft:
1017 upload_type = 'drafts'
1018
1019 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1020 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001021 if auto_topic:
1022 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001023 if not url.startswith('ssh://'):
1024 rp = ['r=%s' % p for p in people[0]] + \
1025 ['cc=%s' % p for p in people[1]]
1026 if rp:
1027 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001028 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001029
Anthony King7bdac712014-07-16 12:56:40 +01001030 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001031 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001032
1033 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1034 self.bare_git.UpdateRef(R_PUB + branch.name,
1035 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001036 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037
1038
1039## Sync ##
1040
Julien Campergue335f5ef2013-10-16 11:02:35 +02001041 def _ExtractArchive(self, tarpath, path=None):
1042 """Extract the given tar on its current location
1043
1044 Args:
1045 - tarpath: The path to the actual tar file
1046
1047 """
1048 try:
1049 with tarfile.open(tarpath, 'r') as tar:
1050 tar.extractall(path=path)
1051 return True
1052 except (IOError, tarfile.TarError) as e:
1053 print("error: Cannot extract archive %s: "
1054 "%s" % (tarpath, str(e)), file=sys.stderr)
1055 return False
1056
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001057 def Sync_NetworkHalf(self,
1058 quiet=False,
1059 is_new=None,
1060 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001061 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001062 no_tags=False,
1063 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064 """Perform only the network IO portion of the sync process.
1065 Local working directory/branch state is not affected.
1066 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001067 if archive and not isinstance(self, MetaProject):
1068 if self.remote.url.startswith(('http://', 'https://')):
1069 print("error: %s: Cannot fetch archives from http/https "
1070 "remotes." % self.name, file=sys.stderr)
1071 return False
1072
1073 name = self.relpath.replace('\\', '/')
1074 name = name.replace('/', '_')
1075 tarpath = '%s.tar' % name
1076 topdir = self.manifest.topdir
1077
1078 try:
1079 self._FetchArchive(tarpath, cwd=topdir)
1080 except GitError as e:
1081 print('error: %s' % str(e), file=sys.stderr)
1082 return False
1083
1084 # From now on, we only need absolute tarpath
1085 tarpath = os.path.join(topdir, tarpath)
1086
1087 if not self._ExtractArchive(tarpath, path=topdir):
1088 return False
1089 try:
1090 os.remove(tarpath)
1091 except OSError as e:
1092 print("warn: Cannot remove archive %s: "
1093 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001094 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001095 return True
1096
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001097 if is_new is None:
1098 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001099 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001101 else:
1102 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001103 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001104
1105 if is_new:
1106 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1107 try:
1108 fd = open(alt, 'rb')
1109 try:
1110 alt_dir = fd.readline().rstrip()
1111 finally:
1112 fd.close()
1113 except IOError:
1114 alt_dir = None
1115 else:
1116 alt_dir = None
1117
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001118 if clone_bundle \
1119 and alt_dir is None \
1120 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001121 is_new = False
1122
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001123 if not current_branch_only:
1124 if self.sync_c:
1125 current_branch_only = True
1126 elif not self.manifest._loaded:
1127 # Manifest cannot check defaults until it syncs.
1128 current_branch_only = False
1129 elif self.manifest.default.sync_c:
1130 current_branch_only = True
1131
Conley Owens666d5342014-05-01 13:09:57 -07001132 has_sha1 = ID_RE.match(self.revisionExpr) and self._CheckForSha1()
1133 if (not has_sha1 #Need to fetch since we don't already have this revision
1134 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1135 current_branch_only=current_branch_only,
1136 no_tags=no_tags)):
Anthony King7bdac712014-07-16 12:56:40 +01001137 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001138
1139 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001140 self._InitMRef()
1141 else:
1142 self._InitMirrorHead()
1143 try:
1144 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1145 except OSError:
1146 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001147 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001148
1149 def PostRepoUpgrade(self):
1150 self._InitHooks()
1151
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001152 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001153 for copyfile in self.copyfiles:
1154 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001155 for linkfile in self.linkfiles:
1156 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001157
Julien Camperguedd654222014-01-09 16:21:37 +01001158 def GetCommitRevisionId(self):
1159 """Get revisionId of a commit.
1160
1161 Use this method instead of GetRevisionId to get the id of the commit rather
1162 than the id of the current git object (for example, a tag)
1163
1164 """
1165 if not self.revisionExpr.startswith(R_TAGS):
1166 return self.GetRevisionId(self._allrefs)
1167
1168 try:
1169 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1170 except GitError:
1171 raise ManifestInvalidRevisionError(
1172 'revision %s in %s not found' % (self.revisionExpr,
1173 self.name))
1174
David Pursehouse8a68ff92012-09-24 12:15:13 +09001175 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001176 if self.revisionId:
1177 return self.revisionId
1178
1179 rem = self.GetRemote(self.remote.name)
1180 rev = rem.ToLocal(self.revisionExpr)
1181
David Pursehouse8a68ff92012-09-24 12:15:13 +09001182 if all_refs is not None and rev in all_refs:
1183 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001184
1185 try:
1186 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1187 except GitError:
1188 raise ManifestInvalidRevisionError(
1189 'revision %s in %s not found' % (self.revisionExpr,
1190 self.name))
1191
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001192 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193 """Perform only the local IO portion of the sync process.
1194 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195 """
David James8d201162013-10-11 17:03:19 -07001196 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001197 all_refs = self.bare_ref.all
1198 self.CleanPublishedCache(all_refs)
1199 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001200
David Pursehouse1d947b32012-10-25 12:23:11 +09001201 def _doff():
1202 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001203 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001204
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001205 head = self.work_git.GetHead()
1206 if head.startswith(R_HEADS):
1207 branch = head[len(R_HEADS):]
1208 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001209 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001210 except KeyError:
1211 head = None
1212 else:
1213 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001214
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001215 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001216 # Currently on a detached HEAD. The user is assumed to
1217 # not have any local modifications worth worrying about.
1218 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001219 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001220 syncbuf.fail(self, _PriorSyncFailedError())
1221 return
1222
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001223 if head == revid:
1224 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001225 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001226 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001227 if not syncbuf.detach_head:
1228 return
1229 else:
1230 lost = self._revlist(not_rev(revid), HEAD)
1231 if lost:
1232 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001233
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001234 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001235 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001236 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001237 syncbuf.fail(self, e)
1238 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001239 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001240 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001241
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001242 if head == revid:
1243 # No changes; don't do anything further.
1244 #
1245 return
1246
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001248
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001249 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001250 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001251 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001252 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001253 syncbuf.info(self,
1254 "leaving %s; does not track upstream",
1255 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001256 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001257 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001258 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001259 syncbuf.fail(self, e)
1260 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001261 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001262 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001264 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001265 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001266 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001267 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001268 if not_merged:
1269 if upstream_gain:
1270 # The user has published this branch and some of those
1271 # commits are not yet merged upstream. We do not want
1272 # to rewrite the published commits so we punt.
1273 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001274 syncbuf.fail(self,
1275 "branch %s is published (but not merged) and is now %d commits behind"
1276 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001277 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001278 elif pub == head:
1279 # All published commits are merged, and thus we are a
1280 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001281 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001282 syncbuf.later1(self, _doff)
1283 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001284
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001285 # Examine the local commits not in the remote. Find the
1286 # last one attributed to this user, if any.
1287 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001288 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001289 last_mine = None
1290 cnt_mine = 0
1291 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301292 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001293 if committer_email == self.UserEmail:
1294 last_mine = commit_id
1295 cnt_mine += 1
1296
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001297 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001298 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299
1300 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001301 syncbuf.fail(self, _DirtyError())
1302 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001304 # If the upstream switched on us, warn the user.
1305 #
1306 if branch.merge != self.revisionExpr:
1307 if branch.merge and self.revisionExpr:
1308 syncbuf.info(self,
1309 'manifest switched %s...%s',
1310 branch.merge,
1311 self.revisionExpr)
1312 elif branch.merge:
1313 syncbuf.info(self,
1314 'manifest no longer tracks %s',
1315 branch.merge)
1316
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001317 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001319 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001321 syncbuf.info(self,
1322 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001323 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001325 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001326 if not ID_RE.match(self.revisionExpr):
1327 # in case of manifest sync the revisionExpr might be a SHA1
1328 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001329 if not branch.merge.startswith('refs/'):
1330 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001331 branch.Save()
1332
Mike Pontillod3153822012-02-28 11:53:24 -08001333 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001334 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001335 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001336 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001337 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001338 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001339 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001340 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001341 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001342 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001343 syncbuf.fail(self, e)
1344 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001345 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001346 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001347
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001348 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001349 # dest should already be an absolute path, but src is project relative
1350 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001351 abssrc = os.path.join(self.worktree, src)
1352 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001354 def AddLinkFile(self, src, dest, absdest):
1355 # dest should already be an absolute path, but src is project relative
1356 # make src an absolute path
1357 abssrc = os.path.join(self.worktree, src)
1358 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1359
James W. Mills24c13082012-04-12 15:04:13 -05001360 def AddAnnotation(self, name, value, keep):
1361 self.annotations.append(_Annotation(name, value, keep))
1362
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001363 def DownloadPatchSet(self, change_id, patch_id):
1364 """Download a single patch set of a single change to FETCH_HEAD.
1365 """
1366 remote = self.GetRemote(self.remote.name)
1367
1368 cmd = ['fetch', remote.name]
1369 cmd.append('refs/changes/%2.2d/%d/%d' \
1370 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001371 if GitCommand(self, cmd, bare=True).Wait() != 0:
1372 return None
1373 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001374 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001375 change_id,
1376 patch_id,
1377 self.bare_git.rev_parse('FETCH_HEAD'))
1378
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001379
1380## Branch Management ##
1381
1382 def StartBranch(self, name):
1383 """Create a new branch off the manifest's revision.
1384 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001385 head = self.work_git.GetHead()
1386 if head == (R_HEADS + name):
1387 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001388
David Pursehouse8a68ff92012-09-24 12:15:13 +09001389 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001390 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001391 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001392 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001393 capture_stdout=True,
1394 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001395
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001396 branch = self.GetBranch(name)
1397 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001398 branch.merge = self.revisionExpr
Alexandre Boeglin38258272015-04-30 14:50:33 +02001399 if not branch.merge.startswith('refs/') and not ID_RE.match(self.revisionExpr):
Conley Owens04f2f0e2014-10-01 17:22:46 -07001400 branch.merge = R_HEADS + self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001401 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001402
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001403 if head.startswith(R_HEADS):
1404 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001405 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001406 except KeyError:
1407 head = None
1408
1409 if revid and head and revid == head:
1410 ref = os.path.join(self.gitdir, R_HEADS + name)
1411 try:
1412 os.makedirs(os.path.dirname(ref))
1413 except OSError:
1414 pass
1415 _lwrite(ref, '%s\n' % revid)
1416 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1417 'ref: %s%s\n' % (R_HEADS, name))
1418 branch.Save()
1419 return True
1420
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001421 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001422 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001423 capture_stdout=True,
1424 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001425 branch.Save()
1426 return True
1427 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001428
Wink Saville02d79452009-04-10 13:01:24 -07001429 def CheckoutBranch(self, name):
1430 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001431
1432 Args:
1433 name: The name of the branch to checkout.
1434
1435 Returns:
1436 True if the checkout succeeded; False if it didn't; None if the branch
1437 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001438 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001439 rev = R_HEADS + name
1440 head = self.work_git.GetHead()
1441 if head == rev:
1442 # Already on the branch
1443 #
1444 return True
Wink Saville02d79452009-04-10 13:01:24 -07001445
David Pursehouse8a68ff92012-09-24 12:15:13 +09001446 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001447 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001448 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001449 except KeyError:
1450 # Branch does not exist in this project
1451 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001452 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001453
1454 if head.startswith(R_HEADS):
1455 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001456 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001457 except KeyError:
1458 head = None
1459
1460 if head == revid:
1461 # Same revision; just update HEAD to point to the new
1462 # target branch, but otherwise take no other action.
1463 #
1464 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1465 'ref: %s%s\n' % (R_HEADS, name))
1466 return True
Wink Saville02d79452009-04-10 13:01:24 -07001467
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001468 return GitCommand(self,
1469 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001470 capture_stdout=True,
1471 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001472
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001473 def AbandonBranch(self, name):
1474 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001475
1476 Args:
1477 name: The name of the branch to abandon.
1478
1479 Returns:
1480 True if the abandon succeeded; False if it didn't; None if the branch
1481 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001482 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001483 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001484 all_refs = self.bare_ref.all
1485 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001486 # Doesn't exist
1487 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001488
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001489 head = self.work_git.GetHead()
1490 if head == rev:
1491 # We can't destroy the branch while we are sitting
1492 # on it. Switch to a detached HEAD.
1493 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001494 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001495
David Pursehouse8a68ff92012-09-24 12:15:13 +09001496 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001497 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001498 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1499 '%s\n' % revid)
1500 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001501 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001502
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001503 return GitCommand(self,
1504 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001505 capture_stdout=True,
1506 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001507
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001508 def PruneHeads(self):
1509 """Prune any topic branches already merged into upstream.
1510 """
1511 cb = self.CurrentBranch
1512 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001513 left = self._allrefs
1514 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001515 if name.startswith(R_HEADS):
1516 name = name[len(R_HEADS):]
1517 if cb is None or name != cb:
1518 kill.append(name)
1519
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001520 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001521 if cb is not None \
1522 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001523 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001524 self.work_git.DetachHead(HEAD)
1525 kill.append(cb)
1526
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001527 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001528 old = self.bare_git.GetHead()
1529 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001530 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1531
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001532 try:
1533 self.bare_git.DetachHead(rev)
1534
1535 b = ['branch', '-d']
1536 b.extend(kill)
1537 b = GitCommand(self, b, bare=True,
1538 capture_stdout=True,
1539 capture_stderr=True)
1540 b.Wait()
1541 finally:
1542 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001543 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001544
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001545 for branch in kill:
1546 if (R_HEADS + branch) not in left:
1547 self.CleanPublishedCache()
1548 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001549
1550 if cb and cb not in kill:
1551 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001552 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001553
1554 kept = []
1555 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001556 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001557 branch = self.GetBranch(branch)
1558 base = branch.LocalMerge
1559 if not base:
1560 base = rev
1561 kept.append(ReviewableBranch(self, branch, base))
1562 return kept
1563
1564
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001565## Submodule Management ##
1566
1567 def GetRegisteredSubprojects(self):
1568 result = []
1569 def rec(subprojects):
1570 if not subprojects:
1571 return
1572 result.extend(subprojects)
1573 for p in subprojects:
1574 rec(p.subprojects)
1575 rec(self.subprojects)
1576 return result
1577
1578 def _GetSubmodules(self):
1579 # Unfortunately we cannot call `git submodule status --recursive` here
1580 # because the working tree might not exist yet, and it cannot be used
1581 # without a working tree in its current implementation.
1582
1583 def get_submodules(gitdir, rev):
1584 # Parse .gitmodules for submodule sub_paths and sub_urls
1585 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1586 if not sub_paths:
1587 return []
1588 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1589 # revision of submodule repository
1590 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1591 submodules = []
1592 for sub_path, sub_url in zip(sub_paths, sub_urls):
1593 try:
1594 sub_rev = sub_revs[sub_path]
1595 except KeyError:
1596 # Ignore non-exist submodules
1597 continue
1598 submodules.append((sub_rev, sub_path, sub_url))
1599 return submodules
1600
1601 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1602 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1603 def parse_gitmodules(gitdir, rev):
1604 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1605 try:
Anthony King7bdac712014-07-16 12:56:40 +01001606 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1607 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001608 except GitError:
1609 return [], []
1610 if p.Wait() != 0:
1611 return [], []
1612
1613 gitmodules_lines = []
1614 fd, temp_gitmodules_path = tempfile.mkstemp()
1615 try:
1616 os.write(fd, p.stdout)
1617 os.close(fd)
1618 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001619 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1620 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001621 if p.Wait() != 0:
1622 return [], []
1623 gitmodules_lines = p.stdout.split('\n')
1624 except GitError:
1625 return [], []
1626 finally:
1627 os.remove(temp_gitmodules_path)
1628
1629 names = set()
1630 paths = {}
1631 urls = {}
1632 for line in gitmodules_lines:
1633 if not line:
1634 continue
1635 m = re_path.match(line)
1636 if m:
1637 names.add(m.group(1))
1638 paths[m.group(1)] = m.group(2)
1639 continue
1640 m = re_url.match(line)
1641 if m:
1642 names.add(m.group(1))
1643 urls[m.group(1)] = m.group(2)
1644 continue
1645 names = sorted(names)
1646 return ([paths.get(name, '') for name in names],
1647 [urls.get(name, '') for name in names])
1648
1649 def git_ls_tree(gitdir, rev, paths):
1650 cmd = ['ls-tree', rev, '--']
1651 cmd.extend(paths)
1652 try:
Anthony King7bdac712014-07-16 12:56:40 +01001653 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1654 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001655 except GitError:
1656 return []
1657 if p.Wait() != 0:
1658 return []
1659 objects = {}
1660 for line in p.stdout.split('\n'):
1661 if not line.strip():
1662 continue
1663 object_rev, object_path = line.split()[2:4]
1664 objects[object_path] = object_rev
1665 return objects
1666
1667 try:
1668 rev = self.GetRevisionId()
1669 except GitError:
1670 return []
1671 return get_submodules(self.gitdir, rev)
1672
1673 def GetDerivedSubprojects(self):
1674 result = []
1675 if not self.Exists:
1676 # If git repo does not exist yet, querying its submodules will
1677 # mess up its states; so return here.
1678 return result
1679 for rev, path, url in self._GetSubmodules():
1680 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001681 relpath, worktree, gitdir, objdir = \
1682 self.manifest.GetSubprojectPaths(self, name, path)
1683 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001684 if project:
1685 result.extend(project.GetDerivedSubprojects())
1686 continue
David James8d201162013-10-11 17:03:19 -07001687
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001688 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001689 url=url,
1690 review=self.remote.review,
1691 revision=self.remote.revision)
1692 subproject = Project(manifest=self.manifest,
1693 name=name,
1694 remote=remote,
1695 gitdir=gitdir,
1696 objdir=objdir,
1697 worktree=worktree,
1698 relpath=relpath,
1699 revisionExpr=self.revisionExpr,
1700 revisionId=rev,
1701 rebase=self.rebase,
1702 groups=self.groups,
1703 sync_c=self.sync_c,
1704 sync_s=self.sync_s,
1705 parent=self,
1706 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001707 result.append(subproject)
1708 result.extend(subproject.GetDerivedSubprojects())
1709 return result
1710
1711
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001712## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001713 def _CheckForSha1(self):
1714 try:
1715 # if revision (sha or tag) is not present then following function
1716 # throws an error.
1717 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1718 return True
1719 except GitError:
1720 # There is no such persistent revision. We have to fetch it.
1721 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001722
Julien Campergue335f5ef2013-10-16 11:02:35 +02001723 def _FetchArchive(self, tarpath, cwd=None):
1724 cmd = ['archive', '-v', '-o', tarpath]
1725 cmd.append('--remote=%s' % self.remote.url)
1726 cmd.append('--prefix=%s/' % self.relpath)
1727 cmd.append(self.revisionExpr)
1728
1729 command = GitCommand(self, cmd, cwd=cwd,
1730 capture_stdout=True,
1731 capture_stderr=True)
1732
1733 if command.Wait() != 0:
1734 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1735
Conley Owens80b87fe2014-05-09 17:13:44 -07001736
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001737 def _RemoteFetch(self, name=None,
1738 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001739 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001740 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001741 alt_dir=None,
1742 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001743
1744 is_sha1 = False
1745 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001746 depth = None
1747
1748 # The depth should not be used when fetching to a mirror because
1749 # it will result in a shallow repository that cannot be cloned or
1750 # fetched from.
1751 if not self.manifest.IsMirror:
1752 if self.clone_depth:
1753 depth = self.clone_depth
1754 else:
1755 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001756 # The repo project should never be synced with partial depth
1757 if self.relpath == '.repo/repo':
1758 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001759
Shawn Pearce69e04d82014-01-29 12:48:54 -08001760 if depth:
1761 current_branch_only = True
1762
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001763 if ID_RE.match(self.revisionExpr) is not None:
1764 is_sha1 = True
1765
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001766 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001767 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001768 # this is a tag and its sha1 value should never change
1769 tag_name = self.revisionExpr[len(R_TAGS):]
1770
1771 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001772 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001773 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001774 if is_sha1 and not depth:
1775 # When syncing a specific commit and --depth is not set:
1776 # * if upstream is explicitly specified and is not a sha1, fetch only
1777 # upstream as users expect only upstream to be fetch.
1778 # Note: The commit might not be in upstream in which case the sync
1779 # will fail.
1780 # * otherwise, fetch all branches to make sure we end up with the
1781 # specific commit.
1782 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001783
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001784 if not name:
1785 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001786
1787 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001788 remote = self.GetRemote(name)
1789 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001790 ssh_proxy = True
1791
Shawn O. Pearce88443382010-10-08 10:02:09 +02001792 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001793 if alt_dir and 'objects' == os.path.basename(alt_dir):
1794 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001795 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1796 remote = self.GetRemote(name)
1797
David Pursehouse8a68ff92012-09-24 12:15:13 +09001798 all_refs = self.bare_ref.all
1799 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001800 tmp = set()
1801
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301802 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001803 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001804 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001805 all_refs[r] = ref_id
1806 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001807 continue
1808
David Pursehouse8a68ff92012-09-24 12:15:13 +09001809 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001810 continue
1811
David Pursehouse8a68ff92012-09-24 12:15:13 +09001812 r = 'refs/_alt/%s' % ref_id
1813 all_refs[r] = ref_id
1814 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001815 tmp.add(r)
1816
Shawn O. Pearce88443382010-10-08 10:02:09 +02001817 tmp_packed = ''
1818 old_packed = ''
1819
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301820 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001821 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001822 tmp_packed += line
1823 if r not in tmp:
1824 old_packed += line
1825
1826 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001827 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001828 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001829
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001830 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001831
Conley Owensf97e8382015-01-21 11:12:46 -08001832 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001833 cmd.append('--depth=%s' % depth)
1834
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001835 if quiet:
1836 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001837 if not self.worktree:
1838 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001839 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001840
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001841 # If using depth then we should not get all the tags since they may
1842 # be outside of the depth.
1843 if no_tags or depth:
1844 cmd.append('--no-tags')
1845 else:
1846 cmd.append('--tags')
1847
Conley Owens80b87fe2014-05-09 17:13:44 -07001848 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001849 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001850 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001851 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001852 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001853 spec.append('tag')
1854 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001855
David Pursehouse403b64e2015-04-27 10:41:33 +09001856 if not self.manifest.IsMirror:
1857 branch = self.revisionExpr
1858 if is_sha1 and depth:
1859 # Shallow checkout of a specific commit, fetch from that commit and not
1860 # the heads only as the commit might be deeper in the history.
1861 spec.append(branch)
1862 else:
1863 if is_sha1:
1864 branch = self.upstream
1865 if branch is not None and branch.strip():
1866 if not branch.startswith('refs/'):
1867 branch = R_HEADS + branch
1868 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001869 cmd.extend(spec)
1870
1871 shallowfetch = self.config.GetString('repo.shallowfetch')
1872 if shallowfetch and shallowfetch != ' '.join(spec):
Anthony King23ff7df2015-03-28 19:42:39 +00001873 GitCommand(self, ['fetch', '--depth=2147483647', name]
1874 + shallowfetch.split(),
Conley Owens80b87fe2014-05-09 17:13:44 -07001875 bare=True, ssh_proxy=ssh_proxy).Wait()
1876 if depth:
Anthony King7bdac712014-07-16 12:56:40 +01001877 self.config.SetString('repo.shallowfetch', ' '.join(spec))
Conley Owens80b87fe2014-05-09 17:13:44 -07001878 else:
Anthony King7bdac712014-07-16 12:56:40 +01001879 self.config.SetString('repo.shallowfetch', None)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001880
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001881 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001882 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001883 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001884 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001885 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001886 ok = True
1887 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001888 # If needed, run the 'git remote prune' the first time through the loop
1889 elif (not _i and
1890 "error:" in gitcmd.stderr and
1891 "git remote prune" in gitcmd.stderr):
1892 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001893 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001894 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001895 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001896 break
1897 continue
Brian Harring14a66742012-09-28 20:21:57 -07001898 elif current_branch_only and is_sha1 and ret == 128:
1899 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1900 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1901 # abort the optimization attempt and do a full sync.
1902 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001903 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001904
1905 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001906 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001907 if old_packed != '':
1908 _lwrite(packed_refs, old_packed)
1909 else:
1910 os.remove(packed_refs)
1911 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001912
1913 if is_sha1 and current_branch_only and self.upstream:
1914 # We just synced the upstream given branch; verify we
1915 # got what we wanted, else trigger a second run of all
1916 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001917 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001918 return self._RemoteFetch(name=name, current_branch_only=False,
1919 initial=False, quiet=quiet, alt_dir=alt_dir)
1920
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001921 return ok
1922
1923 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001924 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001925 return False
1926
1927 remote = self.GetRemote(self.remote.name)
1928 bundle_url = remote.url + '/clone.bundle'
1929 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001930 if GetSchemeFromUrl(bundle_url) not in (
1931 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001932 return False
1933
1934 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1935 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001936
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001937 exist_dst = os.path.exists(bundle_dst)
1938 exist_tmp = os.path.exists(bundle_tmp)
1939
1940 if not initial and not exist_dst and not exist_tmp:
1941 return False
1942
1943 if not exist_dst:
1944 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1945 if not exist_dst:
1946 return False
1947
1948 cmd = ['fetch']
1949 if quiet:
1950 cmd.append('--quiet')
1951 if not self.worktree:
1952 cmd.append('--update-head-ok')
1953 cmd.append(bundle_dst)
1954 for f in remote.fetch:
1955 cmd.append(str(f))
1956 cmd.append('refs/tags/*:refs/tags/*')
1957
1958 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001959 if os.path.exists(bundle_dst):
1960 os.remove(bundle_dst)
1961 if os.path.exists(bundle_tmp):
1962 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001963 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001964
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001965 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001966 if os.path.exists(dstPath):
1967 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001968
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001969 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001970 if quiet:
1971 cmd += ['--silent']
1972 if os.path.exists(tmpPath):
1973 size = os.stat(tmpPath).st_size
1974 if size >= 1024:
1975 cmd += ['--continue-at', '%d' % (size,)]
1976 else:
1977 os.remove(tmpPath)
1978 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1979 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz497bde42015-01-02 13:58:05 -08001980 with self._GetBundleCookieFile(srcUrl, quiet) as cookiefile:
Dave Borowitz137d0132015-01-02 11:12:54 -08001981 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08001982 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08001983 if srcUrl.startswith('persistent-'):
1984 srcUrl = srcUrl[len('persistent-'):]
1985 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001986
Dave Borowitz137d0132015-01-02 11:12:54 -08001987 if IsTrace():
1988 Trace('%s', ' '.join(cmd))
1989 try:
1990 proc = subprocess.Popen(cmd)
1991 except OSError:
1992 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001993
Dave Borowitz137d0132015-01-02 11:12:54 -08001994 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001995
Dave Borowitz137d0132015-01-02 11:12:54 -08001996 if curlret == 22:
1997 # From curl man page:
1998 # 22: HTTP page not retrieved. The requested url was not found or
1999 # returned another error with the HTTP error code being 400 or above.
2000 # This return code only appears if -f, --fail is used.
2001 if not quiet:
2002 print("Server does not provide clone.bundle; ignoring.",
2003 file=sys.stderr)
2004 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002005
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002006 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002007 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002008 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002009 return True
2010 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002011 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002012 return False
2013 else:
2014 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002015
Kris Giesingc8d882a2014-12-23 13:02:32 -08002016 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002017 try:
2018 with open(path) as f:
2019 if f.read(16) == '# v2 git bundle\n':
2020 return True
2021 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002022 if not quiet:
2023 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002024 return False
2025 except OSError:
2026 return False
2027
Dave Borowitz137d0132015-01-02 11:12:54 -08002028 @contextlib.contextmanager
Dave Borowitz497bde42015-01-02 13:58:05 -08002029 def _GetBundleCookieFile(self, url, quiet):
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002030 if url.startswith('persistent-'):
2031 try:
2032 p = subprocess.Popen(
2033 ['git-remote-persistent-https', '-print_config', url],
2034 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2035 stderr=subprocess.PIPE)
Dave Borowitz137d0132015-01-02 11:12:54 -08002036 try:
2037 prefix = 'http.cookiefile='
2038 cookiefile = None
2039 for line in p.stdout:
2040 line = line.strip()
2041 if line.startswith(prefix):
2042 cookiefile = line[len(prefix):]
2043 break
2044 # Leave subprocess open, as cookie file may be transient.
2045 if cookiefile:
2046 yield cookiefile
2047 return
2048 finally:
2049 p.stdin.close()
2050 if p.wait():
2051 err_msg = p.stderr.read()
2052 if ' -print_config' in err_msg:
2053 pass # Persistent proxy doesn't support -print_config.
Dave Borowitz497bde42015-01-02 13:58:05 -08002054 elif not quiet:
Dave Borowitz137d0132015-01-02 11:12:54 -08002055 print(err_msg, file=sys.stderr)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002056 except OSError as e:
2057 if e.errno == errno.ENOENT:
2058 pass # No persistent proxy.
2059 raise
Dave Borowitz137d0132015-01-02 11:12:54 -08002060 yield GitConfig.ForUser().GetString('http.cookiefile')
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002061
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002062 def _Checkout(self, rev, quiet=False):
2063 cmd = ['checkout']
2064 if quiet:
2065 cmd.append('-q')
2066 cmd.append(rev)
2067 cmd.append('--')
2068 if GitCommand(self, cmd).Wait() != 0:
2069 if self._allrefs:
2070 raise GitError('%s checkout %s ' % (self.name, rev))
2071
Anthony King7bdac712014-07-16 12:56:40 +01002072 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002073 cmd = ['cherry-pick']
2074 cmd.append(rev)
2075 cmd.append('--')
2076 if GitCommand(self, cmd).Wait() != 0:
2077 if self._allrefs:
2078 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2079
Anthony King7bdac712014-07-16 12:56:40 +01002080 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002081 cmd = ['revert']
2082 cmd.append('--no-edit')
2083 cmd.append(rev)
2084 cmd.append('--')
2085 if GitCommand(self, cmd).Wait() != 0:
2086 if self._allrefs:
2087 raise GitError('%s revert %s ' % (self.name, rev))
2088
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002089 def _ResetHard(self, rev, quiet=True):
2090 cmd = ['reset', '--hard']
2091 if quiet:
2092 cmd.append('-q')
2093 cmd.append(rev)
2094 if GitCommand(self, cmd).Wait() != 0:
2095 raise GitError('%s reset --hard %s ' % (self.name, rev))
2096
Anthony King7bdac712014-07-16 12:56:40 +01002097 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002098 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002099 if onto is not None:
2100 cmd.extend(['--onto', onto])
2101 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002102 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002103 raise GitError('%s rebase %s ' % (self.name, upstream))
2104
Pierre Tardy3d125942012-05-04 12:18:12 +02002105 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002106 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002107 if ffonly:
2108 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002109 if GitCommand(self, cmd).Wait() != 0:
2110 raise GitError('%s merge %s ' % (self.name, head))
2111
Jonathan Nieder93719792015-03-17 11:29:58 -07002112 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002113 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002114
2115 # Initialize the bare repository, which contains all of the objects.
2116 if not os.path.exists(self.objdir):
2117 os.makedirs(self.objdir)
2118 self.bare_objdir.init()
2119
2120 # If we have a separate directory to hold refs, initialize it as well.
2121 if self.objdir != self.gitdir:
2122 os.makedirs(self.gitdir)
2123 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2124 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002125
Shawn O. Pearce88443382010-10-08 10:02:09 +02002126 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002127 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002128
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002129 if ref_dir or mirror_git:
2130 if not mirror_git:
2131 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002132 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2133 self.relpath + '.git')
2134
2135 if os.path.exists(mirror_git):
2136 ref_dir = mirror_git
2137
2138 elif os.path.exists(repo_git):
2139 ref_dir = repo_git
2140
2141 else:
2142 ref_dir = None
2143
2144 if ref_dir:
2145 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2146 os.path.join(ref_dir, 'objects') + '\n')
2147
Jimmie Westera0444582012-10-24 13:44:42 +02002148 self._UpdateHooks()
2149
2150 m = self.manifest.manifestProject.config
2151 for key in ['user.name', 'user.email']:
Anthony King7bdac712014-07-16 12:56:40 +01002152 if m.Has(key, include_defaults=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002153 self.config.SetString(key, m.GetString(key))
Jonathan Nieder93719792015-03-17 11:29:58 -07002154 if self.manifest.IsMirror:
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002155 self.config.SetString('core.bare', 'true')
2156 else:
2157 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002158
Jimmie Westera0444582012-10-24 13:44:42 +02002159 def _UpdateHooks(self):
2160 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002161 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002162
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002163 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002164 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002165 if not os.path.exists(hooks):
2166 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002167 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002168 name = os.path.basename(stock_hook)
2169
Victor Boivie65e0f352011-04-18 11:23:29 +02002170 if name in ('commit-msg',) and not self.remote.review \
2171 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002172 # Don't install a Gerrit Code Review hook if this
2173 # project does not appear to use it for reviews.
2174 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002175 # Since the manifest project is one of those, but also
2176 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002177 continue
2178
2179 dst = os.path.join(hooks, name)
2180 if os.path.islink(dst):
2181 continue
2182 if os.path.exists(dst):
2183 if filecmp.cmp(stock_hook, dst, shallow=False):
2184 os.remove(dst)
2185 else:
2186 _error("%s: Not replacing %s hook", self.relpath, name)
2187 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002188 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002189 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002190 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002191 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002192 raise GitError('filesystem must support symlinks')
2193 else:
2194 raise
2195
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002196 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002197 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002198 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002199 remote.url = self.remote.url
2200 remote.review = self.remote.review
2201 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002202
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002203 if self.worktree:
2204 remote.ResetFetch(mirror=False)
2205 else:
2206 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002207 remote.Save()
2208
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002209 def _InitMRef(self):
2210 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002211 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002212
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002213 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002214 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002215
2216 def _InitAnyMRef(self, ref):
2217 cur = self.bare_ref.symref(ref)
2218
2219 if self.revisionId:
2220 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2221 msg = 'manifest set to %s' % self.revisionId
2222 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002223 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002224 else:
2225 remote = self.GetRemote(self.remote.name)
2226 dst = remote.ToLocal(self.revisionExpr)
2227 if cur != dst:
2228 msg = 'manifest set to %s' % self.revisionExpr
2229 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002230
David James8d201162013-10-11 17:03:19 -07002231 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2232 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2233
2234 Args:
2235 gitdir: The bare git repository. Must already be initialized.
2236 dotgit: The repository you would like to initialize.
2237 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2238 Only one work tree can store refs under a given |gitdir|.
2239 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2240 This saves you the effort of initializing |dotgit| yourself.
2241 """
2242 # These objects can be shared between several working trees.
2243 symlink_files = ['description', 'info']
2244 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2245 if share_refs:
2246 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002247 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002248 symlink_dirs += ['logs', 'refs']
2249 to_symlink = symlink_files + symlink_dirs
2250
2251 to_copy = []
2252 if copy_all:
2253 to_copy = os.listdir(gitdir)
2254
2255 for name in set(to_copy).union(to_symlink):
2256 try:
2257 src = os.path.realpath(os.path.join(gitdir, name))
2258 dst = os.path.realpath(os.path.join(dotgit, name))
2259
2260 if os.path.lexists(dst) and not os.path.islink(dst):
2261 raise GitError('cannot overwrite a local work tree')
2262
2263 # If the source dir doesn't exist, create an empty dir.
2264 if name in symlink_dirs and not os.path.lexists(src):
2265 os.makedirs(src)
2266
Conley Owens80b87fe2014-05-09 17:13:44 -07002267 # If the source file doesn't exist, ensure the destination
2268 # file doesn't either.
2269 if name in symlink_files and not os.path.lexists(src):
2270 try:
2271 os.remove(dst)
2272 except OSError:
2273 pass
2274
David James8d201162013-10-11 17:03:19 -07002275 if name in to_symlink:
2276 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2277 elif copy_all and not os.path.islink(dst):
2278 if os.path.isdir(src):
2279 shutil.copytree(src, dst)
2280 elif os.path.isfile(src):
2281 shutil.copy(src, dst)
2282 except OSError as e:
2283 if e.errno == errno.EPERM:
2284 raise GitError('filesystem must support symlinks')
2285 else:
2286 raise
2287
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288 def _InitWorkTree(self):
2289 dotgit = os.path.join(self.worktree, '.git')
2290 if not os.path.exists(dotgit):
2291 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002292 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2293 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002294
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002295 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002296
2297 cmd = ['read-tree', '--reset', '-u']
2298 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002299 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002300 if GitCommand(self, cmd).Wait() != 0:
2301 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002302
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002303 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002304
2305 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002306 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002307
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002308 def _revlist(self, *args, **kw):
2309 a = []
2310 a.extend(args)
2311 a.append('--')
2312 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002313
2314 @property
2315 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002316 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002317
Julien Camperguedd654222014-01-09 16:21:37 +01002318 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2319 """Get logs between two revisions of this project."""
2320 comp = '..'
2321 if rev1:
2322 revs = [rev1]
2323 if rev2:
2324 revs.extend([comp, rev2])
2325 cmd = ['log', ''.join(revs)]
2326 out = DiffColoring(self.config)
2327 if out.is_on and color:
2328 cmd.append('--color')
2329 if oneline:
2330 cmd.append('--oneline')
2331
2332 try:
2333 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2334 if log.Wait() == 0:
2335 return log.stdout
2336 except GitError:
2337 # worktree may not exist if groups changed for example. In that case,
2338 # try in gitdir instead.
2339 if not os.path.exists(self.worktree):
2340 return self.bare_git.log(*cmd[1:])
2341 else:
2342 raise
2343 return None
2344
2345 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2346 """Get the list of logs from this revision to given revisionId"""
2347 logs = {}
2348 selfId = self.GetRevisionId(self._allrefs)
2349 toId = toProject.GetRevisionId(toProject._allrefs)
2350
2351 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2352 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2353 return logs
2354
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002355 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002356 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002357 self._project = project
2358 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002359 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002360
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002361 def LsOthers(self):
2362 p = GitCommand(self._project,
2363 ['ls-files',
2364 '-z',
2365 '--others',
2366 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002367 bare=False,
David James8d201162013-10-11 17:03:19 -07002368 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002369 capture_stdout=True,
2370 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002371 if p.Wait() == 0:
2372 out = p.stdout
2373 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002374 return out[:-1].split('\0') # pylint: disable=W1401
2375 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002376 return []
2377
2378 def DiffZ(self, name, *args):
2379 cmd = [name]
2380 cmd.append('-z')
2381 cmd.extend(args)
2382 p = GitCommand(self._project,
2383 cmd,
David James8d201162013-10-11 17:03:19 -07002384 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002385 bare=False,
2386 capture_stdout=True,
2387 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002388 try:
2389 out = p.process.stdout.read()
2390 r = {}
2391 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002392 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002393 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002394 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002395 info = next(out)
2396 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002397 except StopIteration:
2398 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002399
2400 class _Info(object):
2401 def __init__(self, path, omode, nmode, oid, nid, state):
2402 self.path = path
2403 self.src_path = None
2404 self.old_mode = omode
2405 self.new_mode = nmode
2406 self.old_id = oid
2407 self.new_id = nid
2408
2409 if len(state) == 1:
2410 self.status = state
2411 self.level = None
2412 else:
2413 self.status = state[:1]
2414 self.level = state[1:]
2415 while self.level.startswith('0'):
2416 self.level = self.level[1:]
2417
2418 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002419 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002420 if info.status in ('R', 'C'):
2421 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002422 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002423 r[info.path] = info
2424 return r
2425 finally:
2426 p.Wait()
2427
2428 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002429 if self._bare:
2430 path = os.path.join(self._project.gitdir, HEAD)
2431 else:
2432 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002433 try:
2434 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002435 except IOError as e:
2436 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002437 try:
2438 line = fd.read()
2439 finally:
2440 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302441 try:
2442 line = line.decode()
2443 except AttributeError:
2444 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002445 if line.startswith('ref: '):
2446 return line[5:-1]
2447 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002448
2449 def SetHead(self, ref, message=None):
2450 cmdv = []
2451 if message is not None:
2452 cmdv.extend(['-m', message])
2453 cmdv.append(HEAD)
2454 cmdv.append(ref)
2455 self.symbolic_ref(*cmdv)
2456
2457 def DetachHead(self, new, message=None):
2458 cmdv = ['--no-deref']
2459 if message is not None:
2460 cmdv.extend(['-m', message])
2461 cmdv.append(HEAD)
2462 cmdv.append(new)
2463 self.update_ref(*cmdv)
2464
2465 def UpdateRef(self, name, new, old=None,
2466 message=None,
2467 detach=False):
2468 cmdv = []
2469 if message is not None:
2470 cmdv.extend(['-m', message])
2471 if detach:
2472 cmdv.append('--no-deref')
2473 cmdv.append(name)
2474 cmdv.append(new)
2475 if old is not None:
2476 cmdv.append(old)
2477 self.update_ref(*cmdv)
2478
2479 def DeleteRef(self, name, old=None):
2480 if not old:
2481 old = self.rev_parse(name)
2482 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002483 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002484
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002485 def rev_list(self, *args, **kw):
2486 if 'format' in kw:
2487 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2488 else:
2489 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002490 cmdv.extend(args)
2491 p = GitCommand(self._project,
2492 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002493 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002494 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002495 capture_stdout=True,
2496 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002497 r = []
2498 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002499 if line[-1] == '\n':
2500 line = line[:-1]
2501 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002502 if p.Wait() != 0:
2503 raise GitError('%s rev-list %s: %s' % (
2504 self._project.name,
2505 str(args),
2506 p.stderr))
2507 return r
2508
2509 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002510 """Allow arbitrary git commands using pythonic syntax.
2511
2512 This allows you to do things like:
2513 git_obj.rev_parse('HEAD')
2514
2515 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2516 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002517 Any other positional arguments will be passed to the git command, and the
2518 following keyword arguments are supported:
2519 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002520
2521 Args:
2522 name: The name of the git command to call. Any '_' characters will
2523 be replaced with '-'.
2524
2525 Returns:
2526 A callable object that will try to call git with the named command.
2527 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002528 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002529 def runner(*args, **kwargs):
2530 cmdv = []
2531 config = kwargs.pop('config', None)
2532 for k in kwargs:
2533 raise TypeError('%s() got an unexpected keyword argument %r'
2534 % (name, k))
2535 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002536 if not git_require((1, 7, 2)):
2537 raise ValueError('cannot set config on command line for %s()'
2538 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302539 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002540 cmdv.append('-c')
2541 cmdv.append('%s=%s' % (k, v))
2542 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002543 cmdv.extend(args)
2544 p = GitCommand(self._project,
2545 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002546 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002547 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002548 capture_stdout=True,
2549 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002550 if p.Wait() != 0:
2551 raise GitError('%s %s: %s' % (
2552 self._project.name,
2553 name,
2554 p.stderr))
2555 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302556 try:
Conley Owensedd01512013-09-26 12:59:58 -07002557 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302558 except AttributeError:
2559 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002560 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2561 return r[:-1]
2562 return r
2563 return runner
2564
2565
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002566class _PriorSyncFailedError(Exception):
2567 def __str__(self):
2568 return 'prior sync failed; rebase still in progress'
2569
2570class _DirtyError(Exception):
2571 def __str__(self):
2572 return 'contains uncommitted changes'
2573
2574class _InfoMessage(object):
2575 def __init__(self, project, text):
2576 self.project = project
2577 self.text = text
2578
2579 def Print(self, syncbuf):
2580 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2581 syncbuf.out.nl()
2582
2583class _Failure(object):
2584 def __init__(self, project, why):
2585 self.project = project
2586 self.why = why
2587
2588 def Print(self, syncbuf):
2589 syncbuf.out.fail('error: %s/: %s',
2590 self.project.relpath,
2591 str(self.why))
2592 syncbuf.out.nl()
2593
2594class _Later(object):
2595 def __init__(self, project, action):
2596 self.project = project
2597 self.action = action
2598
2599 def Run(self, syncbuf):
2600 out = syncbuf.out
2601 out.project('project %s/', self.project.relpath)
2602 out.nl()
2603 try:
2604 self.action()
2605 out.nl()
2606 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002607 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002608 out.nl()
2609 return False
2610
2611class _SyncColoring(Coloring):
2612 def __init__(self, config):
2613 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002614 self.project = self.printer('header', attr='bold')
2615 self.info = self.printer('info')
2616 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002617
2618class SyncBuffer(object):
2619 def __init__(self, config, detach_head=False):
2620 self._messages = []
2621 self._failures = []
2622 self._later_queue1 = []
2623 self._later_queue2 = []
2624
2625 self.out = _SyncColoring(config)
2626 self.out.redirect(sys.stderr)
2627
2628 self.detach_head = detach_head
2629 self.clean = True
2630
2631 def info(self, project, fmt, *args):
2632 self._messages.append(_InfoMessage(project, fmt % args))
2633
2634 def fail(self, project, err=None):
2635 self._failures.append(_Failure(project, err))
2636 self.clean = False
2637
2638 def later1(self, project, what):
2639 self._later_queue1.append(_Later(project, what))
2640
2641 def later2(self, project, what):
2642 self._later_queue2.append(_Later(project, what))
2643
2644 def Finish(self):
2645 self._PrintMessages()
2646 self._RunLater()
2647 self._PrintMessages()
2648 return self.clean
2649
2650 def _RunLater(self):
2651 for q in ['_later_queue1', '_later_queue2']:
2652 if not self._RunQueue(q):
2653 return
2654
2655 def _RunQueue(self, queue):
2656 for m in getattr(self, queue):
2657 if not m.Run(self):
2658 self.clean = False
2659 return False
2660 setattr(self, queue, [])
2661 return True
2662
2663 def _PrintMessages(self):
2664 for m in self._messages:
2665 m.Print(self)
2666 for m in self._failures:
2667 m.Print(self)
2668
2669 self._messages = []
2670 self._failures = []
2671
2672
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002673class MetaProject(Project):
2674 """A special project housed under .repo.
2675 """
2676 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002677 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002678 manifest=manifest,
2679 name=name,
2680 gitdir=gitdir,
2681 objdir=gitdir,
2682 worktree=worktree,
2683 remote=RemoteSpec('origin'),
2684 relpath='.repo/%s' % name,
2685 revisionExpr='refs/heads/master',
2686 revisionId=None,
2687 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002688
2689 def PreSync(self):
2690 if self.Exists:
2691 cb = self.CurrentBranch
2692 if cb:
2693 base = self.GetBranch(cb).merge
2694 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002695 self.revisionExpr = base
2696 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002697
Anthony King7bdac712014-07-16 12:56:40 +01002698 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002699 """ Prepare MetaProject for manifest branch switch
2700 """
2701
2702 # detach and delete manifest branch, allowing a new
2703 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002704 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002705 self.Sync_LocalHalf(syncbuf)
2706 syncbuf.Finish()
2707
2708 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002709 ['update-ref', '-d', 'refs/heads/default'],
Anthony King7bdac712014-07-16 12:56:40 +01002710 capture_stdout=True,
2711 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002712
2713
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002714 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002715 def LastFetch(self):
2716 try:
2717 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2718 return os.path.getmtime(fh)
2719 except OSError:
2720 return 0
2721
2722 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002723 def HasChanges(self):
2724 """Has the remote received new commits not yet checked out?
2725 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002726 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002727 return False
2728
David Pursehouse8a68ff92012-09-24 12:15:13 +09002729 all_refs = self.bare_ref.all
2730 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002731 head = self.work_git.GetHead()
2732 if head.startswith(R_HEADS):
2733 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002734 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002735 except KeyError:
2736 head = None
2737
2738 if revid == head:
2739 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002740 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002741 return True
2742 return False