blob: cdce1e8485f7e716e13f9093ae7243696cae43af [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:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001875 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001876 if old_packed != '':
1877 _lwrite(packed_refs, old_packed)
1878 else:
1879 os.remove(packed_refs)
1880 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001881
1882 if is_sha1 and current_branch_only and self.upstream:
1883 # We just synced the upstream given branch; verify we
1884 # got what we wanted, else trigger a second run of all
1885 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001886 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001887 return self._RemoteFetch(name=name, current_branch_only=False,
1888 initial=False, quiet=quiet, alt_dir=alt_dir)
1889
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001890 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001891
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001892 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001893 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001894 return False
1895
1896 remote = self.GetRemote(self.remote.name)
1897 bundle_url = remote.url + '/clone.bundle'
1898 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001899 if GetSchemeFromUrl(bundle_url) not in (
1900 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001901 return False
1902
1903 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1904 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1905
1906 exist_dst = os.path.exists(bundle_dst)
1907 exist_tmp = os.path.exists(bundle_tmp)
1908
1909 if not initial and not exist_dst and not exist_tmp:
1910 return False
1911
1912 if not exist_dst:
1913 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1914 if not exist_dst:
1915 return False
1916
1917 cmd = ['fetch']
1918 if quiet:
1919 cmd.append('--quiet')
1920 if not self.worktree:
1921 cmd.append('--update-head-ok')
1922 cmd.append(bundle_dst)
1923 for f in remote.fetch:
1924 cmd.append(str(f))
1925 cmd.append('refs/tags/*:refs/tags/*')
1926
1927 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001928 if os.path.exists(bundle_dst):
1929 os.remove(bundle_dst)
1930 if os.path.exists(bundle_tmp):
1931 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001932 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001933
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001934 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001935 if os.path.exists(dstPath):
1936 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001937
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001938 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001939 if quiet:
1940 cmd += ['--silent']
1941 if os.path.exists(tmpPath):
1942 size = os.stat(tmpPath).st_size
1943 if size >= 1024:
1944 cmd += ['--continue-at', '%d' % (size,)]
1945 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001946 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001947 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1948 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001949 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001950 if cookiefile:
1951 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001952 if srcUrl.startswith('persistent-'):
1953 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001954 cmd += [srcUrl]
1955
1956 if IsTrace():
1957 Trace('%s', ' '.join(cmd))
1958 try:
1959 proc = subprocess.Popen(cmd)
1960 except OSError:
1961 return False
1962
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001963 curlret = proc.wait()
1964
1965 if curlret == 22:
1966 # From curl man page:
1967 # 22: HTTP page not retrieved. The requested url was not found or
1968 # returned another error with the HTTP error code being 400 or above.
1969 # This return code only appears if -f, --fail is used.
1970 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001971 print("Server does not provide clone.bundle; ignoring.",
1972 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001973 return False
1974
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001975 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001976 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001977 os.rename(tmpPath, dstPath)
1978 return True
1979 else:
1980 os.remove(tmpPath)
1981 return False
1982 else:
1983 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001984
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001985 def _IsValidBundle(self, path):
1986 try:
1987 with open(path) as f:
1988 if f.read(16) == '# v2 git bundle\n':
1989 return True
1990 else:
1991 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1992 return False
1993 except OSError:
1994 return False
1995
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001996 def _GetBundleCookieFile(self, url):
1997 if url.startswith('persistent-'):
1998 try:
1999 p = subprocess.Popen(
2000 ['git-remote-persistent-https', '-print_config', url],
2001 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2002 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07002003 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002004 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07002005 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002006 for line in p.stdout:
2007 line = line.strip()
2008 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07002009 cookiefile = line[len(prefix):]
2010 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002011 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08002012 err_msg = p.stderr.read()
2013 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002014 pass # Persistent proxy doesn't support -print_config.
2015 else:
Conley Owenscbc07982013-11-21 10:38:03 -08002016 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07002017 if cookiefile:
2018 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002019 except OSError as e:
2020 if e.errno == errno.ENOENT:
2021 pass # No persistent proxy.
2022 raise
2023 return GitConfig.ForUser().GetString('http.cookiefile')
2024
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002025 def _Checkout(self, rev, quiet=False):
2026 cmd = ['checkout']
2027 if quiet:
2028 cmd.append('-q')
2029 cmd.append(rev)
2030 cmd.append('--')
2031 if GitCommand(self, cmd).Wait() != 0:
2032 if self._allrefs:
2033 raise GitError('%s checkout %s ' % (self.name, rev))
2034
Pierre Tardye5a21222011-03-24 16:28:18 +01002035 def _CherryPick(self, rev, quiet=False):
2036 cmd = ['cherry-pick']
2037 cmd.append(rev)
2038 cmd.append('--')
2039 if GitCommand(self, cmd).Wait() != 0:
2040 if self._allrefs:
2041 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2042
Erwan Mahea94f1622011-08-19 13:56:09 +02002043 def _Revert(self, rev, quiet=False):
2044 cmd = ['revert']
2045 cmd.append('--no-edit')
2046 cmd.append(rev)
2047 cmd.append('--')
2048 if GitCommand(self, cmd).Wait() != 0:
2049 if self._allrefs:
2050 raise GitError('%s revert %s ' % (self.name, rev))
2051
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002052 def _ResetHard(self, rev, quiet=True):
2053 cmd = ['reset', '--hard']
2054 if quiet:
2055 cmd.append('-q')
2056 cmd.append(rev)
2057 if GitCommand(self, cmd).Wait() != 0:
2058 raise GitError('%s reset --hard %s ' % (self.name, rev))
2059
2060 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002061 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002062 if onto is not None:
2063 cmd.extend(['--onto', onto])
2064 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002065 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002066 raise GitError('%s rebase %s ' % (self.name, upstream))
2067
Pierre Tardy3d125942012-05-04 12:18:12 +02002068 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002069 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002070 if ffonly:
2071 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002072 if GitCommand(self, cmd).Wait() != 0:
2073 raise GitError('%s merge %s ' % (self.name, head))
2074
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002075 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002076 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002077
2078 # Initialize the bare repository, which contains all of the objects.
2079 if not os.path.exists(self.objdir):
2080 os.makedirs(self.objdir)
2081 self.bare_objdir.init()
2082
2083 # If we have a separate directory to hold refs, initialize it as well.
2084 if self.objdir != self.gitdir:
2085 os.makedirs(self.gitdir)
2086 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2087 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002088
Shawn O. Pearce88443382010-10-08 10:02:09 +02002089 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002090 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002091
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002092 if ref_dir or mirror_git:
2093 if not mirror_git:
2094 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002095 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2096 self.relpath + '.git')
2097
2098 if os.path.exists(mirror_git):
2099 ref_dir = mirror_git
2100
2101 elif os.path.exists(repo_git):
2102 ref_dir = repo_git
2103
2104 else:
2105 ref_dir = None
2106
2107 if ref_dir:
2108 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2109 os.path.join(ref_dir, 'objects') + '\n')
2110
Jimmie Westera0444582012-10-24 13:44:42 +02002111 self._UpdateHooks()
2112
2113 m = self.manifest.manifestProject.config
2114 for key in ['user.name', 'user.email']:
2115 if m.Has(key, include_defaults = False):
2116 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002117 if self.manifest.IsMirror:
2118 self.config.SetString('core.bare', 'true')
2119 else:
2120 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002121
Jimmie Westera0444582012-10-24 13:44:42 +02002122 def _UpdateHooks(self):
2123 if os.path.exists(self.gitdir):
2124 # Always recreate hooks since they can have been changed
2125 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002126 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002127 try:
2128 to_rm = os.listdir(hooks)
2129 except OSError:
2130 to_rm = []
2131 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002132 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002133 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002135 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002136 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002137 if not os.path.exists(hooks):
2138 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002139 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002140 name = os.path.basename(stock_hook)
2141
Victor Boivie65e0f352011-04-18 11:23:29 +02002142 if name in ('commit-msg',) and not self.remote.review \
2143 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002144 # Don't install a Gerrit Code Review hook if this
2145 # project does not appear to use it for reviews.
2146 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002147 # Since the manifest project is one of those, but also
2148 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002149 continue
2150
2151 dst = os.path.join(hooks, name)
2152 if os.path.islink(dst):
2153 continue
2154 if os.path.exists(dst):
2155 if filecmp.cmp(stock_hook, dst, shallow=False):
2156 os.remove(dst)
2157 else:
2158 _error("%s: Not replacing %s hook", self.relpath, name)
2159 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002160 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002161 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002162 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002163 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002164 raise GitError('filesystem must support symlinks')
2165 else:
2166 raise
2167
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002168 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002169 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002170 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002171 remote.url = self.remote.url
2172 remote.review = self.remote.review
2173 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002174
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002175 if self.worktree:
2176 remote.ResetFetch(mirror=False)
2177 else:
2178 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002179 remote.Save()
2180
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002181 def _InitMRef(self):
2182 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002183 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002184
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002185 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002186 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002187
2188 def _InitAnyMRef(self, ref):
2189 cur = self.bare_ref.symref(ref)
2190
2191 if self.revisionId:
2192 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2193 msg = 'manifest set to %s' % self.revisionId
2194 dst = self.revisionId + '^0'
2195 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2196 else:
2197 remote = self.GetRemote(self.remote.name)
2198 dst = remote.ToLocal(self.revisionExpr)
2199 if cur != dst:
2200 msg = 'manifest set to %s' % self.revisionExpr
2201 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002202
David James8d201162013-10-11 17:03:19 -07002203 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2204 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2205
2206 Args:
2207 gitdir: The bare git repository. Must already be initialized.
2208 dotgit: The repository you would like to initialize.
2209 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2210 Only one work tree can store refs under a given |gitdir|.
2211 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2212 This saves you the effort of initializing |dotgit| yourself.
2213 """
2214 # These objects can be shared between several working trees.
2215 symlink_files = ['description', 'info']
2216 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2217 if share_refs:
2218 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002219 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002220 symlink_dirs += ['logs', 'refs']
2221 to_symlink = symlink_files + symlink_dirs
2222
2223 to_copy = []
2224 if copy_all:
2225 to_copy = os.listdir(gitdir)
2226
2227 for name in set(to_copy).union(to_symlink):
2228 try:
2229 src = os.path.realpath(os.path.join(gitdir, name))
2230 dst = os.path.realpath(os.path.join(dotgit, name))
2231
2232 if os.path.lexists(dst) and not os.path.islink(dst):
2233 raise GitError('cannot overwrite a local work tree')
2234
2235 # If the source dir doesn't exist, create an empty dir.
2236 if name in symlink_dirs and not os.path.lexists(src):
2237 os.makedirs(src)
2238
Conley Owens80b87fe2014-05-09 17:13:44 -07002239 # If the source file doesn't exist, ensure the destination
2240 # file doesn't either.
2241 if name in symlink_files and not os.path.lexists(src):
2242 try:
2243 os.remove(dst)
2244 except OSError:
2245 pass
2246
David James8d201162013-10-11 17:03:19 -07002247 if name in to_symlink:
2248 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2249 elif copy_all and not os.path.islink(dst):
2250 if os.path.isdir(src):
2251 shutil.copytree(src, dst)
2252 elif os.path.isfile(src):
2253 shutil.copy(src, dst)
2254 except OSError as e:
2255 if e.errno == errno.EPERM:
2256 raise GitError('filesystem must support symlinks')
2257 else:
2258 raise
2259
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002260 def _InitWorkTree(self):
2261 dotgit = os.path.join(self.worktree, '.git')
2262 if not os.path.exists(dotgit):
2263 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002264 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2265 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002266
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002267 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002268
2269 cmd = ['read-tree', '--reset', '-u']
2270 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002271 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002272 if GitCommand(self, cmd).Wait() != 0:
2273 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002274
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002275 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002276
2277 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002278 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002279
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002280 def _revlist(self, *args, **kw):
2281 a = []
2282 a.extend(args)
2283 a.append('--')
2284 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002285
2286 @property
2287 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002288 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002289
Julien Camperguedd654222014-01-09 16:21:37 +01002290 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2291 """Get logs between two revisions of this project."""
2292 comp = '..'
2293 if rev1:
2294 revs = [rev1]
2295 if rev2:
2296 revs.extend([comp, rev2])
2297 cmd = ['log', ''.join(revs)]
2298 out = DiffColoring(self.config)
2299 if out.is_on and color:
2300 cmd.append('--color')
2301 if oneline:
2302 cmd.append('--oneline')
2303
2304 try:
2305 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2306 if log.Wait() == 0:
2307 return log.stdout
2308 except GitError:
2309 # worktree may not exist if groups changed for example. In that case,
2310 # try in gitdir instead.
2311 if not os.path.exists(self.worktree):
2312 return self.bare_git.log(*cmd[1:])
2313 else:
2314 raise
2315 return None
2316
2317 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2318 """Get the list of logs from this revision to given revisionId"""
2319 logs = {}
2320 selfId = self.GetRevisionId(self._allrefs)
2321 toId = toProject.GetRevisionId(toProject._allrefs)
2322
2323 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2324 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2325 return logs
2326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002327 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002328 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002329 self._project = project
2330 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002331 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002332
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002333 def LsOthers(self):
2334 p = GitCommand(self._project,
2335 ['ls-files',
2336 '-z',
2337 '--others',
2338 '--exclude-standard'],
2339 bare = False,
David James8d201162013-10-11 17:03:19 -07002340 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002341 capture_stdout = True,
2342 capture_stderr = True)
2343 if p.Wait() == 0:
2344 out = p.stdout
2345 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002346 return out[:-1].split('\0') # pylint: disable=W1401
2347 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002348 return []
2349
2350 def DiffZ(self, name, *args):
2351 cmd = [name]
2352 cmd.append('-z')
2353 cmd.extend(args)
2354 p = GitCommand(self._project,
2355 cmd,
David James8d201162013-10-11 17:03:19 -07002356 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002357 bare = False,
2358 capture_stdout = True,
2359 capture_stderr = True)
2360 try:
2361 out = p.process.stdout.read()
2362 r = {}
2363 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002364 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002365 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002366 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002367 info = next(out)
2368 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002369 except StopIteration:
2370 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002371
2372 class _Info(object):
2373 def __init__(self, path, omode, nmode, oid, nid, state):
2374 self.path = path
2375 self.src_path = None
2376 self.old_mode = omode
2377 self.new_mode = nmode
2378 self.old_id = oid
2379 self.new_id = nid
2380
2381 if len(state) == 1:
2382 self.status = state
2383 self.level = None
2384 else:
2385 self.status = state[:1]
2386 self.level = state[1:]
2387 while self.level.startswith('0'):
2388 self.level = self.level[1:]
2389
2390 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002391 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002392 if info.status in ('R', 'C'):
2393 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002394 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002395 r[info.path] = info
2396 return r
2397 finally:
2398 p.Wait()
2399
2400 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002401 if self._bare:
2402 path = os.path.join(self._project.gitdir, HEAD)
2403 else:
2404 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002405 try:
2406 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002407 except IOError as e:
2408 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002409 try:
2410 line = fd.read()
2411 finally:
2412 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302413 try:
2414 line = line.decode()
2415 except AttributeError:
2416 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002417 if line.startswith('ref: '):
2418 return line[5:-1]
2419 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002420
2421 def SetHead(self, ref, message=None):
2422 cmdv = []
2423 if message is not None:
2424 cmdv.extend(['-m', message])
2425 cmdv.append(HEAD)
2426 cmdv.append(ref)
2427 self.symbolic_ref(*cmdv)
2428
2429 def DetachHead(self, new, message=None):
2430 cmdv = ['--no-deref']
2431 if message is not None:
2432 cmdv.extend(['-m', message])
2433 cmdv.append(HEAD)
2434 cmdv.append(new)
2435 self.update_ref(*cmdv)
2436
2437 def UpdateRef(self, name, new, old=None,
2438 message=None,
2439 detach=False):
2440 cmdv = []
2441 if message is not None:
2442 cmdv.extend(['-m', message])
2443 if detach:
2444 cmdv.append('--no-deref')
2445 cmdv.append(name)
2446 cmdv.append(new)
2447 if old is not None:
2448 cmdv.append(old)
2449 self.update_ref(*cmdv)
2450
2451 def DeleteRef(self, name, old=None):
2452 if not old:
2453 old = self.rev_parse(name)
2454 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002455 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002456
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002457 def rev_list(self, *args, **kw):
2458 if 'format' in kw:
2459 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2460 else:
2461 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002462 cmdv.extend(args)
2463 p = GitCommand(self._project,
2464 cmdv,
2465 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002466 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002467 capture_stdout = True,
2468 capture_stderr = True)
2469 r = []
2470 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002471 if line[-1] == '\n':
2472 line = line[:-1]
2473 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002474 if p.Wait() != 0:
2475 raise GitError('%s rev-list %s: %s' % (
2476 self._project.name,
2477 str(args),
2478 p.stderr))
2479 return r
2480
2481 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002482 """Allow arbitrary git commands using pythonic syntax.
2483
2484 This allows you to do things like:
2485 git_obj.rev_parse('HEAD')
2486
2487 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2488 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002489 Any other positional arguments will be passed to the git command, and the
2490 following keyword arguments are supported:
2491 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002492
2493 Args:
2494 name: The name of the git command to call. Any '_' characters will
2495 be replaced with '-'.
2496
2497 Returns:
2498 A callable object that will try to call git with the named command.
2499 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002500 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002501 def runner(*args, **kwargs):
2502 cmdv = []
2503 config = kwargs.pop('config', None)
2504 for k in kwargs:
2505 raise TypeError('%s() got an unexpected keyword argument %r'
2506 % (name, k))
2507 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002508 if not git_require((1, 7, 2)):
2509 raise ValueError('cannot set config on command line for %s()'
2510 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302511 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002512 cmdv.append('-c')
2513 cmdv.append('%s=%s' % (k, v))
2514 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002515 cmdv.extend(args)
2516 p = GitCommand(self._project,
2517 cmdv,
2518 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002519 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002520 capture_stdout = True,
2521 capture_stderr = True)
2522 if p.Wait() != 0:
2523 raise GitError('%s %s: %s' % (
2524 self._project.name,
2525 name,
2526 p.stderr))
2527 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302528 try:
Conley Owensedd01512013-09-26 12:59:58 -07002529 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302530 except AttributeError:
2531 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002532 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2533 return r[:-1]
2534 return r
2535 return runner
2536
2537
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002538class _PriorSyncFailedError(Exception):
2539 def __str__(self):
2540 return 'prior sync failed; rebase still in progress'
2541
2542class _DirtyError(Exception):
2543 def __str__(self):
2544 return 'contains uncommitted changes'
2545
2546class _InfoMessage(object):
2547 def __init__(self, project, text):
2548 self.project = project
2549 self.text = text
2550
2551 def Print(self, syncbuf):
2552 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2553 syncbuf.out.nl()
2554
2555class _Failure(object):
2556 def __init__(self, project, why):
2557 self.project = project
2558 self.why = why
2559
2560 def Print(self, syncbuf):
2561 syncbuf.out.fail('error: %s/: %s',
2562 self.project.relpath,
2563 str(self.why))
2564 syncbuf.out.nl()
2565
2566class _Later(object):
2567 def __init__(self, project, action):
2568 self.project = project
2569 self.action = action
2570
2571 def Run(self, syncbuf):
2572 out = syncbuf.out
2573 out.project('project %s/', self.project.relpath)
2574 out.nl()
2575 try:
2576 self.action()
2577 out.nl()
2578 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002579 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002580 out.nl()
2581 return False
2582
2583class _SyncColoring(Coloring):
2584 def __init__(self, config):
2585 Coloring.__init__(self, config, 'reposync')
2586 self.project = self.printer('header', attr = 'bold')
2587 self.info = self.printer('info')
2588 self.fail = self.printer('fail', fg='red')
2589
2590class SyncBuffer(object):
2591 def __init__(self, config, detach_head=False):
2592 self._messages = []
2593 self._failures = []
2594 self._later_queue1 = []
2595 self._later_queue2 = []
2596
2597 self.out = _SyncColoring(config)
2598 self.out.redirect(sys.stderr)
2599
2600 self.detach_head = detach_head
2601 self.clean = True
2602
2603 def info(self, project, fmt, *args):
2604 self._messages.append(_InfoMessage(project, fmt % args))
2605
2606 def fail(self, project, err=None):
2607 self._failures.append(_Failure(project, err))
2608 self.clean = False
2609
2610 def later1(self, project, what):
2611 self._later_queue1.append(_Later(project, what))
2612
2613 def later2(self, project, what):
2614 self._later_queue2.append(_Later(project, what))
2615
2616 def Finish(self):
2617 self._PrintMessages()
2618 self._RunLater()
2619 self._PrintMessages()
2620 return self.clean
2621
2622 def _RunLater(self):
2623 for q in ['_later_queue1', '_later_queue2']:
2624 if not self._RunQueue(q):
2625 return
2626
2627 def _RunQueue(self, queue):
2628 for m in getattr(self, queue):
2629 if not m.Run(self):
2630 self.clean = False
2631 return False
2632 setattr(self, queue, [])
2633 return True
2634
2635 def _PrintMessages(self):
2636 for m in self._messages:
2637 m.Print(self)
2638 for m in self._failures:
2639 m.Print(self)
2640
2641 self._messages = []
2642 self._failures = []
2643
2644
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002645class MetaProject(Project):
2646 """A special project housed under .repo.
2647 """
2648 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002649 Project.__init__(self,
2650 manifest = manifest,
2651 name = name,
2652 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002653 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002654 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002655 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002656 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002657 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002658 revisionId = None,
2659 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002660
2661 def PreSync(self):
2662 if self.Exists:
2663 cb = self.CurrentBranch
2664 if cb:
2665 base = self.GetBranch(cb).merge
2666 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002667 self.revisionExpr = base
2668 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002669
Florian Vallee5d016502012-06-07 17:19:26 +02002670 def MetaBranchSwitch(self, target):
2671 """ Prepare MetaProject for manifest branch switch
2672 """
2673
2674 # detach and delete manifest branch, allowing a new
2675 # branch to take over
2676 syncbuf = SyncBuffer(self.config, detach_head = True)
2677 self.Sync_LocalHalf(syncbuf)
2678 syncbuf.Finish()
2679
2680 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002681 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002682 capture_stdout = True,
2683 capture_stderr = True).Wait() == 0
2684
2685
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002686 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002687 def LastFetch(self):
2688 try:
2689 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2690 return os.path.getmtime(fh)
2691 except OSError:
2692 return 0
2693
2694 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002695 def HasChanges(self):
2696 """Has the remote received new commits not yet checked out?
2697 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002698 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002699 return False
2700
David Pursehouse8a68ff92012-09-24 12:15:13 +09002701 all_refs = self.bare_ref.all
2702 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002703 head = self.work_git.GetHead()
2704 if head.startswith(R_HEADS):
2705 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002706 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002707 except KeyError:
2708 head = None
2709
2710 if revid == head:
2711 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002712 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002713 return True
2714 return False