blob: b9a53dce4bf810b8a17b7fb47e7989265b108b9e [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
Doug Anderson37282b42011-03-04 11:54:18 -080016import traceback
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
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070029
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070031from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070032from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090033from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080034from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080035from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070036from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037
Shawn O. Pearced237b692009-04-17 18:49:50 -070038from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
David Pursehouse59bbb582013-05-17 10:49:33 +090040from pyversion import is_python3
41if not is_python3():
42 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053043 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090044 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053045
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070046def _lwrite(path, content):
47 lock = '%s.lock' % path
48
Chirayu Desai303a82f2014-08-19 22:57:17 +053049 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070050 try:
51 fd.write(content)
52 finally:
53 fd.close()
54
55 try:
56 os.rename(lock, path)
57 except OSError:
58 os.remove(lock)
59 raise
60
Shawn O. Pearce48244782009-04-16 08:25:57 -070061def _error(fmt, *args):
62 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070063 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070064
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070065def not_rev(r):
66 return '^' + r
67
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080068def sq(r):
69 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080070
Doug Anderson8ced8642011-01-10 14:16:30 -080071_project_hook_list = None
72def _ProjectHooks():
73 """List the hooks present in the 'hooks' directory.
74
75 These hooks are project hooks and are copied to the '.git/hooks' directory
76 of all subprojects.
77
78 This function caches the list of hooks (based on the contents of the
79 'repo/hooks' directory) on the first call.
80
81 Returns:
82 A list of absolute paths to all of the files in the hooks directory.
83 """
84 global _project_hook_list
85 if _project_hook_list is None:
Jesse Hall672cc492013-11-27 11:17:13 -080086 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087 d = os.path.join(d , 'hooks')
Chirayu Desai217ea7d2013-03-01 19:14:38 +053088 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
Doug Anderson8ced8642011-01-10 14:16:30 -080089 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080090
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080091
Shawn O. Pearce632768b2008-10-23 11:58:52 -070092class DownloadedChange(object):
93 _commit_cache = None
94
95 def __init__(self, project, base, change_id, ps_id, commit):
96 self.project = project
97 self.base = base
98 self.change_id = change_id
99 self.ps_id = ps_id
100 self.commit = commit
101
102 @property
103 def commits(self):
104 if self._commit_cache is None:
105 self._commit_cache = self.project.bare_git.rev_list(
106 '--abbrev=8',
107 '--abbrev-commit',
108 '--pretty=oneline',
109 '--reverse',
110 '--date-order',
111 not_rev(self.base),
112 self.commit,
113 '--')
114 return self._commit_cache
115
116
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700117class ReviewableBranch(object):
118 _commit_cache = None
119
120 def __init__(self, project, branch, base):
121 self.project = project
122 self.branch = branch
123 self.base = base
124
125 @property
126 def name(self):
127 return self.branch.name
128
129 @property
130 def commits(self):
131 if self._commit_cache is None:
132 self._commit_cache = self.project.bare_git.rev_list(
133 '--abbrev=8',
134 '--abbrev-commit',
135 '--pretty=oneline',
136 '--reverse',
137 '--date-order',
138 not_rev(self.base),
139 R_HEADS + self.name,
140 '--')
141 return self._commit_cache
142
143 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800144 def unabbrev_commits(self):
145 r = dict()
146 for commit in self.project.bare_git.rev_list(
147 not_rev(self.base),
148 R_HEADS + self.name,
149 '--'):
150 r[commit[0:8]] = commit
151 return r
152
153 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154 def date(self):
155 return self.project.bare_git.log(
156 '--pretty=format:%cd',
157 '-n', '1',
158 R_HEADS + self.name,
159 '--')
160
Bryan Jacobsf609f912013-05-06 13:36:24 -0400161 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700163 people,
Brian Harring435370c2012-07-28 15:37:04 -0700164 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400165 draft=draft,
166 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700168 def GetPublishedRefs(self):
169 refs = {}
170 output = self.project.bare_git.ls_remote(
171 self.branch.remote.SshReviewUrl(self.project.UserEmail),
172 'refs/changes/*')
173 for line in output.split('\n'):
174 try:
175 (sha, ref) = line.split()
176 refs[sha] = ref
177 except ValueError:
178 pass
179
180 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
182class StatusColoring(Coloring):
183 def __init__(self, config):
184 Coloring.__init__(self, config, 'status')
185 self.project = self.printer('header', attr = 'bold')
186 self.branch = self.printer('header', attr = 'bold')
187 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700188 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700189
190 self.added = self.printer('added', fg = 'green')
191 self.changed = self.printer('changed', fg = 'red')
192 self.untracked = self.printer('untracked', fg = 'red')
193
194
195class DiffColoring(Coloring):
196 def __init__(self, config):
197 Coloring.__init__(self, config, 'diff')
198 self.project = self.printer('header', attr = 'bold')
199
James W. Mills24c13082012-04-12 15:04:13 -0500200class _Annotation:
201 def __init__(self, name, value, keep):
202 self.name = name
203 self.value = value
204 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205
206class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800207 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 self.src = src
209 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800210 self.abs_src = abssrc
211 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700212
213 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800214 src = self.abs_src
215 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 # copy file if it does not exist or is out of date
217 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
218 try:
219 # remove existing file first, since it might be read-only
220 if os.path.exists(dest):
221 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400222 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200223 dest_dir = os.path.dirname(dest)
224 if not os.path.isdir(dest_dir):
225 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226 shutil.copy(src, dest)
227 # make the file read-only
228 mode = os.stat(dest)[stat.ST_MODE]
229 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
230 os.chmod(dest, mode)
231 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700232 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500234class _LinkFile:
235 def __init__(self, src, dest, abssrc, absdest):
236 self.src = src
237 self.dest = dest
238 self.abs_src = abssrc
239 self.abs_dest = absdest
240
241 def _Link(self):
242 src = self.abs_src
243 dest = self.abs_dest
244 # link file if it does not exist or is out of date
245 if not os.path.islink(dest) or os.readlink(dest) != src:
246 try:
247 # remove existing file first, since it might be read-only
248 if os.path.exists(dest):
249 os.remove(dest)
250 else:
251 dest_dir = os.path.dirname(dest)
252 if not os.path.isdir(dest_dir):
253 os.makedirs(dest_dir)
254 os.symlink(src, dest)
255 except IOError:
256 _error('Cannot link file %s to %s', src, dest)
257
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700258class RemoteSpec(object):
259 def __init__(self,
260 name,
261 url = None,
Anthony King36ea2fb2014-05-06 11:54:01 +0100262 review = None,
263 revision = None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700264 self.name = name
265 self.url = url
266 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100267 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700268
Doug Anderson37282b42011-03-04 11:54:18 -0800269class RepoHook(object):
270 """A RepoHook contains information about a script to run as a hook.
271
272 Hooks are used to run a python script before running an upload (for instance,
273 to run presubmit checks). Eventually, we may have hooks for other actions.
274
275 This shouldn't be confused with files in the 'repo/hooks' directory. Those
276 files are copied into each '.git/hooks' folder for each project. Repo-level
277 hooks are associated instead with repo actions.
278
279 Hooks are always python. When a hook is run, we will load the hook into the
280 interpreter and execute its main() function.
281 """
282 def __init__(self,
283 hook_type,
284 hooks_project,
285 topdir,
286 abort_if_user_denies=False):
287 """RepoHook constructor.
288
289 Params:
290 hook_type: A string representing the type of hook. This is also used
291 to figure out the name of the file containing the hook. For
292 example: 'pre-upload'.
293 hooks_project: The project containing the repo hooks. If you have a
294 manifest, this is manifest.repo_hooks_project. OK if this is None,
295 which will make the hook a no-op.
296 topdir: Repo's top directory (the one containing the .repo directory).
297 Scripts will run with CWD as this directory. If you have a manifest,
298 this is manifest.topdir
299 abort_if_user_denies: If True, we'll throw a HookError() if the user
300 doesn't allow us to run the hook.
301 """
302 self._hook_type = hook_type
303 self._hooks_project = hooks_project
304 self._topdir = topdir
305 self._abort_if_user_denies = abort_if_user_denies
306
307 # Store the full path to the script for convenience.
308 if self._hooks_project:
309 self._script_fullpath = os.path.join(self._hooks_project.worktree,
310 self._hook_type + '.py')
311 else:
312 self._script_fullpath = None
313
314 def _GetHash(self):
315 """Return a hash of the contents of the hooks directory.
316
317 We'll just use git to do this. This hash has the property that if anything
318 changes in the directory we will return a different has.
319
320 SECURITY CONSIDERATION:
321 This hash only represents the contents of files in the hook directory, not
322 any other files imported or called by hooks. Changes to imported files
323 can change the script behavior without affecting the hash.
324
325 Returns:
326 A string representing the hash. This will always be ASCII so that it can
327 be printed to the user easily.
328 """
329 assert self._hooks_project, "Must have hooks to calculate their hash."
330
331 # We will use the work_git object rather than just calling GetRevisionId().
332 # That gives us a hash of the latest checked in version of the files that
333 # the user will actually be executing. Specifically, GetRevisionId()
334 # doesn't appear to change even if a user checks out a different version
335 # of the hooks repo (via git checkout) nor if a user commits their own revs.
336 #
337 # NOTE: Local (non-committed) changes will not be factored into this hash.
338 # I think this is OK, since we're really only worried about warning the user
339 # about upstream changes.
340 return self._hooks_project.work_git.rev_parse('HEAD')
341
342 def _GetMustVerb(self):
343 """Return 'must' if the hook is required; 'should' if not."""
344 if self._abort_if_user_denies:
345 return 'must'
346 else:
347 return 'should'
348
349 def _CheckForHookApproval(self):
350 """Check to see whether this hook has been approved.
351
352 We'll look at the hash of all of the hooks. If this matches the hash that
353 the user last approved, we're done. If it doesn't, we'll ask the user
354 about approval.
355
356 Note that we ask permission for each individual hook even though we use
357 the hash of all hooks when detecting changes. We'd like the user to be
358 able to approve / deny each hook individually. We only use the hash of all
359 hooks because there is no other easy way to detect changes to local imports.
360
361 Returns:
362 True if this hook is approved to run; False otherwise.
363
364 Raises:
365 HookError: Raised if the user doesn't approve and abort_if_user_denies
366 was passed to the consturctor.
367 """
Doug Anderson37282b42011-03-04 11:54:18 -0800368 hooks_config = self._hooks_project.config
369 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
370
371 # Get the last hash that the user approved for this hook; may be None.
372 old_hash = hooks_config.GetString(git_approval_key)
373
374 # Get the current hash so we can tell if scripts changed since approval.
375 new_hash = self._GetHash()
376
377 if old_hash is not None:
378 # User previously approved hook and asked not to be prompted again.
379 if new_hash == old_hash:
380 # Approval matched. We're done.
381 return True
382 else:
383 # Give the user a reason why we're prompting, since they last told
384 # us to "never ask again".
385 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
386 self._hook_type)
387 else:
388 prompt = ''
389
390 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
391 if sys.stdout.isatty():
392 prompt += ('Repo %s run the script:\n'
393 ' %s\n'
394 '\n'
395 'Do you want to allow this script to run '
396 '(yes/yes-never-ask-again/NO)? ') % (
397 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530398 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900399 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800400
401 # User is doing a one-time approval.
402 if response in ('y', 'yes'):
403 return True
404 elif response == 'yes-never-ask-again':
405 hooks_config.SetString(git_approval_key, new_hash)
406 return True
407
408 # For anything else, we'll assume no approval.
409 if self._abort_if_user_denies:
410 raise HookError('You must allow the %s hook or use --no-verify.' %
411 self._hook_type)
412
413 return False
414
415 def _ExecuteHook(self, **kwargs):
416 """Actually execute the given hook.
417
418 This will run the hook's 'main' function in our python interpreter.
419
420 Args:
421 kwargs: Keyword arguments to pass to the hook. These are often specific
422 to the hook type. For instance, pre-upload hooks will contain
423 a project_list.
424 """
425 # Keep sys.path and CWD stashed away so that we can always restore them
426 # upon function exit.
427 orig_path = os.getcwd()
428 orig_syspath = sys.path
429
430 try:
431 # Always run hooks with CWD as topdir.
432 os.chdir(self._topdir)
433
434 # Put the hook dir as the first item of sys.path so hooks can do
435 # relative imports. We want to replace the repo dir as [0] so
436 # hooks can't import repo files.
437 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
438
439 # Exec, storing global context in the context dict. We catch exceptions
440 # and convert to a HookError w/ just the failing traceback.
441 context = {}
442 try:
Anthony King70f68902014-05-05 21:15:34 +0100443 exec(compile(open(self._script_fullpath).read(),
444 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800445 except Exception:
446 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
447 traceback.format_exc(), self._hook_type))
448
449 # Running the script should have defined a main() function.
450 if 'main' not in context:
451 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
452
453
454 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
455 # We don't actually want hooks to define their main with this argument--
456 # it's there to remind them that their hook should always take **kwargs.
457 # For instance, a pre-upload hook should be defined like:
458 # def main(project_list, **kwargs):
459 #
460 # This allows us to later expand the API without breaking old hooks.
461 kwargs = kwargs.copy()
462 kwargs['hook_should_take_kwargs'] = True
463
464 # Call the main function in the hook. If the hook should cause the
465 # build to fail, it will raise an Exception. We'll catch that convert
466 # to a HookError w/ just the failing traceback.
467 try:
468 context['main'](**kwargs)
469 except Exception:
470 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
471 'above.' % (
472 traceback.format_exc(), self._hook_type))
473 finally:
474 # Restore sys.path and CWD.
475 sys.path = orig_syspath
476 os.chdir(orig_path)
477
478 def Run(self, user_allows_all_hooks, **kwargs):
479 """Run the hook.
480
481 If the hook doesn't exist (because there is no hooks project or because
482 this particular hook is not enabled), this is a no-op.
483
484 Args:
485 user_allows_all_hooks: If True, we will never prompt about running the
486 hook--we'll just assume it's OK to run it.
487 kwargs: Keyword arguments to pass to the hook. These are often specific
488 to the hook type. For instance, pre-upload hooks will contain
489 a project_list.
490
491 Raises:
492 HookError: If there was a problem finding the hook or the user declined
493 to run a required hook (from _CheckForHookApproval).
494 """
495 # No-op if there is no hooks project or if hook is disabled.
496 if ((not self._hooks_project) or
497 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
498 return
499
500 # Bail with a nice error if we can't find the hook.
501 if not os.path.isfile(self._script_fullpath):
502 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
503
504 # Make sure the user is OK with running the hook.
505 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
506 return
507
508 # Run the hook with the same version of python we're using.
509 self._ExecuteHook(**kwargs)
510
511
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512class Project(object):
513 def __init__(self,
514 manifest,
515 name,
516 remote,
517 gitdir,
David James8d201162013-10-11 17:03:19 -0700518 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700519 worktree,
520 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700521 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800522 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700523 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700524 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700525 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800526 sync_s = False,
David Pursehouseede7f122012-11-27 22:25:30 +0900527 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800528 upstream = None,
529 parent = None,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400530 is_derived = False,
531 dest_branch = None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800532 """Init a Project object.
533
534 Args:
535 manifest: The XmlManifest object.
536 name: The `name` attribute of manifest.xml's project element.
537 remote: RemoteSpec object specifying its remote's properties.
538 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700539 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800540 worktree: Absolute path of git working tree.
541 relpath: Relative path of git working tree to repo's top directory.
542 revisionExpr: The `revision` attribute of manifest.xml's project element.
543 revisionId: git commit id for checking out.
544 rebase: The `rebase` attribute of manifest.xml's project element.
545 groups: The `groups` attribute of manifest.xml's project element.
546 sync_c: The `sync-c` attribute of manifest.xml's project element.
547 sync_s: The `sync-s` attribute of manifest.xml's project element.
548 upstream: The `upstream` attribute of manifest.xml's project element.
549 parent: The parent Project object.
550 is_derived: False if the project was explicitly defined in the manifest;
551 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400552 dest_branch: The branch to which to push changes for review by default.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800553 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700554 self.manifest = manifest
555 self.name = name
556 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800557 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700558 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800559 if worktree:
560 self.worktree = worktree.replace('\\', '/')
561 else:
562 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700563 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700564 self.revisionExpr = revisionExpr
565
566 if revisionId is None \
567 and revisionExpr \
568 and IsId(revisionExpr):
569 self.revisionId = revisionExpr
570 else:
571 self.revisionId = revisionId
572
Mike Pontillod3153822012-02-28 11:53:24 -0800573 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700574 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700575 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800576 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900577 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700578 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800579 self.parent = parent
580 self.is_derived = is_derived
581 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800582
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700584 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500585 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500586 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700587 self.config = GitConfig.ForRepository(
588 gitdir = self.gitdir,
589 defaults = self.manifest.globalConfig)
590
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800591 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700592 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800593 else:
594 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700595 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700596 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700597 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400598 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700599
Doug Anderson37282b42011-03-04 11:54:18 -0800600 # This will be filled in if a project is later identified to be the
601 # project containing repo hooks.
602 self.enabled_repo_hooks = []
603
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700604 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800605 def Derived(self):
606 return self.is_derived
607
608 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700609 def Exists(self):
610 return os.path.isdir(self.gitdir)
611
612 @property
613 def CurrentBranch(self):
614 """Obtain the name of the currently checked out branch.
615 The branch name omits the 'refs/heads/' prefix.
616 None is returned if the project is on a detached HEAD.
617 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700618 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700619 if b.startswith(R_HEADS):
620 return b[len(R_HEADS):]
621 return None
622
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700623 def IsRebaseInProgress(self):
624 w = self.worktree
625 g = os.path.join(w, '.git')
626 return os.path.exists(os.path.join(g, 'rebase-apply')) \
627 or os.path.exists(os.path.join(g, 'rebase-merge')) \
628 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200629
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700630 def IsDirty(self, consider_untracked=True):
631 """Is the working directory modified in some way?
632 """
633 self.work_git.update_index('-q',
634 '--unmerged',
635 '--ignore-missing',
636 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900637 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700638 return True
639 if self.work_git.DiffZ('diff-files'):
640 return True
641 if consider_untracked and self.work_git.LsOthers():
642 return True
643 return False
644
645 _userident_name = None
646 _userident_email = None
647
648 @property
649 def UserName(self):
650 """Obtain the user's personal name.
651 """
652 if self._userident_name is None:
653 self._LoadUserIdentity()
654 return self._userident_name
655
656 @property
657 def UserEmail(self):
658 """Obtain the user's email address. This is very likely
659 to be their Gerrit login.
660 """
661 if self._userident_email is None:
662 self._LoadUserIdentity()
663 return self._userident_email
664
665 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900666 u = self.bare_git.var('GIT_COMMITTER_IDENT')
667 m = re.compile("^(.*) <([^>]*)> ").match(u)
668 if m:
669 self._userident_name = m.group(1)
670 self._userident_email = m.group(2)
671 else:
672 self._userident_name = ''
673 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700674
675 def GetRemote(self, name):
676 """Get the configuration for a single remote.
677 """
678 return self.config.GetRemote(name)
679
680 def GetBranch(self, name):
681 """Get the configuration for a single branch.
682 """
683 return self.config.GetBranch(name)
684
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700685 def GetBranches(self):
686 """Get all existing local branches.
687 """
688 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900689 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700690 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700691
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530692 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700693 if name.startswith(R_HEADS):
694 name = name[len(R_HEADS):]
695 b = self.GetBranch(name)
696 b.current = name == current
697 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900698 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700699 heads[name] = b
700
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530701 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700702 if name.startswith(R_PUB):
703 name = name[len(R_PUB):]
704 b = heads.get(name)
705 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900706 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700707
708 return heads
709
Colin Cross5acde752012-03-28 20:15:45 -0700710 def MatchesGroups(self, manifest_groups):
711 """Returns true if the manifest groups specified at init should cause
712 this project to be synced.
713 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700714 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700715
716 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700717 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700718 manifest_groups: "-group1,group2"
719 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500720
721 The special manifest group "default" will match any project that
722 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700723 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500724 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700725 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500726 if not 'notdefault' in expanded_project_groups:
727 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700728
Conley Owens971de8e2012-04-16 10:36:08 -0700729 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700730 for group in expanded_manifest_groups:
731 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700732 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700733 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700734 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700735
Conley Owens971de8e2012-04-16 10:36:08 -0700736 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737
738## Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700739 def UncommitedFiles(self, get_all=True):
740 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700742 Args:
743 get_all: a boolean, if True - get information about all different
744 uncommitted files. If False - return as soon as any kind of
745 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500746 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700747 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500748 self.work_git.update_index('-q',
749 '--unmerged',
750 '--ignore-missing',
751 '--refresh')
752 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700753 details.append("rebase in progress")
754 if not get_all:
755 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500756
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700757 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
758 if changes:
759 details.extend(changes)
760 if not get_all:
761 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500762
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700763 changes = self.work_git.DiffZ('diff-files').keys()
764 if changes:
765 details.extend(changes)
766 if not get_all:
767 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500768
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700769 changes = self.work_git.LsOthers()
770 if changes:
771 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500772
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700773 return details
774
775 def HasChanges(self):
776 """Returns true if there are uncommitted changes.
777 """
778 if self.UncommitedFiles(get_all=False):
779 return True
780 else:
781 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500782
Terence Haddock4655e812011-03-31 12:33:34 +0200783 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200785
786 Args:
787 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788 """
789 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200790 if output_redir == None:
791 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700792 print(file=output_redir)
793 print('project %s/' % self.relpath, file=output_redir)
794 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700795 return
796
797 self.work_git.update_index('-q',
798 '--unmerged',
799 '--ignore-missing',
800 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700801 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700802 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
803 df = self.work_git.DiffZ('diff-files')
804 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100805 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700806 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807
808 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200809 if not output_redir == None:
810 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811 out.project('project %-40s', self.relpath + '/')
812
813 branch = self.CurrentBranch
814 if branch is None:
815 out.nobranch('(*** NO BRANCH ***)')
816 else:
817 out.branch('branch %s', branch)
818 out.nl()
819
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700820 if rb:
821 out.important('prior sync failed; rebase still in progress')
822 out.nl()
823
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824 paths = list()
825 paths.extend(di.keys())
826 paths.extend(df.keys())
827 paths.extend(do)
828
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530829 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900830 try:
831 i = di[p]
832 except KeyError:
833 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900835 try:
836 f = df[p]
837 except KeyError:
838 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200839
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900840 if i:
841 i_status = i.status.upper()
842 else:
843 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900845 if f:
846 f_status = f.status.lower()
847 else:
848 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849
850 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800851 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700852 i.src_path, p, i.level)
853 else:
854 line = ' %s%s\t%s' % (i_status, f_status, p)
855
856 if i and not f:
857 out.added('%s', line)
858 elif (i and f) or (not i and f):
859 out.changed('%s', line)
860 elif not i and not f:
861 out.untracked('%s', line)
862 else:
863 out.write('%s', line)
864 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200865
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700866 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867
pelyad67872d2012-03-28 14:49:58 +0300868 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 """Prints the status of the repository to stdout.
870 """
871 out = DiffColoring(self.config)
872 cmd = ['diff']
873 if out.is_on:
874 cmd.append('--color')
875 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300876 if absolute_paths:
877 cmd.append('--src-prefix=a/%s/' % self.relpath)
878 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879 cmd.append('--')
880 p = GitCommand(self,
881 cmd,
882 capture_stdout = True,
883 capture_stderr = True)
884 has_diff = False
885 for line in p.process.stdout:
886 if not has_diff:
887 out.nl()
888 out.project('project %s/' % self.relpath)
889 out.nl()
890 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700891 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892 p.Wait()
893
894
895## Publish / Upload ##
896
David Pursehouse8a68ff92012-09-24 12:15:13 +0900897 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898 """Was the branch published (uploaded) for code review?
899 If so, returns the SHA-1 hash of the last published
900 state for the branch.
901 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700902 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900903 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700904 try:
905 return self.bare_git.rev_parse(key)
906 except GitError:
907 return None
908 else:
909 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900910 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700911 except KeyError:
912 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913
David Pursehouse8a68ff92012-09-24 12:15:13 +0900914 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700915 """Prunes any stale published refs.
916 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900917 if all_refs is None:
918 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919 heads = set()
920 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530921 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 if name.startswith(R_HEADS):
923 heads.add(name)
924 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900925 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530927 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928 n = name[len(R_PUB):]
929 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900930 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700932 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933 """List any branches which can be uploaded for review.
934 """
935 heads = {}
936 pubed = {}
937
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530938 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900940 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900942 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943
944 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530945 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900946 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700948 if selected_branch and branch != selected_branch:
949 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800951 rb = self.GetUploadableBranch(branch)
952 if rb:
953 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954 return ready
955
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800956 def GetUploadableBranch(self, branch_name):
957 """Get a single uploadable branch, or None.
958 """
959 branch = self.GetBranch(branch_name)
960 base = branch.LocalMerge
961 if branch.LocalMerge:
962 rb = ReviewableBranch(self, branch, base)
963 if rb.commits:
964 return rb
965 return None
966
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700967 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700968 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700969 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400970 draft=False,
971 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 """Uploads the named branch for code review.
973 """
974 if branch is None:
975 branch = self.CurrentBranch
976 if branch is None:
977 raise GitError('not currently on a branch')
978
979 branch = self.GetBranch(branch)
980 if not branch.LocalMerge:
981 raise GitError('branch %s does not track a remote' % branch.name)
982 if not branch.remote.review:
983 raise GitError('remote %s has no review url' % branch.remote.name)
984
Bryan Jacobsf609f912013-05-06 13:36:24 -0400985 if dest_branch is None:
986 dest_branch = self.dest_branch
987 if dest_branch is None:
988 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 if not dest_branch.startswith(R_HEADS):
990 dest_branch = R_HEADS + dest_branch
991
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800992 if not branch.remote.projectname:
993 branch.remote.projectname = self.name
994 branch.remote.Save()
995
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800996 url = branch.remote.ReviewUrl(self.UserEmail)
997 if url is None:
998 raise UploadError('review not configured')
999 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001000
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001001 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001002 rp = ['gerrit receive-pack']
1003 for e in people[0]:
1004 rp.append('--reviewer=%s' % sq(e))
1005 for e in people[1]:
1006 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001007 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001008
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001009 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001010
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001011 if dest_branch.startswith(R_HEADS):
1012 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001013
1014 upload_type = 'for'
1015 if draft:
1016 upload_type = 'drafts'
1017
1018 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1019 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001020 if auto_topic:
1021 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001022 if not url.startswith('ssh://'):
1023 rp = ['r=%s' % p for p in people[0]] + \
1024 ['cc=%s' % p for p in people[1]]
1025 if rp:
1026 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001027 cmd.append(ref_spec)
1028
1029 if GitCommand(self, cmd, bare = True).Wait() != 0:
1030 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031
1032 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1033 self.bare_git.UpdateRef(R_PUB + branch.name,
1034 R_HEADS + branch.name,
1035 message = msg)
1036
1037
1038## Sync ##
1039
Julien Campergue335f5ef2013-10-16 11:02:35 +02001040 def _ExtractArchive(self, tarpath, path=None):
1041 """Extract the given tar on its current location
1042
1043 Args:
1044 - tarpath: The path to the actual tar file
1045
1046 """
1047 try:
1048 with tarfile.open(tarpath, 'r') as tar:
1049 tar.extractall(path=path)
1050 return True
1051 except (IOError, tarfile.TarError) as e:
1052 print("error: Cannot extract archive %s: "
1053 "%s" % (tarpath, str(e)), file=sys.stderr)
1054 return False
1055
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001056 def Sync_NetworkHalf(self,
1057 quiet=False,
1058 is_new=None,
1059 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001060 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001061 no_tags=False,
1062 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063 """Perform only the network IO portion of the sync process.
1064 Local working directory/branch state is not affected.
1065 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001066 if archive and not isinstance(self, MetaProject):
1067 if self.remote.url.startswith(('http://', 'https://')):
1068 print("error: %s: Cannot fetch archives from http/https "
1069 "remotes." % self.name, file=sys.stderr)
1070 return False
1071
1072 name = self.relpath.replace('\\', '/')
1073 name = name.replace('/', '_')
1074 tarpath = '%s.tar' % name
1075 topdir = self.manifest.topdir
1076
1077 try:
1078 self._FetchArchive(tarpath, cwd=topdir)
1079 except GitError as e:
1080 print('error: %s' % str(e), file=sys.stderr)
1081 return False
1082
1083 # From now on, we only need absolute tarpath
1084 tarpath = os.path.join(topdir, tarpath)
1085
1086 if not self._ExtractArchive(tarpath, path=topdir):
1087 return False
1088 try:
1089 os.remove(tarpath)
1090 except OSError as e:
1091 print("warn: Cannot remove archive %s: "
1092 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001093 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001094 return True
1095
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001096 if is_new is None:
1097 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001098 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001099 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001100 else:
1101 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001103
1104 if is_new:
1105 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1106 try:
1107 fd = open(alt, 'rb')
1108 try:
1109 alt_dir = fd.readline().rstrip()
1110 finally:
1111 fd.close()
1112 except IOError:
1113 alt_dir = None
1114 else:
1115 alt_dir = None
1116
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001117 if clone_bundle \
1118 and alt_dir is None \
1119 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001120 is_new = False
1121
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001122 if not current_branch_only:
1123 if self.sync_c:
1124 current_branch_only = True
1125 elif not self.manifest._loaded:
1126 # Manifest cannot check defaults until it syncs.
1127 current_branch_only = False
1128 elif self.manifest.default.sync_c:
1129 current_branch_only = True
1130
Conley Owens666d5342014-05-01 13:09:57 -07001131 has_sha1 = ID_RE.match(self.revisionExpr) and self._CheckForSha1()
1132 if (not has_sha1 #Need to fetch since we don't already have this revision
1133 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1134 current_branch_only=current_branch_only,
1135 no_tags=no_tags)):
1136 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001137
1138 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001139 self._InitMRef()
1140 else:
1141 self._InitMirrorHead()
1142 try:
1143 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1144 except OSError:
1145 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001147
1148 def PostRepoUpgrade(self):
1149 self._InitHooks()
1150
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001151 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001152 for copyfile in self.copyfiles:
1153 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001154 for linkfile in self.linkfiles:
1155 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001156
Julien Camperguedd654222014-01-09 16:21:37 +01001157 def GetCommitRevisionId(self):
1158 """Get revisionId of a commit.
1159
1160 Use this method instead of GetRevisionId to get the id of the commit rather
1161 than the id of the current git object (for example, a tag)
1162
1163 """
1164 if not self.revisionExpr.startswith(R_TAGS):
1165 return self.GetRevisionId(self._allrefs)
1166
1167 try:
1168 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1169 except GitError:
1170 raise ManifestInvalidRevisionError(
1171 'revision %s in %s not found' % (self.revisionExpr,
1172 self.name))
1173
David Pursehouse8a68ff92012-09-24 12:15:13 +09001174 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001175 if self.revisionId:
1176 return self.revisionId
1177
1178 rem = self.GetRemote(self.remote.name)
1179 rev = rem.ToLocal(self.revisionExpr)
1180
David Pursehouse8a68ff92012-09-24 12:15:13 +09001181 if all_refs is not None and rev in all_refs:
1182 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001183
1184 try:
1185 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1186 except GitError:
1187 raise ManifestInvalidRevisionError(
1188 'revision %s in %s not found' % (self.revisionExpr,
1189 self.name))
1190
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001191 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192 """Perform only the local IO portion of the sync process.
1193 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194 """
David James8d201162013-10-11 17:03:19 -07001195 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001196 all_refs = self.bare_ref.all
1197 self.CleanPublishedCache(all_refs)
1198 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001199
David Pursehouse1d947b32012-10-25 12:23:11 +09001200 def _doff():
1201 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001202 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001203
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001204 head = self.work_git.GetHead()
1205 if head.startswith(R_HEADS):
1206 branch = head[len(R_HEADS):]
1207 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001208 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001209 except KeyError:
1210 head = None
1211 else:
1212 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001214 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001215 # Currently on a detached HEAD. The user is assumed to
1216 # not have any local modifications worth worrying about.
1217 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001218 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001219 syncbuf.fail(self, _PriorSyncFailedError())
1220 return
1221
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001222 if head == revid:
1223 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001224 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001225 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001226 if not syncbuf.detach_head:
1227 return
1228 else:
1229 lost = self._revlist(not_rev(revid), HEAD)
1230 if lost:
1231 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001232
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001233 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001234 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001235 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001236 syncbuf.fail(self, e)
1237 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001238 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001239 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001240
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001241 if head == revid:
1242 # No changes; don't do anything further.
1243 #
1244 return
1245
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001246 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001248 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001249 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001250 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001251 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001252 syncbuf.info(self,
1253 "leaving %s; does not track upstream",
1254 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001255 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001256 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001257 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001258 syncbuf.fail(self, e)
1259 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001260 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001261 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001262
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001263 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001264 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001265 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001266 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001267 if not_merged:
1268 if upstream_gain:
1269 # The user has published this branch and some of those
1270 # commits are not yet merged upstream. We do not want
1271 # to rewrite the published commits so we punt.
1272 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001273 syncbuf.fail(self,
1274 "branch %s is published (but not merged) and is now %d commits behind"
1275 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001276 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001277 elif pub == head:
1278 # All published commits are merged, and thus we are a
1279 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001280 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001281 syncbuf.later1(self, _doff)
1282 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001283
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001284 # Examine the local commits not in the remote. Find the
1285 # last one attributed to this user, if any.
1286 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001287 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001288 last_mine = None
1289 cnt_mine = 0
1290 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301291 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001292 if committer_email == self.UserEmail:
1293 last_mine = commit_id
1294 cnt_mine += 1
1295
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001296 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001297 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298
1299 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001300 syncbuf.fail(self, _DirtyError())
1301 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001303 # If the upstream switched on us, warn the user.
1304 #
1305 if branch.merge != self.revisionExpr:
1306 if branch.merge and self.revisionExpr:
1307 syncbuf.info(self,
1308 'manifest switched %s...%s',
1309 branch.merge,
1310 self.revisionExpr)
1311 elif branch.merge:
1312 syncbuf.info(self,
1313 'manifest no longer tracks %s',
1314 branch.merge)
1315
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001316 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001318 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001320 syncbuf.info(self,
1321 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001322 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001324 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001325 if not ID_RE.match(self.revisionExpr):
1326 # in case of manifest sync the revisionExpr might be a SHA1
1327 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328 branch.Save()
1329
Mike Pontillod3153822012-02-28 11:53:24 -08001330 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001331 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001332 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001333 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001334 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001335 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001336 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001337 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001338 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001339 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001340 syncbuf.fail(self, e)
1341 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001343 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001345 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001346 # dest should already be an absolute path, but src is project relative
1347 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001348 abssrc = os.path.join(self.worktree, src)
1349 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001351 def AddLinkFile(self, src, dest, absdest):
1352 # dest should already be an absolute path, but src is project relative
1353 # make src an absolute path
1354 abssrc = os.path.join(self.worktree, src)
1355 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1356
James W. Mills24c13082012-04-12 15:04:13 -05001357 def AddAnnotation(self, name, value, keep):
1358 self.annotations.append(_Annotation(name, value, keep))
1359
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001360 def DownloadPatchSet(self, change_id, patch_id):
1361 """Download a single patch set of a single change to FETCH_HEAD.
1362 """
1363 remote = self.GetRemote(self.remote.name)
1364
1365 cmd = ['fetch', remote.name]
1366 cmd.append('refs/changes/%2.2d/%d/%d' \
1367 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001368 if GitCommand(self, cmd, bare=True).Wait() != 0:
1369 return None
1370 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001371 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001372 change_id,
1373 patch_id,
1374 self.bare_git.rev_parse('FETCH_HEAD'))
1375
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376
1377## Branch Management ##
1378
1379 def StartBranch(self, name):
1380 """Create a new branch off the manifest's revision.
1381 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001382 head = self.work_git.GetHead()
1383 if head == (R_HEADS + name):
1384 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385
David Pursehouse8a68ff92012-09-24 12:15:13 +09001386 all_refs = self.bare_ref.all
1387 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001388 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001389 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001390 capture_stdout = True,
1391 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001392
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001393 branch = self.GetBranch(name)
1394 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001395 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001396 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001397
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001398 if head.startswith(R_HEADS):
1399 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001400 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001401 except KeyError:
1402 head = None
1403
1404 if revid and head and revid == head:
1405 ref = os.path.join(self.gitdir, R_HEADS + name)
1406 try:
1407 os.makedirs(os.path.dirname(ref))
1408 except OSError:
1409 pass
1410 _lwrite(ref, '%s\n' % revid)
1411 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1412 'ref: %s%s\n' % (R_HEADS, name))
1413 branch.Save()
1414 return True
1415
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001416 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001417 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001418 capture_stdout = True,
1419 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001420 branch.Save()
1421 return True
1422 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001423
Wink Saville02d79452009-04-10 13:01:24 -07001424 def CheckoutBranch(self, name):
1425 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001426
1427 Args:
1428 name: The name of the branch to checkout.
1429
1430 Returns:
1431 True if the checkout succeeded; False if it didn't; None if the branch
1432 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001433 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001434 rev = R_HEADS + name
1435 head = self.work_git.GetHead()
1436 if head == rev:
1437 # Already on the branch
1438 #
1439 return True
Wink Saville02d79452009-04-10 13:01:24 -07001440
David Pursehouse8a68ff92012-09-24 12:15:13 +09001441 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001442 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001443 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001444 except KeyError:
1445 # Branch does not exist in this project
1446 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001447 return None
Wink Saville02d79452009-04-10 13:01:24 -07001448
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001449 if head.startswith(R_HEADS):
1450 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001451 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001452 except KeyError:
1453 head = None
1454
1455 if head == revid:
1456 # Same revision; just update HEAD to point to the new
1457 # target branch, but otherwise take no other action.
1458 #
1459 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1460 'ref: %s%s\n' % (R_HEADS, name))
1461 return True
1462
1463 return GitCommand(self,
1464 ['checkout', name, '--'],
1465 capture_stdout = True,
1466 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001467
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001468 def AbandonBranch(self, name):
1469 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001470
1471 Args:
1472 name: The name of the branch to abandon.
1473
1474 Returns:
1475 True if the abandon succeeded; False if it didn't; None if the branch
1476 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001477 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001478 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001479 all_refs = self.bare_ref.all
1480 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001481 # Doesn't exist
1482 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001483
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001484 head = self.work_git.GetHead()
1485 if head == rev:
1486 # We can't destroy the branch while we are sitting
1487 # on it. Switch to a detached HEAD.
1488 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001489 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001490
David Pursehouse8a68ff92012-09-24 12:15:13 +09001491 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001492 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001493 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1494 '%s\n' % revid)
1495 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001496 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001497
1498 return GitCommand(self,
1499 ['branch', '-D', name],
1500 capture_stdout = True,
1501 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001502
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001503 def PruneHeads(self):
1504 """Prune any topic branches already merged into upstream.
1505 """
1506 cb = self.CurrentBranch
1507 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001508 left = self._allrefs
1509 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001510 if name.startswith(R_HEADS):
1511 name = name[len(R_HEADS):]
1512 if cb is None or name != cb:
1513 kill.append(name)
1514
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001515 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001516 if cb is not None \
1517 and not self._revlist(HEAD + '...' + rev) \
1518 and not self.IsDirty(consider_untracked = False):
1519 self.work_git.DetachHead(HEAD)
1520 kill.append(cb)
1521
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001522 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001523 old = self.bare_git.GetHead()
1524 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001525 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1526
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001527 try:
1528 self.bare_git.DetachHead(rev)
1529
1530 b = ['branch', '-d']
1531 b.extend(kill)
1532 b = GitCommand(self, b, bare=True,
1533 capture_stdout=True,
1534 capture_stderr=True)
1535 b.Wait()
1536 finally:
1537 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001538 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001539
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001540 for branch in kill:
1541 if (R_HEADS + branch) not in left:
1542 self.CleanPublishedCache()
1543 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001544
1545 if cb and cb not in kill:
1546 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001547 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001548
1549 kept = []
1550 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001551 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001552 branch = self.GetBranch(branch)
1553 base = branch.LocalMerge
1554 if not base:
1555 base = rev
1556 kept.append(ReviewableBranch(self, branch, base))
1557 return kept
1558
1559
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001560## Submodule Management ##
1561
1562 def GetRegisteredSubprojects(self):
1563 result = []
1564 def rec(subprojects):
1565 if not subprojects:
1566 return
1567 result.extend(subprojects)
1568 for p in subprojects:
1569 rec(p.subprojects)
1570 rec(self.subprojects)
1571 return result
1572
1573 def _GetSubmodules(self):
1574 # Unfortunately we cannot call `git submodule status --recursive` here
1575 # because the working tree might not exist yet, and it cannot be used
1576 # without a working tree in its current implementation.
1577
1578 def get_submodules(gitdir, rev):
1579 # Parse .gitmodules for submodule sub_paths and sub_urls
1580 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1581 if not sub_paths:
1582 return []
1583 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1584 # revision of submodule repository
1585 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1586 submodules = []
1587 for sub_path, sub_url in zip(sub_paths, sub_urls):
1588 try:
1589 sub_rev = sub_revs[sub_path]
1590 except KeyError:
1591 # Ignore non-exist submodules
1592 continue
1593 submodules.append((sub_rev, sub_path, sub_url))
1594 return submodules
1595
1596 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1597 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1598 def parse_gitmodules(gitdir, rev):
1599 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1600 try:
1601 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1602 bare = True, gitdir = gitdir)
1603 except GitError:
1604 return [], []
1605 if p.Wait() != 0:
1606 return [], []
1607
1608 gitmodules_lines = []
1609 fd, temp_gitmodules_path = tempfile.mkstemp()
1610 try:
1611 os.write(fd, p.stdout)
1612 os.close(fd)
1613 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1614 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1615 bare = True, gitdir = gitdir)
1616 if p.Wait() != 0:
1617 return [], []
1618 gitmodules_lines = p.stdout.split('\n')
1619 except GitError:
1620 return [], []
1621 finally:
1622 os.remove(temp_gitmodules_path)
1623
1624 names = set()
1625 paths = {}
1626 urls = {}
1627 for line in gitmodules_lines:
1628 if not line:
1629 continue
1630 m = re_path.match(line)
1631 if m:
1632 names.add(m.group(1))
1633 paths[m.group(1)] = m.group(2)
1634 continue
1635 m = re_url.match(line)
1636 if m:
1637 names.add(m.group(1))
1638 urls[m.group(1)] = m.group(2)
1639 continue
1640 names = sorted(names)
1641 return ([paths.get(name, '') for name in names],
1642 [urls.get(name, '') for name in names])
1643
1644 def git_ls_tree(gitdir, rev, paths):
1645 cmd = ['ls-tree', rev, '--']
1646 cmd.extend(paths)
1647 try:
1648 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1649 bare = True, gitdir = gitdir)
1650 except GitError:
1651 return []
1652 if p.Wait() != 0:
1653 return []
1654 objects = {}
1655 for line in p.stdout.split('\n'):
1656 if not line.strip():
1657 continue
1658 object_rev, object_path = line.split()[2:4]
1659 objects[object_path] = object_rev
1660 return objects
1661
1662 try:
1663 rev = self.GetRevisionId()
1664 except GitError:
1665 return []
1666 return get_submodules(self.gitdir, rev)
1667
1668 def GetDerivedSubprojects(self):
1669 result = []
1670 if not self.Exists:
1671 # If git repo does not exist yet, querying its submodules will
1672 # mess up its states; so return here.
1673 return result
1674 for rev, path, url in self._GetSubmodules():
1675 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001676 relpath, worktree, gitdir, objdir = \
1677 self.manifest.GetSubprojectPaths(self, name, path)
1678 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001679 if project:
1680 result.extend(project.GetDerivedSubprojects())
1681 continue
David James8d201162013-10-11 17:03:19 -07001682
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001683 remote = RemoteSpec(self.remote.name,
1684 url = url,
Anthony King36ea2fb2014-05-06 11:54:01 +01001685 review = self.remote.review,
1686 revision = self.remote.revision)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001687 subproject = Project(manifest = self.manifest,
1688 name = name,
1689 remote = remote,
1690 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001691 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001692 worktree = worktree,
1693 relpath = relpath,
1694 revisionExpr = self.revisionExpr,
1695 revisionId = rev,
1696 rebase = self.rebase,
1697 groups = self.groups,
1698 sync_c = self.sync_c,
1699 sync_s = self.sync_s,
1700 parent = self,
1701 is_derived = True)
1702 result.append(subproject)
1703 result.extend(subproject.GetDerivedSubprojects())
1704 return result
1705
1706
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001707## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001708 def _CheckForSha1(self):
1709 try:
1710 # if revision (sha or tag) is not present then following function
1711 # throws an error.
1712 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1713 return True
1714 except GitError:
1715 # There is no such persistent revision. We have to fetch it.
1716 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001717
Julien Campergue335f5ef2013-10-16 11:02:35 +02001718 def _FetchArchive(self, tarpath, cwd=None):
1719 cmd = ['archive', '-v', '-o', tarpath]
1720 cmd.append('--remote=%s' % self.remote.url)
1721 cmd.append('--prefix=%s/' % self.relpath)
1722 cmd.append(self.revisionExpr)
1723
1724 command = GitCommand(self, cmd, cwd=cwd,
1725 capture_stdout=True,
1726 capture_stderr=True)
1727
1728 if command.Wait() != 0:
1729 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1730
Conley Owens80b87fe2014-05-09 17:13:44 -07001731
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001732 def _RemoteFetch(self, name=None,
1733 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001734 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001735 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001736 alt_dir=None,
1737 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001738
1739 is_sha1 = False
1740 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001741 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001742
David Pursehouse9bc422f2014-04-15 10:28:56 +09001743 # The depth should not be used when fetching to a mirror because
1744 # it will result in a shallow repository that cannot be cloned or
1745 # fetched from.
1746 if not self.manifest.IsMirror:
1747 if self.clone_depth:
1748 depth = self.clone_depth
1749 else:
1750 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1751
Shawn Pearce69e04d82014-01-29 12:48:54 -08001752 if depth:
1753 current_branch_only = True
1754
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001755 if ID_RE.match(self.revisionExpr) is not None:
1756 is_sha1 = True
1757
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001758 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001759 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001760 # this is a tag and its sha1 value should never change
1761 tag_name = self.revisionExpr[len(R_TAGS):]
1762
1763 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001764 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001765 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001766 if is_sha1 and not depth:
1767 # When syncing a specific commit and --depth is not set:
1768 # * if upstream is explicitly specified and is not a sha1, fetch only
1769 # upstream as users expect only upstream to be fetch.
1770 # Note: The commit might not be in upstream in which case the sync
1771 # will fail.
1772 # * otherwise, fetch all branches to make sure we end up with the
1773 # specific commit.
1774 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001775
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001776 if not name:
1777 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001778
1779 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001780 remote = self.GetRemote(name)
1781 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001782 ssh_proxy = True
1783
Shawn O. Pearce88443382010-10-08 10:02:09 +02001784 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001785 if alt_dir and 'objects' == os.path.basename(alt_dir):
1786 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001787 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1788 remote = self.GetRemote(name)
1789
David Pursehouse8a68ff92012-09-24 12:15:13 +09001790 all_refs = self.bare_ref.all
1791 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001792 tmp = set()
1793
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301794 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001795 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001796 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001797 all_refs[r] = ref_id
1798 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001799 continue
1800
David Pursehouse8a68ff92012-09-24 12:15:13 +09001801 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001802 continue
1803
David Pursehouse8a68ff92012-09-24 12:15:13 +09001804 r = 'refs/_alt/%s' % ref_id
1805 all_refs[r] = ref_id
1806 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001807 tmp.add(r)
1808
Shawn O. Pearce88443382010-10-08 10:02:09 +02001809 tmp_packed = ''
1810 old_packed = ''
1811
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301812 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001813 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001814 tmp_packed += line
1815 if r not in tmp:
1816 old_packed += line
1817
1818 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001819 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001820 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001821
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001822 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001823
1824 # The --depth option only affects the initial fetch; after that we'll do
1825 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001826 if depth and initial:
1827 cmd.append('--depth=%s' % depth)
1828
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001829 if quiet:
1830 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001831 if not self.worktree:
1832 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001833 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001834
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001835 # If using depth then we should not get all the tags since they may
1836 # be outside of the depth.
1837 if no_tags or depth:
1838 cmd.append('--no-tags')
1839 else:
1840 cmd.append('--tags')
1841
Conley Owens80b87fe2014-05-09 17:13:44 -07001842 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001843 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001844 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001845 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001846 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001847 spec.append('tag')
1848 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001849
1850 branch = self.revisionExpr
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001851 if is_sha1 and depth:
1852 # Shallow checkout of a specific commit, fetch from that commit and not
1853 # the heads only as the commit might be deeper in the history.
1854 spec.append(branch)
1855 else:
1856 if is_sha1:
1857 branch = self.upstream
1858 if branch is not None and branch.strip():
1859 if not branch.startswith('refs/'):
1860 branch = R_HEADS + branch
1861 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001862 cmd.extend(spec)
1863
1864 shallowfetch = self.config.GetString('repo.shallowfetch')
1865 if shallowfetch and shallowfetch != ' '.join(spec):
1866 GitCommand(self, ['fetch', '--unshallow', name] + shallowfetch.split(),
1867 bare=True, ssh_proxy=ssh_proxy).Wait()
1868 if depth:
1869 self.config.SetString('repo.shallowfetch', ' '.join(spec))
1870 else:
1871 self.config.SetString('repo.shallowfetch', None)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001872
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001873 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001874 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001875 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1876 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001877 ok = True
1878 break
Brian Harring14a66742012-09-28 20:21:57 -07001879 elif current_branch_only and is_sha1 and ret == 128:
1880 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1881 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1882 # abort the optimization attempt and do a full sync.
1883 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001884 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001885
1886 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001887 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001888 if old_packed != '':
1889 _lwrite(packed_refs, old_packed)
1890 else:
1891 os.remove(packed_refs)
1892 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001893
1894 if is_sha1 and current_branch_only and self.upstream:
1895 # We just synced the upstream given branch; verify we
1896 # got what we wanted, else trigger a second run of all
1897 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001898 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001899 return self._RemoteFetch(name=name, current_branch_only=False,
1900 initial=False, quiet=quiet, alt_dir=alt_dir)
1901
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001902 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001903
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001904 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001905 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001906 return False
1907
1908 remote = self.GetRemote(self.remote.name)
1909 bundle_url = remote.url + '/clone.bundle'
1910 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001911 if GetSchemeFromUrl(bundle_url) not in (
1912 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001913 return False
1914
1915 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1916 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1917
1918 exist_dst = os.path.exists(bundle_dst)
1919 exist_tmp = os.path.exists(bundle_tmp)
1920
1921 if not initial and not exist_dst and not exist_tmp:
1922 return False
1923
1924 if not exist_dst:
1925 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1926 if not exist_dst:
1927 return False
1928
1929 cmd = ['fetch']
1930 if quiet:
1931 cmd.append('--quiet')
1932 if not self.worktree:
1933 cmd.append('--update-head-ok')
1934 cmd.append(bundle_dst)
1935 for f in remote.fetch:
1936 cmd.append(str(f))
1937 cmd.append('refs/tags/*:refs/tags/*')
1938
1939 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001940 if os.path.exists(bundle_dst):
1941 os.remove(bundle_dst)
1942 if os.path.exists(bundle_tmp):
1943 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001944 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001945
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001946 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001947 if os.path.exists(dstPath):
1948 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001949
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001950 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001951 if quiet:
1952 cmd += ['--silent']
1953 if os.path.exists(tmpPath):
1954 size = os.stat(tmpPath).st_size
1955 if size >= 1024:
1956 cmd += ['--continue-at', '%d' % (size,)]
1957 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001958 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001959 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1960 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001961 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001962 if cookiefile:
1963 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001964 if srcUrl.startswith('persistent-'):
1965 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001966 cmd += [srcUrl]
1967
1968 if IsTrace():
1969 Trace('%s', ' '.join(cmd))
1970 try:
1971 proc = subprocess.Popen(cmd)
1972 except OSError:
1973 return False
1974
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001975 curlret = proc.wait()
1976
1977 if curlret == 22:
1978 # From curl man page:
1979 # 22: HTTP page not retrieved. The requested url was not found or
1980 # returned another error with the HTTP error code being 400 or above.
1981 # This return code only appears if -f, --fail is used.
1982 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001983 print("Server does not provide clone.bundle; ignoring.",
1984 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001985 return False
1986
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001987 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001988 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001989 os.rename(tmpPath, dstPath)
1990 return True
1991 else:
1992 os.remove(tmpPath)
1993 return False
1994 else:
1995 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001996
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001997 def _IsValidBundle(self, path):
1998 try:
1999 with open(path) as f:
2000 if f.read(16) == '# v2 git bundle\n':
2001 return True
2002 else:
2003 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
2004 return False
2005 except OSError:
2006 return False
2007
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002008 def _GetBundleCookieFile(self, url):
2009 if url.startswith('persistent-'):
2010 try:
2011 p = subprocess.Popen(
2012 ['git-remote-persistent-https', '-print_config', url],
2013 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2014 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07002015 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002016 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07002017 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002018 for line in p.stdout:
2019 line = line.strip()
2020 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07002021 cookiefile = line[len(prefix):]
2022 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002023 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08002024 err_msg = p.stderr.read()
2025 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002026 pass # Persistent proxy doesn't support -print_config.
2027 else:
Conley Owenscbc07982013-11-21 10:38:03 -08002028 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07002029 if cookiefile:
2030 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002031 except OSError as e:
2032 if e.errno == errno.ENOENT:
2033 pass # No persistent proxy.
2034 raise
2035 return GitConfig.ForUser().GetString('http.cookiefile')
2036
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002037 def _Checkout(self, rev, quiet=False):
2038 cmd = ['checkout']
2039 if quiet:
2040 cmd.append('-q')
2041 cmd.append(rev)
2042 cmd.append('--')
2043 if GitCommand(self, cmd).Wait() != 0:
2044 if self._allrefs:
2045 raise GitError('%s checkout %s ' % (self.name, rev))
2046
Pierre Tardye5a21222011-03-24 16:28:18 +01002047 def _CherryPick(self, rev, quiet=False):
2048 cmd = ['cherry-pick']
2049 cmd.append(rev)
2050 cmd.append('--')
2051 if GitCommand(self, cmd).Wait() != 0:
2052 if self._allrefs:
2053 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2054
Erwan Mahea94f1622011-08-19 13:56:09 +02002055 def _Revert(self, rev, quiet=False):
2056 cmd = ['revert']
2057 cmd.append('--no-edit')
2058 cmd.append(rev)
2059 cmd.append('--')
2060 if GitCommand(self, cmd).Wait() != 0:
2061 if self._allrefs:
2062 raise GitError('%s revert %s ' % (self.name, rev))
2063
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002064 def _ResetHard(self, rev, quiet=True):
2065 cmd = ['reset', '--hard']
2066 if quiet:
2067 cmd.append('-q')
2068 cmd.append(rev)
2069 if GitCommand(self, cmd).Wait() != 0:
2070 raise GitError('%s reset --hard %s ' % (self.name, rev))
2071
2072 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002073 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002074 if onto is not None:
2075 cmd.extend(['--onto', onto])
2076 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002077 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002078 raise GitError('%s rebase %s ' % (self.name, upstream))
2079
Pierre Tardy3d125942012-05-04 12:18:12 +02002080 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002081 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002082 if ffonly:
2083 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002084 if GitCommand(self, cmd).Wait() != 0:
2085 raise GitError('%s merge %s ' % (self.name, head))
2086
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002087 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002088 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002089
2090 # Initialize the bare repository, which contains all of the objects.
2091 if not os.path.exists(self.objdir):
2092 os.makedirs(self.objdir)
2093 self.bare_objdir.init()
2094
2095 # If we have a separate directory to hold refs, initialize it as well.
2096 if self.objdir != self.gitdir:
2097 os.makedirs(self.gitdir)
2098 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2099 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002100
Shawn O. Pearce88443382010-10-08 10:02:09 +02002101 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002102 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002103
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002104 if ref_dir or mirror_git:
2105 if not mirror_git:
2106 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002107 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2108 self.relpath + '.git')
2109
2110 if os.path.exists(mirror_git):
2111 ref_dir = mirror_git
2112
2113 elif os.path.exists(repo_git):
2114 ref_dir = repo_git
2115
2116 else:
2117 ref_dir = None
2118
2119 if ref_dir:
2120 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2121 os.path.join(ref_dir, 'objects') + '\n')
2122
Jimmie Westera0444582012-10-24 13:44:42 +02002123 self._UpdateHooks()
2124
2125 m = self.manifest.manifestProject.config
2126 for key in ['user.name', 'user.email']:
2127 if m.Has(key, include_defaults = False):
2128 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002129 if self.manifest.IsMirror:
2130 self.config.SetString('core.bare', 'true')
2131 else:
2132 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002133
Jimmie Westera0444582012-10-24 13:44:42 +02002134 def _UpdateHooks(self):
2135 if os.path.exists(self.gitdir):
2136 # Always recreate hooks since they can have been changed
2137 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002138 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002139 try:
2140 to_rm = os.listdir(hooks)
2141 except OSError:
2142 to_rm = []
2143 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002144 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002145 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002146
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002147 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002148 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002149 if not os.path.exists(hooks):
2150 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002151 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002152 name = os.path.basename(stock_hook)
2153
Victor Boivie65e0f352011-04-18 11:23:29 +02002154 if name in ('commit-msg',) and not self.remote.review \
2155 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002156 # Don't install a Gerrit Code Review hook if this
2157 # project does not appear to use it for reviews.
2158 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002159 # Since the manifest project is one of those, but also
2160 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002161 continue
2162
2163 dst = os.path.join(hooks, name)
2164 if os.path.islink(dst):
2165 continue
2166 if os.path.exists(dst):
2167 if filecmp.cmp(stock_hook, dst, shallow=False):
2168 os.remove(dst)
2169 else:
2170 _error("%s: Not replacing %s hook", self.relpath, name)
2171 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002172 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002173 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002174 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002175 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002176 raise GitError('filesystem must support symlinks')
2177 else:
2178 raise
2179
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002180 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002181 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002182 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002183 remote.url = self.remote.url
2184 remote.review = self.remote.review
2185 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002186
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002187 if self.worktree:
2188 remote.ResetFetch(mirror=False)
2189 else:
2190 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002191 remote.Save()
2192
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002193 def _InitMRef(self):
2194 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002195 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002196
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002197 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002198 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002199
2200 def _InitAnyMRef(self, ref):
2201 cur = self.bare_ref.symref(ref)
2202
2203 if self.revisionId:
2204 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2205 msg = 'manifest set to %s' % self.revisionId
2206 dst = self.revisionId + '^0'
2207 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2208 else:
2209 remote = self.GetRemote(self.remote.name)
2210 dst = remote.ToLocal(self.revisionExpr)
2211 if cur != dst:
2212 msg = 'manifest set to %s' % self.revisionExpr
2213 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002214
David James8d201162013-10-11 17:03:19 -07002215 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2216 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2217
2218 Args:
2219 gitdir: The bare git repository. Must already be initialized.
2220 dotgit: The repository you would like to initialize.
2221 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2222 Only one work tree can store refs under a given |gitdir|.
2223 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2224 This saves you the effort of initializing |dotgit| yourself.
2225 """
2226 # These objects can be shared between several working trees.
2227 symlink_files = ['description', 'info']
2228 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2229 if share_refs:
2230 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002231 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002232 symlink_dirs += ['logs', 'refs']
2233 to_symlink = symlink_files + symlink_dirs
2234
2235 to_copy = []
2236 if copy_all:
2237 to_copy = os.listdir(gitdir)
2238
2239 for name in set(to_copy).union(to_symlink):
2240 try:
2241 src = os.path.realpath(os.path.join(gitdir, name))
2242 dst = os.path.realpath(os.path.join(dotgit, name))
2243
2244 if os.path.lexists(dst) and not os.path.islink(dst):
2245 raise GitError('cannot overwrite a local work tree')
2246
2247 # If the source dir doesn't exist, create an empty dir.
2248 if name in symlink_dirs and not os.path.lexists(src):
2249 os.makedirs(src)
2250
Conley Owens80b87fe2014-05-09 17:13:44 -07002251 # If the source file doesn't exist, ensure the destination
2252 # file doesn't either.
2253 if name in symlink_files and not os.path.lexists(src):
2254 try:
2255 os.remove(dst)
2256 except OSError:
2257 pass
2258
David James8d201162013-10-11 17:03:19 -07002259 if name in to_symlink:
2260 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2261 elif copy_all and not os.path.islink(dst):
2262 if os.path.isdir(src):
2263 shutil.copytree(src, dst)
2264 elif os.path.isfile(src):
2265 shutil.copy(src, dst)
2266 except OSError as e:
2267 if e.errno == errno.EPERM:
2268 raise GitError('filesystem must support symlinks')
2269 else:
2270 raise
2271
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002272 def _InitWorkTree(self):
2273 dotgit = os.path.join(self.worktree, '.git')
2274 if not os.path.exists(dotgit):
2275 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002276 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2277 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002278
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002279 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002280
2281 cmd = ['read-tree', '--reset', '-u']
2282 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002283 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002284 if GitCommand(self, cmd).Wait() != 0:
2285 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002286
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002287 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288
2289 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002290 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002291
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002292 def _revlist(self, *args, **kw):
2293 a = []
2294 a.extend(args)
2295 a.append('--')
2296 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002297
2298 @property
2299 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002300 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002301
Julien Camperguedd654222014-01-09 16:21:37 +01002302 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2303 """Get logs between two revisions of this project."""
2304 comp = '..'
2305 if rev1:
2306 revs = [rev1]
2307 if rev2:
2308 revs.extend([comp, rev2])
2309 cmd = ['log', ''.join(revs)]
2310 out = DiffColoring(self.config)
2311 if out.is_on and color:
2312 cmd.append('--color')
2313 if oneline:
2314 cmd.append('--oneline')
2315
2316 try:
2317 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2318 if log.Wait() == 0:
2319 return log.stdout
2320 except GitError:
2321 # worktree may not exist if groups changed for example. In that case,
2322 # try in gitdir instead.
2323 if not os.path.exists(self.worktree):
2324 return self.bare_git.log(*cmd[1:])
2325 else:
2326 raise
2327 return None
2328
2329 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2330 """Get the list of logs from this revision to given revisionId"""
2331 logs = {}
2332 selfId = self.GetRevisionId(self._allrefs)
2333 toId = toProject.GetRevisionId(toProject._allrefs)
2334
2335 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2336 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2337 return logs
2338
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002339 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002340 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002341 self._project = project
2342 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002343 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002344
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002345 def LsOthers(self):
2346 p = GitCommand(self._project,
2347 ['ls-files',
2348 '-z',
2349 '--others',
2350 '--exclude-standard'],
2351 bare = False,
David James8d201162013-10-11 17:03:19 -07002352 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002353 capture_stdout = True,
2354 capture_stderr = True)
2355 if p.Wait() == 0:
2356 out = p.stdout
2357 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002358 return out[:-1].split('\0') # pylint: disable=W1401
2359 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002360 return []
2361
2362 def DiffZ(self, name, *args):
2363 cmd = [name]
2364 cmd.append('-z')
2365 cmd.extend(args)
2366 p = GitCommand(self._project,
2367 cmd,
David James8d201162013-10-11 17:03:19 -07002368 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002369 bare = False,
2370 capture_stdout = True,
2371 capture_stderr = True)
2372 try:
2373 out = p.process.stdout.read()
2374 r = {}
2375 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002376 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002377 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002378 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002379 info = next(out)
2380 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002381 except StopIteration:
2382 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002383
2384 class _Info(object):
2385 def __init__(self, path, omode, nmode, oid, nid, state):
2386 self.path = path
2387 self.src_path = None
2388 self.old_mode = omode
2389 self.new_mode = nmode
2390 self.old_id = oid
2391 self.new_id = nid
2392
2393 if len(state) == 1:
2394 self.status = state
2395 self.level = None
2396 else:
2397 self.status = state[:1]
2398 self.level = state[1:]
2399 while self.level.startswith('0'):
2400 self.level = self.level[1:]
2401
2402 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002403 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002404 if info.status in ('R', 'C'):
2405 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002406 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002407 r[info.path] = info
2408 return r
2409 finally:
2410 p.Wait()
2411
2412 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002413 if self._bare:
2414 path = os.path.join(self._project.gitdir, HEAD)
2415 else:
2416 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002417 try:
2418 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002419 except IOError as e:
2420 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002421 try:
2422 line = fd.read()
2423 finally:
2424 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302425 try:
2426 line = line.decode()
2427 except AttributeError:
2428 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002429 if line.startswith('ref: '):
2430 return line[5:-1]
2431 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002432
2433 def SetHead(self, ref, message=None):
2434 cmdv = []
2435 if message is not None:
2436 cmdv.extend(['-m', message])
2437 cmdv.append(HEAD)
2438 cmdv.append(ref)
2439 self.symbolic_ref(*cmdv)
2440
2441 def DetachHead(self, new, message=None):
2442 cmdv = ['--no-deref']
2443 if message is not None:
2444 cmdv.extend(['-m', message])
2445 cmdv.append(HEAD)
2446 cmdv.append(new)
2447 self.update_ref(*cmdv)
2448
2449 def UpdateRef(self, name, new, old=None,
2450 message=None,
2451 detach=False):
2452 cmdv = []
2453 if message is not None:
2454 cmdv.extend(['-m', message])
2455 if detach:
2456 cmdv.append('--no-deref')
2457 cmdv.append(name)
2458 cmdv.append(new)
2459 if old is not None:
2460 cmdv.append(old)
2461 self.update_ref(*cmdv)
2462
2463 def DeleteRef(self, name, old=None):
2464 if not old:
2465 old = self.rev_parse(name)
2466 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002467 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002468
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002469 def rev_list(self, *args, **kw):
2470 if 'format' in kw:
2471 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2472 else:
2473 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002474 cmdv.extend(args)
2475 p = GitCommand(self._project,
2476 cmdv,
2477 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002478 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002479 capture_stdout = True,
2480 capture_stderr = True)
2481 r = []
2482 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002483 if line[-1] == '\n':
2484 line = line[:-1]
2485 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002486 if p.Wait() != 0:
2487 raise GitError('%s rev-list %s: %s' % (
2488 self._project.name,
2489 str(args),
2490 p.stderr))
2491 return r
2492
2493 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002494 """Allow arbitrary git commands using pythonic syntax.
2495
2496 This allows you to do things like:
2497 git_obj.rev_parse('HEAD')
2498
2499 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2500 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002501 Any other positional arguments will be passed to the git command, and the
2502 following keyword arguments are supported:
2503 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002504
2505 Args:
2506 name: The name of the git command to call. Any '_' characters will
2507 be replaced with '-'.
2508
2509 Returns:
2510 A callable object that will try to call git with the named command.
2511 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002512 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002513 def runner(*args, **kwargs):
2514 cmdv = []
2515 config = kwargs.pop('config', None)
2516 for k in kwargs:
2517 raise TypeError('%s() got an unexpected keyword argument %r'
2518 % (name, k))
2519 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002520 if not git_require((1, 7, 2)):
2521 raise ValueError('cannot set config on command line for %s()'
2522 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302523 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002524 cmdv.append('-c')
2525 cmdv.append('%s=%s' % (k, v))
2526 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002527 cmdv.extend(args)
2528 p = GitCommand(self._project,
2529 cmdv,
2530 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002531 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002532 capture_stdout = True,
2533 capture_stderr = True)
2534 if p.Wait() != 0:
2535 raise GitError('%s %s: %s' % (
2536 self._project.name,
2537 name,
2538 p.stderr))
2539 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302540 try:
Conley Owensedd01512013-09-26 12:59:58 -07002541 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302542 except AttributeError:
2543 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002544 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2545 return r[:-1]
2546 return r
2547 return runner
2548
2549
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002550class _PriorSyncFailedError(Exception):
2551 def __str__(self):
2552 return 'prior sync failed; rebase still in progress'
2553
2554class _DirtyError(Exception):
2555 def __str__(self):
2556 return 'contains uncommitted changes'
2557
2558class _InfoMessage(object):
2559 def __init__(self, project, text):
2560 self.project = project
2561 self.text = text
2562
2563 def Print(self, syncbuf):
2564 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2565 syncbuf.out.nl()
2566
2567class _Failure(object):
2568 def __init__(self, project, why):
2569 self.project = project
2570 self.why = why
2571
2572 def Print(self, syncbuf):
2573 syncbuf.out.fail('error: %s/: %s',
2574 self.project.relpath,
2575 str(self.why))
2576 syncbuf.out.nl()
2577
2578class _Later(object):
2579 def __init__(self, project, action):
2580 self.project = project
2581 self.action = action
2582
2583 def Run(self, syncbuf):
2584 out = syncbuf.out
2585 out.project('project %s/', self.project.relpath)
2586 out.nl()
2587 try:
2588 self.action()
2589 out.nl()
2590 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002591 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002592 out.nl()
2593 return False
2594
2595class _SyncColoring(Coloring):
2596 def __init__(self, config):
2597 Coloring.__init__(self, config, 'reposync')
2598 self.project = self.printer('header', attr = 'bold')
2599 self.info = self.printer('info')
2600 self.fail = self.printer('fail', fg='red')
2601
2602class SyncBuffer(object):
2603 def __init__(self, config, detach_head=False):
2604 self._messages = []
2605 self._failures = []
2606 self._later_queue1 = []
2607 self._later_queue2 = []
2608
2609 self.out = _SyncColoring(config)
2610 self.out.redirect(sys.stderr)
2611
2612 self.detach_head = detach_head
2613 self.clean = True
2614
2615 def info(self, project, fmt, *args):
2616 self._messages.append(_InfoMessage(project, fmt % args))
2617
2618 def fail(self, project, err=None):
2619 self._failures.append(_Failure(project, err))
2620 self.clean = False
2621
2622 def later1(self, project, what):
2623 self._later_queue1.append(_Later(project, what))
2624
2625 def later2(self, project, what):
2626 self._later_queue2.append(_Later(project, what))
2627
2628 def Finish(self):
2629 self._PrintMessages()
2630 self._RunLater()
2631 self._PrintMessages()
2632 return self.clean
2633
2634 def _RunLater(self):
2635 for q in ['_later_queue1', '_later_queue2']:
2636 if not self._RunQueue(q):
2637 return
2638
2639 def _RunQueue(self, queue):
2640 for m in getattr(self, queue):
2641 if not m.Run(self):
2642 self.clean = False
2643 return False
2644 setattr(self, queue, [])
2645 return True
2646
2647 def _PrintMessages(self):
2648 for m in self._messages:
2649 m.Print(self)
2650 for m in self._failures:
2651 m.Print(self)
2652
2653 self._messages = []
2654 self._failures = []
2655
2656
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002657class MetaProject(Project):
2658 """A special project housed under .repo.
2659 """
2660 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002661 Project.__init__(self,
2662 manifest = manifest,
2663 name = name,
2664 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002665 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002666 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002667 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002668 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002669 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002670 revisionId = None,
2671 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002672
2673 def PreSync(self):
2674 if self.Exists:
2675 cb = self.CurrentBranch
2676 if cb:
2677 base = self.GetBranch(cb).merge
2678 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002679 self.revisionExpr = base
2680 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002681
Florian Vallee5d016502012-06-07 17:19:26 +02002682 def MetaBranchSwitch(self, target):
2683 """ Prepare MetaProject for manifest branch switch
2684 """
2685
2686 # detach and delete manifest branch, allowing a new
2687 # branch to take over
2688 syncbuf = SyncBuffer(self.config, detach_head = True)
2689 self.Sync_LocalHalf(syncbuf)
2690 syncbuf.Finish()
2691
2692 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002693 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002694 capture_stdout = True,
2695 capture_stderr = True).Wait() == 0
2696
2697
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002698 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002699 def LastFetch(self):
2700 try:
2701 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2702 return os.path.getmtime(fh)
2703 except OSError:
2704 return 0
2705
2706 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002707 def HasChanges(self):
2708 """Has the remote received new commits not yet checked out?
2709 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002710 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002711 return False
2712
David Pursehouse8a68ff92012-09-24 12:15:13 +09002713 all_refs = self.bare_ref.all
2714 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002715 head = self.work_git.GetHead()
2716 if head.startswith(R_HEADS):
2717 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002718 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002719 except KeyError:
2720 head = None
2721
2722 if revid == head:
2723 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002724 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002725 return True
2726 return False