blob: a84857e62f8b3ec42771abd98d1a6e0a862e0bcf [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
Brian Harring14a66742012-09-28 20:21:57 -07001766 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1767 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001768
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769 if not name:
1770 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001771
1772 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001773 remote = self.GetRemote(name)
1774 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001775 ssh_proxy = True
1776
Shawn O. Pearce88443382010-10-08 10:02:09 +02001777 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001778 if alt_dir and 'objects' == os.path.basename(alt_dir):
1779 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001780 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1781 remote = self.GetRemote(name)
1782
David Pursehouse8a68ff92012-09-24 12:15:13 +09001783 all_refs = self.bare_ref.all
1784 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001785 tmp = set()
1786
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301787 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001788 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001789 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001790 all_refs[r] = ref_id
1791 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001792 continue
1793
David Pursehouse8a68ff92012-09-24 12:15:13 +09001794 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001795 continue
1796
David Pursehouse8a68ff92012-09-24 12:15:13 +09001797 r = 'refs/_alt/%s' % ref_id
1798 all_refs[r] = ref_id
1799 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001800 tmp.add(r)
1801
Shawn O. Pearce88443382010-10-08 10:02:09 +02001802 tmp_packed = ''
1803 old_packed = ''
1804
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301805 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001806 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001807 tmp_packed += line
1808 if r not in tmp:
1809 old_packed += line
1810
1811 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001812 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001813 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001814
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001815 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001816
1817 # The --depth option only affects the initial fetch; after that we'll do
1818 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001819 if depth and initial:
1820 cmd.append('--depth=%s' % depth)
1821
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001822 if quiet:
1823 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001824 if not self.worktree:
1825 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001826 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001827
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001828 # If using depth then we should not get all the tags since they may
1829 # be outside of the depth.
1830 if no_tags or depth:
1831 cmd.append('--no-tags')
1832 else:
1833 cmd.append('--tags')
1834
Conley Owens80b87fe2014-05-09 17:13:44 -07001835 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001836 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001837 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001838 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001839 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001840 spec.append('tag')
1841 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001842
1843 branch = self.revisionExpr
1844 if is_sha1:
1845 branch = self.upstream
1846 if branch is not None and branch.strip():
1847 if not branch.startswith('refs/'):
1848 branch = R_HEADS + branch
1849 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001850 cmd.extend(spec)
1851
1852 shallowfetch = self.config.GetString('repo.shallowfetch')
1853 if shallowfetch and shallowfetch != ' '.join(spec):
1854 GitCommand(self, ['fetch', '--unshallow', name] + shallowfetch.split(),
1855 bare=True, ssh_proxy=ssh_proxy).Wait()
1856 if depth:
1857 self.config.SetString('repo.shallowfetch', ' '.join(spec))
1858 else:
1859 self.config.SetString('repo.shallowfetch', None)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001860
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001861 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001862 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001863 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1864 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001865 ok = True
1866 break
Brian Harring14a66742012-09-28 20:21:57 -07001867 elif current_branch_only and is_sha1 and ret == 128:
1868 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1869 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1870 # abort the optimization attempt and do a full sync.
1871 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001872 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001873
1874 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001875 # Ensure that some refs exist. Otherwise, we probably aren't looking
1876 # at a real git repository and may have a bad url.
1877 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001878 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001879
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001880 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001881 if old_packed != '':
1882 _lwrite(packed_refs, old_packed)
1883 else:
1884 os.remove(packed_refs)
1885 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001886
1887 if is_sha1 and current_branch_only and self.upstream:
1888 # We just synced the upstream given branch; verify we
1889 # got what we wanted, else trigger a second run of all
1890 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001891 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001892 return self._RemoteFetch(name=name, current_branch_only=False,
1893 initial=False, quiet=quiet, alt_dir=alt_dir)
1894
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001895 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001896
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001897 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001898 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001899 return False
1900
1901 remote = self.GetRemote(self.remote.name)
1902 bundle_url = remote.url + '/clone.bundle'
1903 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001904 if GetSchemeFromUrl(bundle_url) not in (
1905 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001906 return False
1907
1908 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1909 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1910
1911 exist_dst = os.path.exists(bundle_dst)
1912 exist_tmp = os.path.exists(bundle_tmp)
1913
1914 if not initial and not exist_dst and not exist_tmp:
1915 return False
1916
1917 if not exist_dst:
1918 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1919 if not exist_dst:
1920 return False
1921
1922 cmd = ['fetch']
1923 if quiet:
1924 cmd.append('--quiet')
1925 if not self.worktree:
1926 cmd.append('--update-head-ok')
1927 cmd.append(bundle_dst)
1928 for f in remote.fetch:
1929 cmd.append(str(f))
1930 cmd.append('refs/tags/*:refs/tags/*')
1931
1932 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001933 if os.path.exists(bundle_dst):
1934 os.remove(bundle_dst)
1935 if os.path.exists(bundle_tmp):
1936 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001937 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001938
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001939 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001940 if os.path.exists(dstPath):
1941 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001942
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001943 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001944 if quiet:
1945 cmd += ['--silent']
1946 if os.path.exists(tmpPath):
1947 size = os.stat(tmpPath).st_size
1948 if size >= 1024:
1949 cmd += ['--continue-at', '%d' % (size,)]
1950 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001951 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001952 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1953 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001954 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001955 if cookiefile:
1956 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001957 if srcUrl.startswith('persistent-'):
1958 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001959 cmd += [srcUrl]
1960
1961 if IsTrace():
1962 Trace('%s', ' '.join(cmd))
1963 try:
1964 proc = subprocess.Popen(cmd)
1965 except OSError:
1966 return False
1967
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001968 curlret = proc.wait()
1969
1970 if curlret == 22:
1971 # From curl man page:
1972 # 22: HTTP page not retrieved. The requested url was not found or
1973 # returned another error with the HTTP error code being 400 or above.
1974 # This return code only appears if -f, --fail is used.
1975 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001976 print("Server does not provide clone.bundle; ignoring.",
1977 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001978 return False
1979
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001980 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001981 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001982 os.rename(tmpPath, dstPath)
1983 return True
1984 else:
1985 os.remove(tmpPath)
1986 return False
1987 else:
1988 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001989
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001990 def _IsValidBundle(self, path):
1991 try:
1992 with open(path) as f:
1993 if f.read(16) == '# v2 git bundle\n':
1994 return True
1995 else:
1996 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1997 return False
1998 except OSError:
1999 return False
2000
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002001 def _GetBundleCookieFile(self, url):
2002 if url.startswith('persistent-'):
2003 try:
2004 p = subprocess.Popen(
2005 ['git-remote-persistent-https', '-print_config', url],
2006 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2007 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07002008 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002009 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07002010 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002011 for line in p.stdout:
2012 line = line.strip()
2013 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07002014 cookiefile = line[len(prefix):]
2015 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002016 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08002017 err_msg = p.stderr.read()
2018 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002019 pass # Persistent proxy doesn't support -print_config.
2020 else:
Conley Owenscbc07982013-11-21 10:38:03 -08002021 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07002022 if cookiefile:
2023 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002024 except OSError as e:
2025 if e.errno == errno.ENOENT:
2026 pass # No persistent proxy.
2027 raise
2028 return GitConfig.ForUser().GetString('http.cookiefile')
2029
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002030 def _Checkout(self, rev, quiet=False):
2031 cmd = ['checkout']
2032 if quiet:
2033 cmd.append('-q')
2034 cmd.append(rev)
2035 cmd.append('--')
2036 if GitCommand(self, cmd).Wait() != 0:
2037 if self._allrefs:
2038 raise GitError('%s checkout %s ' % (self.name, rev))
2039
Pierre Tardye5a21222011-03-24 16:28:18 +01002040 def _CherryPick(self, rev, quiet=False):
2041 cmd = ['cherry-pick']
2042 cmd.append(rev)
2043 cmd.append('--')
2044 if GitCommand(self, cmd).Wait() != 0:
2045 if self._allrefs:
2046 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2047
Erwan Mahea94f1622011-08-19 13:56:09 +02002048 def _Revert(self, rev, quiet=False):
2049 cmd = ['revert']
2050 cmd.append('--no-edit')
2051 cmd.append(rev)
2052 cmd.append('--')
2053 if GitCommand(self, cmd).Wait() != 0:
2054 if self._allrefs:
2055 raise GitError('%s revert %s ' % (self.name, rev))
2056
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002057 def _ResetHard(self, rev, quiet=True):
2058 cmd = ['reset', '--hard']
2059 if quiet:
2060 cmd.append('-q')
2061 cmd.append(rev)
2062 if GitCommand(self, cmd).Wait() != 0:
2063 raise GitError('%s reset --hard %s ' % (self.name, rev))
2064
2065 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002066 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002067 if onto is not None:
2068 cmd.extend(['--onto', onto])
2069 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002070 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002071 raise GitError('%s rebase %s ' % (self.name, upstream))
2072
Pierre Tardy3d125942012-05-04 12:18:12 +02002073 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002074 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002075 if ffonly:
2076 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002077 if GitCommand(self, cmd).Wait() != 0:
2078 raise GitError('%s merge %s ' % (self.name, head))
2079
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002080 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002081 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002082
2083 # Initialize the bare repository, which contains all of the objects.
2084 if not os.path.exists(self.objdir):
2085 os.makedirs(self.objdir)
2086 self.bare_objdir.init()
2087
2088 # If we have a separate directory to hold refs, initialize it as well.
2089 if self.objdir != self.gitdir:
2090 os.makedirs(self.gitdir)
2091 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2092 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002093
Shawn O. Pearce88443382010-10-08 10:02:09 +02002094 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002095 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002096
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002097 if ref_dir or mirror_git:
2098 if not mirror_git:
2099 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002100 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2101 self.relpath + '.git')
2102
2103 if os.path.exists(mirror_git):
2104 ref_dir = mirror_git
2105
2106 elif os.path.exists(repo_git):
2107 ref_dir = repo_git
2108
2109 else:
2110 ref_dir = None
2111
2112 if ref_dir:
2113 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2114 os.path.join(ref_dir, 'objects') + '\n')
2115
Jimmie Westera0444582012-10-24 13:44:42 +02002116 self._UpdateHooks()
2117
2118 m = self.manifest.manifestProject.config
2119 for key in ['user.name', 'user.email']:
2120 if m.Has(key, include_defaults = False):
2121 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002122 if self.manifest.IsMirror:
2123 self.config.SetString('core.bare', 'true')
2124 else:
2125 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002126
Jimmie Westera0444582012-10-24 13:44:42 +02002127 def _UpdateHooks(self):
2128 if os.path.exists(self.gitdir):
2129 # Always recreate hooks since they can have been changed
2130 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002131 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002132 try:
2133 to_rm = os.listdir(hooks)
2134 except OSError:
2135 to_rm = []
2136 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002137 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002138 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002139
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002140 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002141 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002142 if not os.path.exists(hooks):
2143 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002144 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002145 name = os.path.basename(stock_hook)
2146
Victor Boivie65e0f352011-04-18 11:23:29 +02002147 if name in ('commit-msg',) and not self.remote.review \
2148 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002149 # Don't install a Gerrit Code Review hook if this
2150 # project does not appear to use it for reviews.
2151 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002152 # Since the manifest project is one of those, but also
2153 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002154 continue
2155
2156 dst = os.path.join(hooks, name)
2157 if os.path.islink(dst):
2158 continue
2159 if os.path.exists(dst):
2160 if filecmp.cmp(stock_hook, dst, shallow=False):
2161 os.remove(dst)
2162 else:
2163 _error("%s: Not replacing %s hook", self.relpath, name)
2164 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002165 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002166 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002167 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002168 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002169 raise GitError('filesystem must support symlinks')
2170 else:
2171 raise
2172
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002173 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002174 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002175 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002176 remote.url = self.remote.url
2177 remote.review = self.remote.review
2178 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002179
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002180 if self.worktree:
2181 remote.ResetFetch(mirror=False)
2182 else:
2183 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002184 remote.Save()
2185
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002186 def _InitMRef(self):
2187 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002188 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002189
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002190 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002191 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002192
2193 def _InitAnyMRef(self, ref):
2194 cur = self.bare_ref.symref(ref)
2195
2196 if self.revisionId:
2197 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2198 msg = 'manifest set to %s' % self.revisionId
2199 dst = self.revisionId + '^0'
2200 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2201 else:
2202 remote = self.GetRemote(self.remote.name)
2203 dst = remote.ToLocal(self.revisionExpr)
2204 if cur != dst:
2205 msg = 'manifest set to %s' % self.revisionExpr
2206 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002207
David James8d201162013-10-11 17:03:19 -07002208 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2209 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2210
2211 Args:
2212 gitdir: The bare git repository. Must already be initialized.
2213 dotgit: The repository you would like to initialize.
2214 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2215 Only one work tree can store refs under a given |gitdir|.
2216 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2217 This saves you the effort of initializing |dotgit| yourself.
2218 """
2219 # These objects can be shared between several working trees.
2220 symlink_files = ['description', 'info']
2221 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2222 if share_refs:
2223 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002224 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002225 symlink_dirs += ['logs', 'refs']
2226 to_symlink = symlink_files + symlink_dirs
2227
2228 to_copy = []
2229 if copy_all:
2230 to_copy = os.listdir(gitdir)
2231
2232 for name in set(to_copy).union(to_symlink):
2233 try:
2234 src = os.path.realpath(os.path.join(gitdir, name))
2235 dst = os.path.realpath(os.path.join(dotgit, name))
2236
2237 if os.path.lexists(dst) and not os.path.islink(dst):
2238 raise GitError('cannot overwrite a local work tree')
2239
2240 # If the source dir doesn't exist, create an empty dir.
2241 if name in symlink_dirs and not os.path.lexists(src):
2242 os.makedirs(src)
2243
Conley Owens80b87fe2014-05-09 17:13:44 -07002244 # If the source file doesn't exist, ensure the destination
2245 # file doesn't either.
2246 if name in symlink_files and not os.path.lexists(src):
2247 try:
2248 os.remove(dst)
2249 except OSError:
2250 pass
2251
David James8d201162013-10-11 17:03:19 -07002252 if name in to_symlink:
2253 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2254 elif copy_all and not os.path.islink(dst):
2255 if os.path.isdir(src):
2256 shutil.copytree(src, dst)
2257 elif os.path.isfile(src):
2258 shutil.copy(src, dst)
2259 except OSError as e:
2260 if e.errno == errno.EPERM:
2261 raise GitError('filesystem must support symlinks')
2262 else:
2263 raise
2264
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002265 def _InitWorkTree(self):
2266 dotgit = os.path.join(self.worktree, '.git')
2267 if not os.path.exists(dotgit):
2268 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002269 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2270 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002271
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002272 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002273
2274 cmd = ['read-tree', '--reset', '-u']
2275 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002276 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002277 if GitCommand(self, cmd).Wait() != 0:
2278 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002279
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002280 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002281
2282 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002283 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002284
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002285 def _revlist(self, *args, **kw):
2286 a = []
2287 a.extend(args)
2288 a.append('--')
2289 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002290
2291 @property
2292 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002293 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002294
Julien Camperguedd654222014-01-09 16:21:37 +01002295 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2296 """Get logs between two revisions of this project."""
2297 comp = '..'
2298 if rev1:
2299 revs = [rev1]
2300 if rev2:
2301 revs.extend([comp, rev2])
2302 cmd = ['log', ''.join(revs)]
2303 out = DiffColoring(self.config)
2304 if out.is_on and color:
2305 cmd.append('--color')
2306 if oneline:
2307 cmd.append('--oneline')
2308
2309 try:
2310 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2311 if log.Wait() == 0:
2312 return log.stdout
2313 except GitError:
2314 # worktree may not exist if groups changed for example. In that case,
2315 # try in gitdir instead.
2316 if not os.path.exists(self.worktree):
2317 return self.bare_git.log(*cmd[1:])
2318 else:
2319 raise
2320 return None
2321
2322 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2323 """Get the list of logs from this revision to given revisionId"""
2324 logs = {}
2325 selfId = self.GetRevisionId(self._allrefs)
2326 toId = toProject.GetRevisionId(toProject._allrefs)
2327
2328 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2329 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2330 return logs
2331
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002332 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002333 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002334 self._project = project
2335 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002336 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002337
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002338 def LsOthers(self):
2339 p = GitCommand(self._project,
2340 ['ls-files',
2341 '-z',
2342 '--others',
2343 '--exclude-standard'],
2344 bare = False,
David James8d201162013-10-11 17:03:19 -07002345 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002346 capture_stdout = True,
2347 capture_stderr = True)
2348 if p.Wait() == 0:
2349 out = p.stdout
2350 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002351 return out[:-1].split('\0') # pylint: disable=W1401
2352 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002353 return []
2354
2355 def DiffZ(self, name, *args):
2356 cmd = [name]
2357 cmd.append('-z')
2358 cmd.extend(args)
2359 p = GitCommand(self._project,
2360 cmd,
David James8d201162013-10-11 17:03:19 -07002361 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002362 bare = False,
2363 capture_stdout = True,
2364 capture_stderr = True)
2365 try:
2366 out = p.process.stdout.read()
2367 r = {}
2368 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002369 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002370 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002371 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002372 info = next(out)
2373 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002374 except StopIteration:
2375 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002376
2377 class _Info(object):
2378 def __init__(self, path, omode, nmode, oid, nid, state):
2379 self.path = path
2380 self.src_path = None
2381 self.old_mode = omode
2382 self.new_mode = nmode
2383 self.old_id = oid
2384 self.new_id = nid
2385
2386 if len(state) == 1:
2387 self.status = state
2388 self.level = None
2389 else:
2390 self.status = state[:1]
2391 self.level = state[1:]
2392 while self.level.startswith('0'):
2393 self.level = self.level[1:]
2394
2395 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002396 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002397 if info.status in ('R', 'C'):
2398 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002399 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002400 r[info.path] = info
2401 return r
2402 finally:
2403 p.Wait()
2404
2405 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002406 if self._bare:
2407 path = os.path.join(self._project.gitdir, HEAD)
2408 else:
2409 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002410 try:
2411 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002412 except IOError as e:
2413 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002414 try:
2415 line = fd.read()
2416 finally:
2417 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302418 try:
2419 line = line.decode()
2420 except AttributeError:
2421 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002422 if line.startswith('ref: '):
2423 return line[5:-1]
2424 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002425
2426 def SetHead(self, ref, message=None):
2427 cmdv = []
2428 if message is not None:
2429 cmdv.extend(['-m', message])
2430 cmdv.append(HEAD)
2431 cmdv.append(ref)
2432 self.symbolic_ref(*cmdv)
2433
2434 def DetachHead(self, new, message=None):
2435 cmdv = ['--no-deref']
2436 if message is not None:
2437 cmdv.extend(['-m', message])
2438 cmdv.append(HEAD)
2439 cmdv.append(new)
2440 self.update_ref(*cmdv)
2441
2442 def UpdateRef(self, name, new, old=None,
2443 message=None,
2444 detach=False):
2445 cmdv = []
2446 if message is not None:
2447 cmdv.extend(['-m', message])
2448 if detach:
2449 cmdv.append('--no-deref')
2450 cmdv.append(name)
2451 cmdv.append(new)
2452 if old is not None:
2453 cmdv.append(old)
2454 self.update_ref(*cmdv)
2455
2456 def DeleteRef(self, name, old=None):
2457 if not old:
2458 old = self.rev_parse(name)
2459 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002460 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002461
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002462 def rev_list(self, *args, **kw):
2463 if 'format' in kw:
2464 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2465 else:
2466 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002467 cmdv.extend(args)
2468 p = GitCommand(self._project,
2469 cmdv,
2470 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002471 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002472 capture_stdout = True,
2473 capture_stderr = True)
2474 r = []
2475 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002476 if line[-1] == '\n':
2477 line = line[:-1]
2478 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002479 if p.Wait() != 0:
2480 raise GitError('%s rev-list %s: %s' % (
2481 self._project.name,
2482 str(args),
2483 p.stderr))
2484 return r
2485
2486 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002487 """Allow arbitrary git commands using pythonic syntax.
2488
2489 This allows you to do things like:
2490 git_obj.rev_parse('HEAD')
2491
2492 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2493 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002494 Any other positional arguments will be passed to the git command, and the
2495 following keyword arguments are supported:
2496 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002497
2498 Args:
2499 name: The name of the git command to call. Any '_' characters will
2500 be replaced with '-'.
2501
2502 Returns:
2503 A callable object that will try to call git with the named command.
2504 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002505 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002506 def runner(*args, **kwargs):
2507 cmdv = []
2508 config = kwargs.pop('config', None)
2509 for k in kwargs:
2510 raise TypeError('%s() got an unexpected keyword argument %r'
2511 % (name, k))
2512 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002513 if not git_require((1, 7, 2)):
2514 raise ValueError('cannot set config on command line for %s()'
2515 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302516 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002517 cmdv.append('-c')
2518 cmdv.append('%s=%s' % (k, v))
2519 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002520 cmdv.extend(args)
2521 p = GitCommand(self._project,
2522 cmdv,
2523 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002524 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002525 capture_stdout = True,
2526 capture_stderr = True)
2527 if p.Wait() != 0:
2528 raise GitError('%s %s: %s' % (
2529 self._project.name,
2530 name,
2531 p.stderr))
2532 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302533 try:
Conley Owensedd01512013-09-26 12:59:58 -07002534 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302535 except AttributeError:
2536 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002537 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2538 return r[:-1]
2539 return r
2540 return runner
2541
2542
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002543class _PriorSyncFailedError(Exception):
2544 def __str__(self):
2545 return 'prior sync failed; rebase still in progress'
2546
2547class _DirtyError(Exception):
2548 def __str__(self):
2549 return 'contains uncommitted changes'
2550
2551class _InfoMessage(object):
2552 def __init__(self, project, text):
2553 self.project = project
2554 self.text = text
2555
2556 def Print(self, syncbuf):
2557 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2558 syncbuf.out.nl()
2559
2560class _Failure(object):
2561 def __init__(self, project, why):
2562 self.project = project
2563 self.why = why
2564
2565 def Print(self, syncbuf):
2566 syncbuf.out.fail('error: %s/: %s',
2567 self.project.relpath,
2568 str(self.why))
2569 syncbuf.out.nl()
2570
2571class _Later(object):
2572 def __init__(self, project, action):
2573 self.project = project
2574 self.action = action
2575
2576 def Run(self, syncbuf):
2577 out = syncbuf.out
2578 out.project('project %s/', self.project.relpath)
2579 out.nl()
2580 try:
2581 self.action()
2582 out.nl()
2583 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002584 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002585 out.nl()
2586 return False
2587
2588class _SyncColoring(Coloring):
2589 def __init__(self, config):
2590 Coloring.__init__(self, config, 'reposync')
2591 self.project = self.printer('header', attr = 'bold')
2592 self.info = self.printer('info')
2593 self.fail = self.printer('fail', fg='red')
2594
2595class SyncBuffer(object):
2596 def __init__(self, config, detach_head=False):
2597 self._messages = []
2598 self._failures = []
2599 self._later_queue1 = []
2600 self._later_queue2 = []
2601
2602 self.out = _SyncColoring(config)
2603 self.out.redirect(sys.stderr)
2604
2605 self.detach_head = detach_head
2606 self.clean = True
2607
2608 def info(self, project, fmt, *args):
2609 self._messages.append(_InfoMessage(project, fmt % args))
2610
2611 def fail(self, project, err=None):
2612 self._failures.append(_Failure(project, err))
2613 self.clean = False
2614
2615 def later1(self, project, what):
2616 self._later_queue1.append(_Later(project, what))
2617
2618 def later2(self, project, what):
2619 self._later_queue2.append(_Later(project, what))
2620
2621 def Finish(self):
2622 self._PrintMessages()
2623 self._RunLater()
2624 self._PrintMessages()
2625 return self.clean
2626
2627 def _RunLater(self):
2628 for q in ['_later_queue1', '_later_queue2']:
2629 if not self._RunQueue(q):
2630 return
2631
2632 def _RunQueue(self, queue):
2633 for m in getattr(self, queue):
2634 if not m.Run(self):
2635 self.clean = False
2636 return False
2637 setattr(self, queue, [])
2638 return True
2639
2640 def _PrintMessages(self):
2641 for m in self._messages:
2642 m.Print(self)
2643 for m in self._failures:
2644 m.Print(self)
2645
2646 self._messages = []
2647 self._failures = []
2648
2649
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002650class MetaProject(Project):
2651 """A special project housed under .repo.
2652 """
2653 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002654 Project.__init__(self,
2655 manifest = manifest,
2656 name = name,
2657 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002658 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002659 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002660 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002661 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002662 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002663 revisionId = None,
2664 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002665
2666 def PreSync(self):
2667 if self.Exists:
2668 cb = self.CurrentBranch
2669 if cb:
2670 base = self.GetBranch(cb).merge
2671 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002672 self.revisionExpr = base
2673 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002674
Florian Vallee5d016502012-06-07 17:19:26 +02002675 def MetaBranchSwitch(self, target):
2676 """ Prepare MetaProject for manifest branch switch
2677 """
2678
2679 # detach and delete manifest branch, allowing a new
2680 # branch to take over
2681 syncbuf = SyncBuffer(self.config, detach_head = True)
2682 self.Sync_LocalHalf(syncbuf)
2683 syncbuf.Finish()
2684
2685 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002686 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002687 capture_stdout = True,
2688 capture_stderr = True).Wait() == 0
2689
2690
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002691 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002692 def LastFetch(self):
2693 try:
2694 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2695 return os.path.getmtime(fh)
2696 except OSError:
2697 return 0
2698
2699 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002700 def HasChanges(self):
2701 """Has the remote received new commits not yet checked out?
2702 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002703 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002704 return False
2705
David Pursehouse8a68ff92012-09-24 12:15:13 +09002706 all_refs = self.bare_ref.all
2707 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002708 head = self.work_git.GetHead()
2709 if head.startswith(R_HEADS):
2710 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002711 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002712 except KeyError:
2713 head = None
2714
2715 if revid == head:
2716 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002717 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002718 return True
2719 return False