blob: 339f1a1453e4d192583a8eaf39daa3b9eedca114 [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):
Kris Giesingc8d882a2014-12-23 13:02:32 -08001976 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
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
Kris Giesingc8d882a2014-12-23 13:02:32 -08001985 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001986 try:
1987 with open(path) as f:
1988 if f.read(16) == '# v2 git bundle\n':
1989 return True
1990 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08001991 if not quiet:
1992 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001993 return False
1994 except OSError:
1995 return False
1996
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001997 def _GetBundleCookieFile(self, url):
1998 if url.startswith('persistent-'):
1999 try:
2000 p = subprocess.Popen(
2001 ['git-remote-persistent-https', '-print_config', url],
2002 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2003 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07002004 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002005 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07002006 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002007 for line in p.stdout:
2008 line = line.strip()
2009 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07002010 cookiefile = line[len(prefix):]
2011 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002012 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08002013 err_msg = p.stderr.read()
2014 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002015 pass # Persistent proxy doesn't support -print_config.
2016 else:
Conley Owenscbc07982013-11-21 10:38:03 -08002017 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07002018 if cookiefile:
2019 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002020 except OSError as e:
2021 if e.errno == errno.ENOENT:
2022 pass # No persistent proxy.
2023 raise
2024 return GitConfig.ForUser().GetString('http.cookiefile')
2025
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002026 def _Checkout(self, rev, quiet=False):
2027 cmd = ['checkout']
2028 if quiet:
2029 cmd.append('-q')
2030 cmd.append(rev)
2031 cmd.append('--')
2032 if GitCommand(self, cmd).Wait() != 0:
2033 if self._allrefs:
2034 raise GitError('%s checkout %s ' % (self.name, rev))
2035
Pierre Tardye5a21222011-03-24 16:28:18 +01002036 def _CherryPick(self, rev, quiet=False):
2037 cmd = ['cherry-pick']
2038 cmd.append(rev)
2039 cmd.append('--')
2040 if GitCommand(self, cmd).Wait() != 0:
2041 if self._allrefs:
2042 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2043
Erwan Mahea94f1622011-08-19 13:56:09 +02002044 def _Revert(self, rev, quiet=False):
2045 cmd = ['revert']
2046 cmd.append('--no-edit')
2047 cmd.append(rev)
2048 cmd.append('--')
2049 if GitCommand(self, cmd).Wait() != 0:
2050 if self._allrefs:
2051 raise GitError('%s revert %s ' % (self.name, rev))
2052
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002053 def _ResetHard(self, rev, quiet=True):
2054 cmd = ['reset', '--hard']
2055 if quiet:
2056 cmd.append('-q')
2057 cmd.append(rev)
2058 if GitCommand(self, cmd).Wait() != 0:
2059 raise GitError('%s reset --hard %s ' % (self.name, rev))
2060
2061 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002062 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002063 if onto is not None:
2064 cmd.extend(['--onto', onto])
2065 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002066 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002067 raise GitError('%s rebase %s ' % (self.name, upstream))
2068
Pierre Tardy3d125942012-05-04 12:18:12 +02002069 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002070 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002071 if ffonly:
2072 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002073 if GitCommand(self, cmd).Wait() != 0:
2074 raise GitError('%s merge %s ' % (self.name, head))
2075
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002076 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002077 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002078
2079 # Initialize the bare repository, which contains all of the objects.
2080 if not os.path.exists(self.objdir):
2081 os.makedirs(self.objdir)
2082 self.bare_objdir.init()
2083
2084 # If we have a separate directory to hold refs, initialize it as well.
2085 if self.objdir != self.gitdir:
2086 os.makedirs(self.gitdir)
2087 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2088 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002089
Shawn O. Pearce88443382010-10-08 10:02:09 +02002090 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002091 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002092
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002093 if ref_dir or mirror_git:
2094 if not mirror_git:
2095 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002096 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2097 self.relpath + '.git')
2098
2099 if os.path.exists(mirror_git):
2100 ref_dir = mirror_git
2101
2102 elif os.path.exists(repo_git):
2103 ref_dir = repo_git
2104
2105 else:
2106 ref_dir = None
2107
2108 if ref_dir:
2109 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2110 os.path.join(ref_dir, 'objects') + '\n')
2111
Jimmie Westera0444582012-10-24 13:44:42 +02002112 self._UpdateHooks()
2113
2114 m = self.manifest.manifestProject.config
2115 for key in ['user.name', 'user.email']:
2116 if m.Has(key, include_defaults = False):
2117 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002118 if self.manifest.IsMirror:
2119 self.config.SetString('core.bare', 'true')
2120 else:
2121 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002122
Jimmie Westera0444582012-10-24 13:44:42 +02002123 def _UpdateHooks(self):
2124 if os.path.exists(self.gitdir):
2125 # Always recreate hooks since they can have been changed
2126 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002127 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002128 try:
2129 to_rm = os.listdir(hooks)
2130 except OSError:
2131 to_rm = []
2132 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002133 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002134 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002135
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002136 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002137 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002138 if not os.path.exists(hooks):
2139 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002140 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002141 name = os.path.basename(stock_hook)
2142
Victor Boivie65e0f352011-04-18 11:23:29 +02002143 if name in ('commit-msg',) and not self.remote.review \
2144 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002145 # Don't install a Gerrit Code Review hook if this
2146 # project does not appear to use it for reviews.
2147 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002148 # Since the manifest project is one of those, but also
2149 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002150 continue
2151
2152 dst = os.path.join(hooks, name)
2153 if os.path.islink(dst):
2154 continue
2155 if os.path.exists(dst):
2156 if filecmp.cmp(stock_hook, dst, shallow=False):
2157 os.remove(dst)
2158 else:
2159 _error("%s: Not replacing %s hook", self.relpath, name)
2160 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002161 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002162 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002163 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002164 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002165 raise GitError('filesystem must support symlinks')
2166 else:
2167 raise
2168
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002169 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002170 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002171 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002172 remote.url = self.remote.url
2173 remote.review = self.remote.review
2174 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002175
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002176 if self.worktree:
2177 remote.ResetFetch(mirror=False)
2178 else:
2179 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002180 remote.Save()
2181
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002182 def _InitMRef(self):
2183 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002184 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002185
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002186 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002187 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002188
2189 def _InitAnyMRef(self, ref):
2190 cur = self.bare_ref.symref(ref)
2191
2192 if self.revisionId:
2193 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2194 msg = 'manifest set to %s' % self.revisionId
2195 dst = self.revisionId + '^0'
2196 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2197 else:
2198 remote = self.GetRemote(self.remote.name)
2199 dst = remote.ToLocal(self.revisionExpr)
2200 if cur != dst:
2201 msg = 'manifest set to %s' % self.revisionExpr
2202 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002203
David James8d201162013-10-11 17:03:19 -07002204 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2205 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2206
2207 Args:
2208 gitdir: The bare git repository. Must already be initialized.
2209 dotgit: The repository you would like to initialize.
2210 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2211 Only one work tree can store refs under a given |gitdir|.
2212 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2213 This saves you the effort of initializing |dotgit| yourself.
2214 """
2215 # These objects can be shared between several working trees.
2216 symlink_files = ['description', 'info']
2217 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2218 if share_refs:
2219 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002220 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002221 symlink_dirs += ['logs', 'refs']
2222 to_symlink = symlink_files + symlink_dirs
2223
2224 to_copy = []
2225 if copy_all:
2226 to_copy = os.listdir(gitdir)
2227
2228 for name in set(to_copy).union(to_symlink):
2229 try:
2230 src = os.path.realpath(os.path.join(gitdir, name))
2231 dst = os.path.realpath(os.path.join(dotgit, name))
2232
2233 if os.path.lexists(dst) and not os.path.islink(dst):
2234 raise GitError('cannot overwrite a local work tree')
2235
2236 # If the source dir doesn't exist, create an empty dir.
2237 if name in symlink_dirs and not os.path.lexists(src):
2238 os.makedirs(src)
2239
Conley Owens80b87fe2014-05-09 17:13:44 -07002240 # If the source file doesn't exist, ensure the destination
2241 # file doesn't either.
2242 if name in symlink_files and not os.path.lexists(src):
2243 try:
2244 os.remove(dst)
2245 except OSError:
2246 pass
2247
David James8d201162013-10-11 17:03:19 -07002248 if name in to_symlink:
2249 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2250 elif copy_all and not os.path.islink(dst):
2251 if os.path.isdir(src):
2252 shutil.copytree(src, dst)
2253 elif os.path.isfile(src):
2254 shutil.copy(src, dst)
2255 except OSError as e:
2256 if e.errno == errno.EPERM:
2257 raise GitError('filesystem must support symlinks')
2258 else:
2259 raise
2260
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002261 def _InitWorkTree(self):
2262 dotgit = os.path.join(self.worktree, '.git')
2263 if not os.path.exists(dotgit):
2264 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002265 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2266 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002267
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002268 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002269
2270 cmd = ['read-tree', '--reset', '-u']
2271 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002272 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002273 if GitCommand(self, cmd).Wait() != 0:
2274 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002275
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002276 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002277
2278 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002279 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002280
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002281 def _revlist(self, *args, **kw):
2282 a = []
2283 a.extend(args)
2284 a.append('--')
2285 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002286
2287 @property
2288 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002289 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002290
Julien Camperguedd654222014-01-09 16:21:37 +01002291 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2292 """Get logs between two revisions of this project."""
2293 comp = '..'
2294 if rev1:
2295 revs = [rev1]
2296 if rev2:
2297 revs.extend([comp, rev2])
2298 cmd = ['log', ''.join(revs)]
2299 out = DiffColoring(self.config)
2300 if out.is_on and color:
2301 cmd.append('--color')
2302 if oneline:
2303 cmd.append('--oneline')
2304
2305 try:
2306 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2307 if log.Wait() == 0:
2308 return log.stdout
2309 except GitError:
2310 # worktree may not exist if groups changed for example. In that case,
2311 # try in gitdir instead.
2312 if not os.path.exists(self.worktree):
2313 return self.bare_git.log(*cmd[1:])
2314 else:
2315 raise
2316 return None
2317
2318 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2319 """Get the list of logs from this revision to given revisionId"""
2320 logs = {}
2321 selfId = self.GetRevisionId(self._allrefs)
2322 toId = toProject.GetRevisionId(toProject._allrefs)
2323
2324 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2325 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2326 return logs
2327
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002328 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002329 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002330 self._project = project
2331 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002332 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002333
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002334 def LsOthers(self):
2335 p = GitCommand(self._project,
2336 ['ls-files',
2337 '-z',
2338 '--others',
2339 '--exclude-standard'],
2340 bare = False,
David James8d201162013-10-11 17:03:19 -07002341 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002342 capture_stdout = True,
2343 capture_stderr = True)
2344 if p.Wait() == 0:
2345 out = p.stdout
2346 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002347 return out[:-1].split('\0') # pylint: disable=W1401
2348 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002349 return []
2350
2351 def DiffZ(self, name, *args):
2352 cmd = [name]
2353 cmd.append('-z')
2354 cmd.extend(args)
2355 p = GitCommand(self._project,
2356 cmd,
David James8d201162013-10-11 17:03:19 -07002357 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002358 bare = False,
2359 capture_stdout = True,
2360 capture_stderr = True)
2361 try:
2362 out = p.process.stdout.read()
2363 r = {}
2364 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002365 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002366 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002367 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002368 info = next(out)
2369 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002370 except StopIteration:
2371 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002372
2373 class _Info(object):
2374 def __init__(self, path, omode, nmode, oid, nid, state):
2375 self.path = path
2376 self.src_path = None
2377 self.old_mode = omode
2378 self.new_mode = nmode
2379 self.old_id = oid
2380 self.new_id = nid
2381
2382 if len(state) == 1:
2383 self.status = state
2384 self.level = None
2385 else:
2386 self.status = state[:1]
2387 self.level = state[1:]
2388 while self.level.startswith('0'):
2389 self.level = self.level[1:]
2390
2391 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002392 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002393 if info.status in ('R', 'C'):
2394 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002395 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002396 r[info.path] = info
2397 return r
2398 finally:
2399 p.Wait()
2400
2401 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002402 if self._bare:
2403 path = os.path.join(self._project.gitdir, HEAD)
2404 else:
2405 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002406 try:
2407 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002408 except IOError as e:
2409 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002410 try:
2411 line = fd.read()
2412 finally:
2413 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302414 try:
2415 line = line.decode()
2416 except AttributeError:
2417 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002418 if line.startswith('ref: '):
2419 return line[5:-1]
2420 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002421
2422 def SetHead(self, ref, message=None):
2423 cmdv = []
2424 if message is not None:
2425 cmdv.extend(['-m', message])
2426 cmdv.append(HEAD)
2427 cmdv.append(ref)
2428 self.symbolic_ref(*cmdv)
2429
2430 def DetachHead(self, new, message=None):
2431 cmdv = ['--no-deref']
2432 if message is not None:
2433 cmdv.extend(['-m', message])
2434 cmdv.append(HEAD)
2435 cmdv.append(new)
2436 self.update_ref(*cmdv)
2437
2438 def UpdateRef(self, name, new, old=None,
2439 message=None,
2440 detach=False):
2441 cmdv = []
2442 if message is not None:
2443 cmdv.extend(['-m', message])
2444 if detach:
2445 cmdv.append('--no-deref')
2446 cmdv.append(name)
2447 cmdv.append(new)
2448 if old is not None:
2449 cmdv.append(old)
2450 self.update_ref(*cmdv)
2451
2452 def DeleteRef(self, name, old=None):
2453 if not old:
2454 old = self.rev_parse(name)
2455 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002456 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002457
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002458 def rev_list(self, *args, **kw):
2459 if 'format' in kw:
2460 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2461 else:
2462 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002463 cmdv.extend(args)
2464 p = GitCommand(self._project,
2465 cmdv,
2466 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002467 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002468 capture_stdout = True,
2469 capture_stderr = True)
2470 r = []
2471 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002472 if line[-1] == '\n':
2473 line = line[:-1]
2474 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002475 if p.Wait() != 0:
2476 raise GitError('%s rev-list %s: %s' % (
2477 self._project.name,
2478 str(args),
2479 p.stderr))
2480 return r
2481
2482 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002483 """Allow arbitrary git commands using pythonic syntax.
2484
2485 This allows you to do things like:
2486 git_obj.rev_parse('HEAD')
2487
2488 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2489 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002490 Any other positional arguments will be passed to the git command, and the
2491 following keyword arguments are supported:
2492 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002493
2494 Args:
2495 name: The name of the git command to call. Any '_' characters will
2496 be replaced with '-'.
2497
2498 Returns:
2499 A callable object that will try to call git with the named command.
2500 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002501 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002502 def runner(*args, **kwargs):
2503 cmdv = []
2504 config = kwargs.pop('config', None)
2505 for k in kwargs:
2506 raise TypeError('%s() got an unexpected keyword argument %r'
2507 % (name, k))
2508 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002509 if not git_require((1, 7, 2)):
2510 raise ValueError('cannot set config on command line for %s()'
2511 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302512 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002513 cmdv.append('-c')
2514 cmdv.append('%s=%s' % (k, v))
2515 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002516 cmdv.extend(args)
2517 p = GitCommand(self._project,
2518 cmdv,
2519 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002520 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002521 capture_stdout = True,
2522 capture_stderr = True)
2523 if p.Wait() != 0:
2524 raise GitError('%s %s: %s' % (
2525 self._project.name,
2526 name,
2527 p.stderr))
2528 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302529 try:
Conley Owensedd01512013-09-26 12:59:58 -07002530 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302531 except AttributeError:
2532 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002533 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2534 return r[:-1]
2535 return r
2536 return runner
2537
2538
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002539class _PriorSyncFailedError(Exception):
2540 def __str__(self):
2541 return 'prior sync failed; rebase still in progress'
2542
2543class _DirtyError(Exception):
2544 def __str__(self):
2545 return 'contains uncommitted changes'
2546
2547class _InfoMessage(object):
2548 def __init__(self, project, text):
2549 self.project = project
2550 self.text = text
2551
2552 def Print(self, syncbuf):
2553 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2554 syncbuf.out.nl()
2555
2556class _Failure(object):
2557 def __init__(self, project, why):
2558 self.project = project
2559 self.why = why
2560
2561 def Print(self, syncbuf):
2562 syncbuf.out.fail('error: %s/: %s',
2563 self.project.relpath,
2564 str(self.why))
2565 syncbuf.out.nl()
2566
2567class _Later(object):
2568 def __init__(self, project, action):
2569 self.project = project
2570 self.action = action
2571
2572 def Run(self, syncbuf):
2573 out = syncbuf.out
2574 out.project('project %s/', self.project.relpath)
2575 out.nl()
2576 try:
2577 self.action()
2578 out.nl()
2579 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002580 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002581 out.nl()
2582 return False
2583
2584class _SyncColoring(Coloring):
2585 def __init__(self, config):
2586 Coloring.__init__(self, config, 'reposync')
2587 self.project = self.printer('header', attr = 'bold')
2588 self.info = self.printer('info')
2589 self.fail = self.printer('fail', fg='red')
2590
2591class SyncBuffer(object):
2592 def __init__(self, config, detach_head=False):
2593 self._messages = []
2594 self._failures = []
2595 self._later_queue1 = []
2596 self._later_queue2 = []
2597
2598 self.out = _SyncColoring(config)
2599 self.out.redirect(sys.stderr)
2600
2601 self.detach_head = detach_head
2602 self.clean = True
2603
2604 def info(self, project, fmt, *args):
2605 self._messages.append(_InfoMessage(project, fmt % args))
2606
2607 def fail(self, project, err=None):
2608 self._failures.append(_Failure(project, err))
2609 self.clean = False
2610
2611 def later1(self, project, what):
2612 self._later_queue1.append(_Later(project, what))
2613
2614 def later2(self, project, what):
2615 self._later_queue2.append(_Later(project, what))
2616
2617 def Finish(self):
2618 self._PrintMessages()
2619 self._RunLater()
2620 self._PrintMessages()
2621 return self.clean
2622
2623 def _RunLater(self):
2624 for q in ['_later_queue1', '_later_queue2']:
2625 if not self._RunQueue(q):
2626 return
2627
2628 def _RunQueue(self, queue):
2629 for m in getattr(self, queue):
2630 if not m.Run(self):
2631 self.clean = False
2632 return False
2633 setattr(self, queue, [])
2634 return True
2635
2636 def _PrintMessages(self):
2637 for m in self._messages:
2638 m.Print(self)
2639 for m in self._failures:
2640 m.Print(self)
2641
2642 self._messages = []
2643 self._failures = []
2644
2645
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002646class MetaProject(Project):
2647 """A special project housed under .repo.
2648 """
2649 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002650 Project.__init__(self,
2651 manifest = manifest,
2652 name = name,
2653 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002654 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002655 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002656 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002657 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002658 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002659 revisionId = None,
2660 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002661
2662 def PreSync(self):
2663 if self.Exists:
2664 cb = self.CurrentBranch
2665 if cb:
2666 base = self.GetBranch(cb).merge
2667 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002668 self.revisionExpr = base
2669 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002670
Florian Vallee5d016502012-06-07 17:19:26 +02002671 def MetaBranchSwitch(self, target):
2672 """ Prepare MetaProject for manifest branch switch
2673 """
2674
2675 # detach and delete manifest branch, allowing a new
2676 # branch to take over
2677 syncbuf = SyncBuffer(self.config, detach_head = True)
2678 self.Sync_LocalHalf(syncbuf)
2679 syncbuf.Finish()
2680
2681 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002682 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002683 capture_stdout = True,
2684 capture_stderr = True).Wait() == 0
2685
2686
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002687 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002688 def LastFetch(self):
2689 try:
2690 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2691 return os.path.getmtime(fh)
2692 except OSError:
2693 return 0
2694
2695 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002696 def HasChanges(self):
2697 """Has the remote received new commits not yet checked out?
2698 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002699 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002700 return False
2701
David Pursehouse8a68ff92012-09-24 12:15:13 +09002702 all_refs = self.bare_ref.all
2703 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002704 head = self.work_git.GetHead()
2705 if head.startswith(R_HEADS):
2706 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002707 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002708 except KeyError:
2709 head = None
2710
2711 if revid == head:
2712 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002713 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002714 return True
2715 return False