blob: e501cc26474255315c113722afbb94c76659a985 [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 ##
739
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500740 def HasChanges(self):
741 """Returns true if there are uncommitted changes.
742 """
743 self.work_git.update_index('-q',
744 '--unmerged',
745 '--ignore-missing',
746 '--refresh')
747 if self.IsRebaseInProgress():
748 return True
749
750 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
751 return True
752
753 if self.work_git.DiffZ('diff-files'):
754 return True
755
756 if self.work_git.LsOthers():
757 return True
758
759 return False
760
Terence Haddock4655e812011-03-31 12:33:34 +0200761 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700762 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200763
764 Args:
765 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700766 """
767 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200768 if output_redir == None:
769 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700770 print(file=output_redir)
771 print('project %s/' % self.relpath, file=output_redir)
772 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700773 return
774
775 self.work_git.update_index('-q',
776 '--unmerged',
777 '--ignore-missing',
778 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700779 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
781 df = self.work_git.DiffZ('diff-files')
782 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100783 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700784 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700785
786 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200787 if not output_redir == None:
788 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700789 out.project('project %-40s', self.relpath + '/')
790
791 branch = self.CurrentBranch
792 if branch is None:
793 out.nobranch('(*** NO BRANCH ***)')
794 else:
795 out.branch('branch %s', branch)
796 out.nl()
797
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700798 if rb:
799 out.important('prior sync failed; rebase still in progress')
800 out.nl()
801
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700802 paths = list()
803 paths.extend(di.keys())
804 paths.extend(df.keys())
805 paths.extend(do)
806
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530807 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900808 try:
809 i = di[p]
810 except KeyError:
811 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900813 try:
814 f = df[p]
815 except KeyError:
816 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200817
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900818 if i:
819 i_status = i.status.upper()
820 else:
821 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700822
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900823 if f:
824 f_status = f.status.lower()
825 else:
826 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700827
828 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800829 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830 i.src_path, p, i.level)
831 else:
832 line = ' %s%s\t%s' % (i_status, f_status, p)
833
834 if i and not f:
835 out.added('%s', line)
836 elif (i and f) or (not i and f):
837 out.changed('%s', line)
838 elif not i and not f:
839 out.untracked('%s', line)
840 else:
841 out.write('%s', line)
842 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200843
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700844 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845
pelyad67872d2012-03-28 14:49:58 +0300846 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700847 """Prints the status of the repository to stdout.
848 """
849 out = DiffColoring(self.config)
850 cmd = ['diff']
851 if out.is_on:
852 cmd.append('--color')
853 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300854 if absolute_paths:
855 cmd.append('--src-prefix=a/%s/' % self.relpath)
856 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857 cmd.append('--')
858 p = GitCommand(self,
859 cmd,
860 capture_stdout = True,
861 capture_stderr = True)
862 has_diff = False
863 for line in p.process.stdout:
864 if not has_diff:
865 out.nl()
866 out.project('project %s/' % self.relpath)
867 out.nl()
868 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700869 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700870 p.Wait()
871
872
873## Publish / Upload ##
874
David Pursehouse8a68ff92012-09-24 12:15:13 +0900875 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700876 """Was the branch published (uploaded) for code review?
877 If so, returns the SHA-1 hash of the last published
878 state for the branch.
879 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700880 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900881 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700882 try:
883 return self.bare_git.rev_parse(key)
884 except GitError:
885 return None
886 else:
887 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900888 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700889 except KeyError:
890 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700891
David Pursehouse8a68ff92012-09-24 12:15:13 +0900892 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893 """Prunes any stale published refs.
894 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900895 if all_refs is None:
896 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700897 heads = set()
898 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530899 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900 if name.startswith(R_HEADS):
901 heads.add(name)
902 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900903 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530905 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906 n = name[len(R_PUB):]
907 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900908 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700909
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700910 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911 """List any branches which can be uploaded for review.
912 """
913 heads = {}
914 pubed = {}
915
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530916 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900918 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900920 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921
922 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530923 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900924 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700926 if selected_branch and branch != selected_branch:
927 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800929 rb = self.GetUploadableBranch(branch)
930 if rb:
931 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932 return ready
933
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800934 def GetUploadableBranch(self, branch_name):
935 """Get a single uploadable branch, or None.
936 """
937 branch = self.GetBranch(branch_name)
938 base = branch.LocalMerge
939 if branch.LocalMerge:
940 rb = ReviewableBranch(self, branch, base)
941 if rb.commits:
942 return rb
943 return None
944
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700945 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700946 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700947 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400948 draft=False,
949 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950 """Uploads the named branch for code review.
951 """
952 if branch is None:
953 branch = self.CurrentBranch
954 if branch is None:
955 raise GitError('not currently on a branch')
956
957 branch = self.GetBranch(branch)
958 if not branch.LocalMerge:
959 raise GitError('branch %s does not track a remote' % branch.name)
960 if not branch.remote.review:
961 raise GitError('remote %s has no review url' % branch.remote.name)
962
Bryan Jacobsf609f912013-05-06 13:36:24 -0400963 if dest_branch is None:
964 dest_branch = self.dest_branch
965 if dest_branch is None:
966 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967 if not dest_branch.startswith(R_HEADS):
968 dest_branch = R_HEADS + dest_branch
969
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800970 if not branch.remote.projectname:
971 branch.remote.projectname = self.name
972 branch.remote.Save()
973
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800974 url = branch.remote.ReviewUrl(self.UserEmail)
975 if url is None:
976 raise UploadError('review not configured')
977 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800978
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800979 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800980 rp = ['gerrit receive-pack']
981 for e in people[0]:
982 rp.append('--reviewer=%s' % sq(e))
983 for e in people[1]:
984 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800985 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700986
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800987 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800988
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800989 if dest_branch.startswith(R_HEADS):
990 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700991
992 upload_type = 'for'
993 if draft:
994 upload_type = 'drafts'
995
996 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
997 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800998 if auto_topic:
999 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001000 if not url.startswith('ssh://'):
1001 rp = ['r=%s' % p for p in people[0]] + \
1002 ['cc=%s' % p for p in people[1]]
1003 if rp:
1004 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001005 cmd.append(ref_spec)
1006
1007 if GitCommand(self, cmd, bare = True).Wait() != 0:
1008 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001009
1010 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1011 self.bare_git.UpdateRef(R_PUB + branch.name,
1012 R_HEADS + branch.name,
1013 message = msg)
1014
1015
1016## Sync ##
1017
Julien Campergue335f5ef2013-10-16 11:02:35 +02001018 def _ExtractArchive(self, tarpath, path=None):
1019 """Extract the given tar on its current location
1020
1021 Args:
1022 - tarpath: The path to the actual tar file
1023
1024 """
1025 try:
1026 with tarfile.open(tarpath, 'r') as tar:
1027 tar.extractall(path=path)
1028 return True
1029 except (IOError, tarfile.TarError) as e:
1030 print("error: Cannot extract archive %s: "
1031 "%s" % (tarpath, str(e)), file=sys.stderr)
1032 return False
1033
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001034 def Sync_NetworkHalf(self,
1035 quiet=False,
1036 is_new=None,
1037 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001038 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001039 no_tags=False,
1040 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041 """Perform only the network IO portion of the sync process.
1042 Local working directory/branch state is not affected.
1043 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001044 if archive and not isinstance(self, MetaProject):
1045 if self.remote.url.startswith(('http://', 'https://')):
1046 print("error: %s: Cannot fetch archives from http/https "
1047 "remotes." % self.name, file=sys.stderr)
1048 return False
1049
1050 name = self.relpath.replace('\\', '/')
1051 name = name.replace('/', '_')
1052 tarpath = '%s.tar' % name
1053 topdir = self.manifest.topdir
1054
1055 try:
1056 self._FetchArchive(tarpath, cwd=topdir)
1057 except GitError as e:
1058 print('error: %s' % str(e), file=sys.stderr)
1059 return False
1060
1061 # From now on, we only need absolute tarpath
1062 tarpath = os.path.join(topdir, tarpath)
1063
1064 if not self._ExtractArchive(tarpath, path=topdir):
1065 return False
1066 try:
1067 os.remove(tarpath)
1068 except OSError as e:
1069 print("warn: Cannot remove archive %s: "
1070 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001071 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001072 return True
1073
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001074 if is_new is None:
1075 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001076 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001078 else:
1079 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001081
1082 if is_new:
1083 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1084 try:
1085 fd = open(alt, 'rb')
1086 try:
1087 alt_dir = fd.readline().rstrip()
1088 finally:
1089 fd.close()
1090 except IOError:
1091 alt_dir = None
1092 else:
1093 alt_dir = None
1094
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001095 if clone_bundle \
1096 and alt_dir is None \
1097 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001098 is_new = False
1099
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001100 if not current_branch_only:
1101 if self.sync_c:
1102 current_branch_only = True
1103 elif not self.manifest._loaded:
1104 # Manifest cannot check defaults until it syncs.
1105 current_branch_only = False
1106 elif self.manifest.default.sync_c:
1107 current_branch_only = True
1108
Conley Owens666d5342014-05-01 13:09:57 -07001109 has_sha1 = ID_RE.match(self.revisionExpr) and self._CheckForSha1()
1110 if (not has_sha1 #Need to fetch since we don't already have this revision
1111 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1112 current_branch_only=current_branch_only,
1113 no_tags=no_tags)):
1114 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001115
1116 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001117 self._InitMRef()
1118 else:
1119 self._InitMirrorHead()
1120 try:
1121 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1122 except OSError:
1123 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001124 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001125
1126 def PostRepoUpgrade(self):
1127 self._InitHooks()
1128
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001129 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001130 for copyfile in self.copyfiles:
1131 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001132 for linkfile in self.linkfiles:
1133 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001134
Julien Camperguedd654222014-01-09 16:21:37 +01001135 def GetCommitRevisionId(self):
1136 """Get revisionId of a commit.
1137
1138 Use this method instead of GetRevisionId to get the id of the commit rather
1139 than the id of the current git object (for example, a tag)
1140
1141 """
1142 if not self.revisionExpr.startswith(R_TAGS):
1143 return self.GetRevisionId(self._allrefs)
1144
1145 try:
1146 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1147 except GitError:
1148 raise ManifestInvalidRevisionError(
1149 'revision %s in %s not found' % (self.revisionExpr,
1150 self.name))
1151
David Pursehouse8a68ff92012-09-24 12:15:13 +09001152 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001153 if self.revisionId:
1154 return self.revisionId
1155
1156 rem = self.GetRemote(self.remote.name)
1157 rev = rem.ToLocal(self.revisionExpr)
1158
David Pursehouse8a68ff92012-09-24 12:15:13 +09001159 if all_refs is not None and rev in all_refs:
1160 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001161
1162 try:
1163 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1164 except GitError:
1165 raise ManifestInvalidRevisionError(
1166 'revision %s in %s not found' % (self.revisionExpr,
1167 self.name))
1168
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001169 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170 """Perform only the local IO portion of the sync process.
1171 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172 """
David James8d201162013-10-11 17:03:19 -07001173 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001174 all_refs = self.bare_ref.all
1175 self.CleanPublishedCache(all_refs)
1176 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001177
David Pursehouse1d947b32012-10-25 12:23:11 +09001178 def _doff():
1179 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001180 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001181
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001182 head = self.work_git.GetHead()
1183 if head.startswith(R_HEADS):
1184 branch = head[len(R_HEADS):]
1185 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001186 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001187 except KeyError:
1188 head = None
1189 else:
1190 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001191
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001192 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193 # Currently on a detached HEAD. The user is assumed to
1194 # not have any local modifications worth worrying about.
1195 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001196 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001197 syncbuf.fail(self, _PriorSyncFailedError())
1198 return
1199
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001200 if head == revid:
1201 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001202 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001203 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001204 if not syncbuf.detach_head:
1205 return
1206 else:
1207 lost = self._revlist(not_rev(revid), HEAD)
1208 if lost:
1209 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001210
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001211 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001212 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001213 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001214 syncbuf.fail(self, e)
1215 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001216 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001217 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001218
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001219 if head == revid:
1220 # No changes; don't do anything further.
1221 #
1222 return
1223
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001225
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001226 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001228 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001229 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001230 syncbuf.info(self,
1231 "leaving %s; does not track upstream",
1232 branch.name)
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. Pearce3c8dea12009-05-29 18:38:17 -07001241 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001242 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001243 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001244 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001245 if not_merged:
1246 if upstream_gain:
1247 # The user has published this branch and some of those
1248 # commits are not yet merged upstream. We do not want
1249 # to rewrite the published commits so we punt.
1250 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001251 syncbuf.fail(self,
1252 "branch %s is published (but not merged) and is now %d commits behind"
1253 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001254 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001255 elif pub == head:
1256 # All published commits are merged, and thus we are a
1257 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001258 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001259 syncbuf.later1(self, _doff)
1260 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001261
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001262 # Examine the local commits not in the remote. Find the
1263 # last one attributed to this user, if any.
1264 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001265 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001266 last_mine = None
1267 cnt_mine = 0
1268 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301269 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001270 if committer_email == self.UserEmail:
1271 last_mine = commit_id
1272 cnt_mine += 1
1273
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001274 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001275 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001276
1277 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001278 syncbuf.fail(self, _DirtyError())
1279 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001281 # If the upstream switched on us, warn the user.
1282 #
1283 if branch.merge != self.revisionExpr:
1284 if branch.merge and self.revisionExpr:
1285 syncbuf.info(self,
1286 'manifest switched %s...%s',
1287 branch.merge,
1288 self.revisionExpr)
1289 elif branch.merge:
1290 syncbuf.info(self,
1291 'manifest no longer tracks %s',
1292 branch.merge)
1293
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001294 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001295 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001296 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001298 syncbuf.info(self,
1299 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001300 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001302 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001303 if not ID_RE.match(self.revisionExpr):
1304 # in case of manifest sync the revisionExpr might be a SHA1
1305 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 branch.Save()
1307
Mike Pontillod3153822012-02-28 11:53:24 -08001308 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001309 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001310 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001311 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001312 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001313 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001314 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001315 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001316 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001317 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001318 syncbuf.fail(self, e)
1319 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001321 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001323 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 # dest should already be an absolute path, but src is project relative
1325 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001326 abssrc = os.path.join(self.worktree, src)
1327 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001329 def AddLinkFile(self, src, dest, absdest):
1330 # dest should already be an absolute path, but src is project relative
1331 # make src an absolute path
1332 abssrc = os.path.join(self.worktree, src)
1333 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1334
James W. Mills24c13082012-04-12 15:04:13 -05001335 def AddAnnotation(self, name, value, keep):
1336 self.annotations.append(_Annotation(name, value, keep))
1337
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001338 def DownloadPatchSet(self, change_id, patch_id):
1339 """Download a single patch set of a single change to FETCH_HEAD.
1340 """
1341 remote = self.GetRemote(self.remote.name)
1342
1343 cmd = ['fetch', remote.name]
1344 cmd.append('refs/changes/%2.2d/%d/%d' \
1345 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001346 if GitCommand(self, cmd, bare=True).Wait() != 0:
1347 return None
1348 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001349 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001350 change_id,
1351 patch_id,
1352 self.bare_git.rev_parse('FETCH_HEAD'))
1353
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001354
1355## Branch Management ##
1356
1357 def StartBranch(self, name):
1358 """Create a new branch off the manifest's revision.
1359 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001360 head = self.work_git.GetHead()
1361 if head == (R_HEADS + name):
1362 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363
David Pursehouse8a68ff92012-09-24 12:15:13 +09001364 all_refs = self.bare_ref.all
1365 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001366 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001367 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001368 capture_stdout = True,
1369 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001370
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001371 branch = self.GetBranch(name)
1372 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001373 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001374 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001375
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001376 if head.startswith(R_HEADS):
1377 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001378 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001379 except KeyError:
1380 head = None
1381
1382 if revid and head and revid == head:
1383 ref = os.path.join(self.gitdir, R_HEADS + name)
1384 try:
1385 os.makedirs(os.path.dirname(ref))
1386 except OSError:
1387 pass
1388 _lwrite(ref, '%s\n' % revid)
1389 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1390 'ref: %s%s\n' % (R_HEADS, name))
1391 branch.Save()
1392 return True
1393
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001394 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001395 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001396 capture_stdout = True,
1397 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001398 branch.Save()
1399 return True
1400 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001401
Wink Saville02d79452009-04-10 13:01:24 -07001402 def CheckoutBranch(self, name):
1403 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001404
1405 Args:
1406 name: The name of the branch to checkout.
1407
1408 Returns:
1409 True if the checkout succeeded; False if it didn't; None if the branch
1410 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001411 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001412 rev = R_HEADS + name
1413 head = self.work_git.GetHead()
1414 if head == rev:
1415 # Already on the branch
1416 #
1417 return True
Wink Saville02d79452009-04-10 13:01:24 -07001418
David Pursehouse8a68ff92012-09-24 12:15:13 +09001419 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001420 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001421 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001422 except KeyError:
1423 # Branch does not exist in this project
1424 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001425 return None
Wink Saville02d79452009-04-10 13:01:24 -07001426
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001427 if head.startswith(R_HEADS):
1428 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001429 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001430 except KeyError:
1431 head = None
1432
1433 if head == revid:
1434 # Same revision; just update HEAD to point to the new
1435 # target branch, but otherwise take no other action.
1436 #
1437 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1438 'ref: %s%s\n' % (R_HEADS, name))
1439 return True
1440
1441 return GitCommand(self,
1442 ['checkout', name, '--'],
1443 capture_stdout = True,
1444 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001445
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001446 def AbandonBranch(self, name):
1447 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001448
1449 Args:
1450 name: The name of the branch to abandon.
1451
1452 Returns:
1453 True if the abandon succeeded; False if it didn't; None if the branch
1454 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001455 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001456 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001457 all_refs = self.bare_ref.all
1458 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001459 # Doesn't exist
1460 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001461
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001462 head = self.work_git.GetHead()
1463 if head == rev:
1464 # We can't destroy the branch while we are sitting
1465 # on it. Switch to a detached HEAD.
1466 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001467 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001468
David Pursehouse8a68ff92012-09-24 12:15:13 +09001469 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001470 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001471 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1472 '%s\n' % revid)
1473 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001474 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001475
1476 return GitCommand(self,
1477 ['branch', '-D', name],
1478 capture_stdout = True,
1479 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001480
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001481 def PruneHeads(self):
1482 """Prune any topic branches already merged into upstream.
1483 """
1484 cb = self.CurrentBranch
1485 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001486 left = self._allrefs
1487 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001488 if name.startswith(R_HEADS):
1489 name = name[len(R_HEADS):]
1490 if cb is None or name != cb:
1491 kill.append(name)
1492
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001493 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001494 if cb is not None \
1495 and not self._revlist(HEAD + '...' + rev) \
1496 and not self.IsDirty(consider_untracked = False):
1497 self.work_git.DetachHead(HEAD)
1498 kill.append(cb)
1499
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001500 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001501 old = self.bare_git.GetHead()
1502 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001503 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1504
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001505 try:
1506 self.bare_git.DetachHead(rev)
1507
1508 b = ['branch', '-d']
1509 b.extend(kill)
1510 b = GitCommand(self, b, bare=True,
1511 capture_stdout=True,
1512 capture_stderr=True)
1513 b.Wait()
1514 finally:
1515 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001516 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001517
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001518 for branch in kill:
1519 if (R_HEADS + branch) not in left:
1520 self.CleanPublishedCache()
1521 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001522
1523 if cb and cb not in kill:
1524 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001525 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001526
1527 kept = []
1528 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001529 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001530 branch = self.GetBranch(branch)
1531 base = branch.LocalMerge
1532 if not base:
1533 base = rev
1534 kept.append(ReviewableBranch(self, branch, base))
1535 return kept
1536
1537
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001538## Submodule Management ##
1539
1540 def GetRegisteredSubprojects(self):
1541 result = []
1542 def rec(subprojects):
1543 if not subprojects:
1544 return
1545 result.extend(subprojects)
1546 for p in subprojects:
1547 rec(p.subprojects)
1548 rec(self.subprojects)
1549 return result
1550
1551 def _GetSubmodules(self):
1552 # Unfortunately we cannot call `git submodule status --recursive` here
1553 # because the working tree might not exist yet, and it cannot be used
1554 # without a working tree in its current implementation.
1555
1556 def get_submodules(gitdir, rev):
1557 # Parse .gitmodules for submodule sub_paths and sub_urls
1558 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1559 if not sub_paths:
1560 return []
1561 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1562 # revision of submodule repository
1563 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1564 submodules = []
1565 for sub_path, sub_url in zip(sub_paths, sub_urls):
1566 try:
1567 sub_rev = sub_revs[sub_path]
1568 except KeyError:
1569 # Ignore non-exist submodules
1570 continue
1571 submodules.append((sub_rev, sub_path, sub_url))
1572 return submodules
1573
1574 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1575 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1576 def parse_gitmodules(gitdir, rev):
1577 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1578 try:
1579 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1580 bare = True, gitdir = gitdir)
1581 except GitError:
1582 return [], []
1583 if p.Wait() != 0:
1584 return [], []
1585
1586 gitmodules_lines = []
1587 fd, temp_gitmodules_path = tempfile.mkstemp()
1588 try:
1589 os.write(fd, p.stdout)
1590 os.close(fd)
1591 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1592 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1593 bare = True, gitdir = gitdir)
1594 if p.Wait() != 0:
1595 return [], []
1596 gitmodules_lines = p.stdout.split('\n')
1597 except GitError:
1598 return [], []
1599 finally:
1600 os.remove(temp_gitmodules_path)
1601
1602 names = set()
1603 paths = {}
1604 urls = {}
1605 for line in gitmodules_lines:
1606 if not line:
1607 continue
1608 m = re_path.match(line)
1609 if m:
1610 names.add(m.group(1))
1611 paths[m.group(1)] = m.group(2)
1612 continue
1613 m = re_url.match(line)
1614 if m:
1615 names.add(m.group(1))
1616 urls[m.group(1)] = m.group(2)
1617 continue
1618 names = sorted(names)
1619 return ([paths.get(name, '') for name in names],
1620 [urls.get(name, '') for name in names])
1621
1622 def git_ls_tree(gitdir, rev, paths):
1623 cmd = ['ls-tree', rev, '--']
1624 cmd.extend(paths)
1625 try:
1626 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1627 bare = True, gitdir = gitdir)
1628 except GitError:
1629 return []
1630 if p.Wait() != 0:
1631 return []
1632 objects = {}
1633 for line in p.stdout.split('\n'):
1634 if not line.strip():
1635 continue
1636 object_rev, object_path = line.split()[2:4]
1637 objects[object_path] = object_rev
1638 return objects
1639
1640 try:
1641 rev = self.GetRevisionId()
1642 except GitError:
1643 return []
1644 return get_submodules(self.gitdir, rev)
1645
1646 def GetDerivedSubprojects(self):
1647 result = []
1648 if not self.Exists:
1649 # If git repo does not exist yet, querying its submodules will
1650 # mess up its states; so return here.
1651 return result
1652 for rev, path, url in self._GetSubmodules():
1653 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001654 relpath, worktree, gitdir, objdir = \
1655 self.manifest.GetSubprojectPaths(self, name, path)
1656 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001657 if project:
1658 result.extend(project.GetDerivedSubprojects())
1659 continue
David James8d201162013-10-11 17:03:19 -07001660
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001661 remote = RemoteSpec(self.remote.name,
1662 url = url,
Anthony King36ea2fb2014-05-06 11:54:01 +01001663 review = self.remote.review,
1664 revision = self.remote.revision)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001665 subproject = Project(manifest = self.manifest,
1666 name = name,
1667 remote = remote,
1668 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001669 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001670 worktree = worktree,
1671 relpath = relpath,
1672 revisionExpr = self.revisionExpr,
1673 revisionId = rev,
1674 rebase = self.rebase,
1675 groups = self.groups,
1676 sync_c = self.sync_c,
1677 sync_s = self.sync_s,
1678 parent = self,
1679 is_derived = True)
1680 result.append(subproject)
1681 result.extend(subproject.GetDerivedSubprojects())
1682 return result
1683
1684
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001686 def _CheckForSha1(self):
1687 try:
1688 # if revision (sha or tag) is not present then following function
1689 # throws an error.
1690 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1691 return True
1692 except GitError:
1693 # There is no such persistent revision. We have to fetch it.
1694 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001695
Julien Campergue335f5ef2013-10-16 11:02:35 +02001696 def _FetchArchive(self, tarpath, cwd=None):
1697 cmd = ['archive', '-v', '-o', tarpath]
1698 cmd.append('--remote=%s' % self.remote.url)
1699 cmd.append('--prefix=%s/' % self.relpath)
1700 cmd.append(self.revisionExpr)
1701
1702 command = GitCommand(self, cmd, cwd=cwd,
1703 capture_stdout=True,
1704 capture_stderr=True)
1705
1706 if command.Wait() != 0:
1707 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1708
Conley Owens80b87fe2014-05-09 17:13:44 -07001709
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001710 def _RemoteFetch(self, name=None,
1711 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001712 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001713 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001714 alt_dir=None,
1715 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001716
1717 is_sha1 = False
1718 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001719 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001720
David Pursehouse9bc422f2014-04-15 10:28:56 +09001721 # The depth should not be used when fetching to a mirror because
1722 # it will result in a shallow repository that cannot be cloned or
1723 # fetched from.
1724 if not self.manifest.IsMirror:
1725 if self.clone_depth:
1726 depth = self.clone_depth
1727 else:
1728 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1729
Shawn Pearce69e04d82014-01-29 12:48:54 -08001730 if depth:
1731 current_branch_only = True
1732
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001733 if ID_RE.match(self.revisionExpr) is not None:
1734 is_sha1 = True
1735
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001736 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001737 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001738 # this is a tag and its sha1 value should never change
1739 tag_name = self.revisionExpr[len(R_TAGS):]
1740
1741 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001742 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001743 return True
Brian Harring14a66742012-09-28 20:21:57 -07001744 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1745 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001746
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001747 if not name:
1748 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001749
1750 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001751 remote = self.GetRemote(name)
1752 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001753 ssh_proxy = True
1754
Shawn O. Pearce88443382010-10-08 10:02:09 +02001755 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001756 if alt_dir and 'objects' == os.path.basename(alt_dir):
1757 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001758 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1759 remote = self.GetRemote(name)
1760
David Pursehouse8a68ff92012-09-24 12:15:13 +09001761 all_refs = self.bare_ref.all
1762 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001763 tmp = set()
1764
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301765 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001766 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001767 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001768 all_refs[r] = ref_id
1769 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001770 continue
1771
David Pursehouse8a68ff92012-09-24 12:15:13 +09001772 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001773 continue
1774
David Pursehouse8a68ff92012-09-24 12:15:13 +09001775 r = 'refs/_alt/%s' % ref_id
1776 all_refs[r] = ref_id
1777 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001778 tmp.add(r)
1779
Shawn O. Pearce88443382010-10-08 10:02:09 +02001780 tmp_packed = ''
1781 old_packed = ''
1782
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301783 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001784 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001785 tmp_packed += line
1786 if r not in tmp:
1787 old_packed += line
1788
1789 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001790 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001791 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001792
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001793 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001794
1795 # The --depth option only affects the initial fetch; after that we'll do
1796 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001797 if depth and initial:
1798 cmd.append('--depth=%s' % depth)
1799
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001800 if quiet:
1801 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001802 if not self.worktree:
1803 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001804 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001805
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001806 # If using depth then we should not get all the tags since they may
1807 # be outside of the depth.
1808 if no_tags or depth:
1809 cmd.append('--no-tags')
1810 else:
1811 cmd.append('--tags')
1812
Conley Owens80b87fe2014-05-09 17:13:44 -07001813 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001814 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001815 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001816 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001817 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001818 spec.append('tag')
1819 spec.append(tag_name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001820 else:
1821 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001822 if is_sha1:
1823 branch = self.upstream
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001824 if branch is not None and branch.strip():
1825 if not branch.startswith('refs/'):
1826 branch = R_HEADS + branch
1827 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001828 cmd.extend(spec)
1829
1830 shallowfetch = self.config.GetString('repo.shallowfetch')
1831 if shallowfetch and shallowfetch != ' '.join(spec):
1832 GitCommand(self, ['fetch', '--unshallow', name] + shallowfetch.split(),
1833 bare=True, ssh_proxy=ssh_proxy).Wait()
1834 if depth:
1835 self.config.SetString('repo.shallowfetch', ' '.join(spec))
1836 else:
1837 self.config.SetString('repo.shallowfetch', None)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001838
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001839 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001840 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001841 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1842 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001843 ok = True
1844 break
Brian Harring14a66742012-09-28 20:21:57 -07001845 elif current_branch_only and is_sha1 and ret == 128:
1846 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1847 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1848 # abort the optimization attempt and do a full sync.
1849 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001850 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001851
1852 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001853 # Ensure that some refs exist. Otherwise, we probably aren't looking
1854 # at a real git repository and may have a bad url.
1855 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001856 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001857
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001858 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001859 if old_packed != '':
1860 _lwrite(packed_refs, old_packed)
1861 else:
1862 os.remove(packed_refs)
1863 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001864
1865 if is_sha1 and current_branch_only and self.upstream:
1866 # We just synced the upstream given branch; verify we
1867 # got what we wanted, else trigger a second run of all
1868 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001869 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001870 return self._RemoteFetch(name=name, current_branch_only=False,
1871 initial=False, quiet=quiet, alt_dir=alt_dir)
1872
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001873 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001874
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001875 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001876 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001877 return False
1878
1879 remote = self.GetRemote(self.remote.name)
1880 bundle_url = remote.url + '/clone.bundle'
1881 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001882 if GetSchemeFromUrl(bundle_url) not in (
1883 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001884 return False
1885
1886 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1887 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1888
1889 exist_dst = os.path.exists(bundle_dst)
1890 exist_tmp = os.path.exists(bundle_tmp)
1891
1892 if not initial and not exist_dst and not exist_tmp:
1893 return False
1894
1895 if not exist_dst:
1896 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1897 if not exist_dst:
1898 return False
1899
1900 cmd = ['fetch']
1901 if quiet:
1902 cmd.append('--quiet')
1903 if not self.worktree:
1904 cmd.append('--update-head-ok')
1905 cmd.append(bundle_dst)
1906 for f in remote.fetch:
1907 cmd.append(str(f))
1908 cmd.append('refs/tags/*:refs/tags/*')
1909
1910 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001911 if os.path.exists(bundle_dst):
1912 os.remove(bundle_dst)
1913 if os.path.exists(bundle_tmp):
1914 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001915 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001916
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001917 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001918 if os.path.exists(dstPath):
1919 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001920
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001921 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001922 if quiet:
1923 cmd += ['--silent']
1924 if os.path.exists(tmpPath):
1925 size = os.stat(tmpPath).st_size
1926 if size >= 1024:
1927 cmd += ['--continue-at', '%d' % (size,)]
1928 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001929 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001930 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1931 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001932 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001933 if cookiefile:
1934 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001935 if srcUrl.startswith('persistent-'):
1936 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001937 cmd += [srcUrl]
1938
1939 if IsTrace():
1940 Trace('%s', ' '.join(cmd))
1941 try:
1942 proc = subprocess.Popen(cmd)
1943 except OSError:
1944 return False
1945
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001946 curlret = proc.wait()
1947
1948 if curlret == 22:
1949 # From curl man page:
1950 # 22: HTTP page not retrieved. The requested url was not found or
1951 # returned another error with the HTTP error code being 400 or above.
1952 # This return code only appears if -f, --fail is used.
1953 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001954 print("Server does not provide clone.bundle; ignoring.",
1955 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001956 return False
1957
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001958 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001959 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001960 os.rename(tmpPath, dstPath)
1961 return True
1962 else:
1963 os.remove(tmpPath)
1964 return False
1965 else:
1966 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001967
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001968 def _IsValidBundle(self, path):
1969 try:
1970 with open(path) as f:
1971 if f.read(16) == '# v2 git bundle\n':
1972 return True
1973 else:
1974 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1975 return False
1976 except OSError:
1977 return False
1978
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001979 def _GetBundleCookieFile(self, url):
1980 if url.startswith('persistent-'):
1981 try:
1982 p = subprocess.Popen(
1983 ['git-remote-persistent-https', '-print_config', url],
1984 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1985 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001986 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001987 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001988 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001989 for line in p.stdout:
1990 line = line.strip()
1991 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001992 cookiefile = line[len(prefix):]
1993 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001994 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001995 err_msg = p.stderr.read()
1996 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001997 pass # Persistent proxy doesn't support -print_config.
1998 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001999 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07002000 if cookiefile:
2001 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002002 except OSError as e:
2003 if e.errno == errno.ENOENT:
2004 pass # No persistent proxy.
2005 raise
2006 return GitConfig.ForUser().GetString('http.cookiefile')
2007
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002008 def _Checkout(self, rev, quiet=False):
2009 cmd = ['checkout']
2010 if quiet:
2011 cmd.append('-q')
2012 cmd.append(rev)
2013 cmd.append('--')
2014 if GitCommand(self, cmd).Wait() != 0:
2015 if self._allrefs:
2016 raise GitError('%s checkout %s ' % (self.name, rev))
2017
Pierre Tardye5a21222011-03-24 16:28:18 +01002018 def _CherryPick(self, rev, quiet=False):
2019 cmd = ['cherry-pick']
2020 cmd.append(rev)
2021 cmd.append('--')
2022 if GitCommand(self, cmd).Wait() != 0:
2023 if self._allrefs:
2024 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2025
Erwan Mahea94f1622011-08-19 13:56:09 +02002026 def _Revert(self, rev, quiet=False):
2027 cmd = ['revert']
2028 cmd.append('--no-edit')
2029 cmd.append(rev)
2030 cmd.append('--')
2031 if GitCommand(self, cmd).Wait() != 0:
2032 if self._allrefs:
2033 raise GitError('%s revert %s ' % (self.name, rev))
2034
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002035 def _ResetHard(self, rev, quiet=True):
2036 cmd = ['reset', '--hard']
2037 if quiet:
2038 cmd.append('-q')
2039 cmd.append(rev)
2040 if GitCommand(self, cmd).Wait() != 0:
2041 raise GitError('%s reset --hard %s ' % (self.name, rev))
2042
2043 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002044 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002045 if onto is not None:
2046 cmd.extend(['--onto', onto])
2047 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002048 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002049 raise GitError('%s rebase %s ' % (self.name, upstream))
2050
Pierre Tardy3d125942012-05-04 12:18:12 +02002051 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002052 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002053 if ffonly:
2054 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002055 if GitCommand(self, cmd).Wait() != 0:
2056 raise GitError('%s merge %s ' % (self.name, head))
2057
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002058 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002059 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002060
2061 # Initialize the bare repository, which contains all of the objects.
2062 if not os.path.exists(self.objdir):
2063 os.makedirs(self.objdir)
2064 self.bare_objdir.init()
2065
2066 # If we have a separate directory to hold refs, initialize it as well.
2067 if self.objdir != self.gitdir:
2068 os.makedirs(self.gitdir)
2069 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2070 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002071
Shawn O. Pearce88443382010-10-08 10:02:09 +02002072 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002073 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002074
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002075 if ref_dir or mirror_git:
2076 if not mirror_git:
2077 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002078 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2079 self.relpath + '.git')
2080
2081 if os.path.exists(mirror_git):
2082 ref_dir = mirror_git
2083
2084 elif os.path.exists(repo_git):
2085 ref_dir = repo_git
2086
2087 else:
2088 ref_dir = None
2089
2090 if ref_dir:
2091 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2092 os.path.join(ref_dir, 'objects') + '\n')
2093
Jimmie Westera0444582012-10-24 13:44:42 +02002094 self._UpdateHooks()
2095
2096 m = self.manifest.manifestProject.config
2097 for key in ['user.name', 'user.email']:
2098 if m.Has(key, include_defaults = False):
2099 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002100 if self.manifest.IsMirror:
2101 self.config.SetString('core.bare', 'true')
2102 else:
2103 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002104
Jimmie Westera0444582012-10-24 13:44:42 +02002105 def _UpdateHooks(self):
2106 if os.path.exists(self.gitdir):
2107 # Always recreate hooks since they can have been changed
2108 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002109 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002110 try:
2111 to_rm = os.listdir(hooks)
2112 except OSError:
2113 to_rm = []
2114 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002115 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002116 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002117
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002118 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002119 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002120 if not os.path.exists(hooks):
2121 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002122 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002123 name = os.path.basename(stock_hook)
2124
Victor Boivie65e0f352011-04-18 11:23:29 +02002125 if name in ('commit-msg',) and not self.remote.review \
2126 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002127 # Don't install a Gerrit Code Review hook if this
2128 # project does not appear to use it for reviews.
2129 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002130 # Since the manifest project is one of those, but also
2131 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002132 continue
2133
2134 dst = os.path.join(hooks, name)
2135 if os.path.islink(dst):
2136 continue
2137 if os.path.exists(dst):
2138 if filecmp.cmp(stock_hook, dst, shallow=False):
2139 os.remove(dst)
2140 else:
2141 _error("%s: Not replacing %s hook", self.relpath, name)
2142 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002143 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002144 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002145 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002146 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002147 raise GitError('filesystem must support symlinks')
2148 else:
2149 raise
2150
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002151 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002152 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002153 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002154 remote.url = self.remote.url
2155 remote.review = self.remote.review
2156 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002157
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002158 if self.worktree:
2159 remote.ResetFetch(mirror=False)
2160 else:
2161 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002162 remote.Save()
2163
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002164 def _InitMRef(self):
2165 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002166 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002167
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002168 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002169 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002170
2171 def _InitAnyMRef(self, ref):
2172 cur = self.bare_ref.symref(ref)
2173
2174 if self.revisionId:
2175 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2176 msg = 'manifest set to %s' % self.revisionId
2177 dst = self.revisionId + '^0'
2178 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2179 else:
2180 remote = self.GetRemote(self.remote.name)
2181 dst = remote.ToLocal(self.revisionExpr)
2182 if cur != dst:
2183 msg = 'manifest set to %s' % self.revisionExpr
2184 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002185
David James8d201162013-10-11 17:03:19 -07002186 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2187 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2188
2189 Args:
2190 gitdir: The bare git repository. Must already be initialized.
2191 dotgit: The repository you would like to initialize.
2192 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2193 Only one work tree can store refs under a given |gitdir|.
2194 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2195 This saves you the effort of initializing |dotgit| yourself.
2196 """
2197 # These objects can be shared between several working trees.
2198 symlink_files = ['description', 'info']
2199 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2200 if share_refs:
2201 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002202 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002203 symlink_dirs += ['logs', 'refs']
2204 to_symlink = symlink_files + symlink_dirs
2205
2206 to_copy = []
2207 if copy_all:
2208 to_copy = os.listdir(gitdir)
2209
2210 for name in set(to_copy).union(to_symlink):
2211 try:
2212 src = os.path.realpath(os.path.join(gitdir, name))
2213 dst = os.path.realpath(os.path.join(dotgit, name))
2214
2215 if os.path.lexists(dst) and not os.path.islink(dst):
2216 raise GitError('cannot overwrite a local work tree')
2217
2218 # If the source dir doesn't exist, create an empty dir.
2219 if name in symlink_dirs and not os.path.lexists(src):
2220 os.makedirs(src)
2221
Conley Owens80b87fe2014-05-09 17:13:44 -07002222 # If the source file doesn't exist, ensure the destination
2223 # file doesn't either.
2224 if name in symlink_files and not os.path.lexists(src):
2225 try:
2226 os.remove(dst)
2227 except OSError:
2228 pass
2229
David James8d201162013-10-11 17:03:19 -07002230 if name in to_symlink:
2231 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2232 elif copy_all and not os.path.islink(dst):
2233 if os.path.isdir(src):
2234 shutil.copytree(src, dst)
2235 elif os.path.isfile(src):
2236 shutil.copy(src, dst)
2237 except OSError as e:
2238 if e.errno == errno.EPERM:
2239 raise GitError('filesystem must support symlinks')
2240 else:
2241 raise
2242
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002243 def _InitWorkTree(self):
2244 dotgit = os.path.join(self.worktree, '.git')
2245 if not os.path.exists(dotgit):
2246 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002247 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2248 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002249
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002250 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002251
2252 cmd = ['read-tree', '--reset', '-u']
2253 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002254 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002255 if GitCommand(self, cmd).Wait() != 0:
2256 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002257
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002258 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002259
2260 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002261 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002262
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002263 def _revlist(self, *args, **kw):
2264 a = []
2265 a.extend(args)
2266 a.append('--')
2267 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002268
2269 @property
2270 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002271 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002272
Julien Camperguedd654222014-01-09 16:21:37 +01002273 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2274 """Get logs between two revisions of this project."""
2275 comp = '..'
2276 if rev1:
2277 revs = [rev1]
2278 if rev2:
2279 revs.extend([comp, rev2])
2280 cmd = ['log', ''.join(revs)]
2281 out = DiffColoring(self.config)
2282 if out.is_on and color:
2283 cmd.append('--color')
2284 if oneline:
2285 cmd.append('--oneline')
2286
2287 try:
2288 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2289 if log.Wait() == 0:
2290 return log.stdout
2291 except GitError:
2292 # worktree may not exist if groups changed for example. In that case,
2293 # try in gitdir instead.
2294 if not os.path.exists(self.worktree):
2295 return self.bare_git.log(*cmd[1:])
2296 else:
2297 raise
2298 return None
2299
2300 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2301 """Get the list of logs from this revision to given revisionId"""
2302 logs = {}
2303 selfId = self.GetRevisionId(self._allrefs)
2304 toId = toProject.GetRevisionId(toProject._allrefs)
2305
2306 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2307 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2308 return logs
2309
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002310 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002311 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002312 self._project = project
2313 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002314 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002315
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002316 def LsOthers(self):
2317 p = GitCommand(self._project,
2318 ['ls-files',
2319 '-z',
2320 '--others',
2321 '--exclude-standard'],
2322 bare = False,
David James8d201162013-10-11 17:03:19 -07002323 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002324 capture_stdout = True,
2325 capture_stderr = True)
2326 if p.Wait() == 0:
2327 out = p.stdout
2328 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002329 return out[:-1].split('\0') # pylint: disable=W1401
2330 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002331 return []
2332
2333 def DiffZ(self, name, *args):
2334 cmd = [name]
2335 cmd.append('-z')
2336 cmd.extend(args)
2337 p = GitCommand(self._project,
2338 cmd,
David James8d201162013-10-11 17:03:19 -07002339 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002340 bare = False,
2341 capture_stdout = True,
2342 capture_stderr = True)
2343 try:
2344 out = p.process.stdout.read()
2345 r = {}
2346 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002347 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002348 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002349 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002350 info = next(out)
2351 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002352 except StopIteration:
2353 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002354
2355 class _Info(object):
2356 def __init__(self, path, omode, nmode, oid, nid, state):
2357 self.path = path
2358 self.src_path = None
2359 self.old_mode = omode
2360 self.new_mode = nmode
2361 self.old_id = oid
2362 self.new_id = nid
2363
2364 if len(state) == 1:
2365 self.status = state
2366 self.level = None
2367 else:
2368 self.status = state[:1]
2369 self.level = state[1:]
2370 while self.level.startswith('0'):
2371 self.level = self.level[1:]
2372
2373 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002374 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002375 if info.status in ('R', 'C'):
2376 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002377 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002378 r[info.path] = info
2379 return r
2380 finally:
2381 p.Wait()
2382
2383 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002384 if self._bare:
2385 path = os.path.join(self._project.gitdir, HEAD)
2386 else:
2387 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002388 try:
2389 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002390 except IOError as e:
2391 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002392 try:
2393 line = fd.read()
2394 finally:
2395 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302396 try:
2397 line = line.decode()
2398 except AttributeError:
2399 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002400 if line.startswith('ref: '):
2401 return line[5:-1]
2402 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002403
2404 def SetHead(self, ref, message=None):
2405 cmdv = []
2406 if message is not None:
2407 cmdv.extend(['-m', message])
2408 cmdv.append(HEAD)
2409 cmdv.append(ref)
2410 self.symbolic_ref(*cmdv)
2411
2412 def DetachHead(self, new, message=None):
2413 cmdv = ['--no-deref']
2414 if message is not None:
2415 cmdv.extend(['-m', message])
2416 cmdv.append(HEAD)
2417 cmdv.append(new)
2418 self.update_ref(*cmdv)
2419
2420 def UpdateRef(self, name, new, old=None,
2421 message=None,
2422 detach=False):
2423 cmdv = []
2424 if message is not None:
2425 cmdv.extend(['-m', message])
2426 if detach:
2427 cmdv.append('--no-deref')
2428 cmdv.append(name)
2429 cmdv.append(new)
2430 if old is not None:
2431 cmdv.append(old)
2432 self.update_ref(*cmdv)
2433
2434 def DeleteRef(self, name, old=None):
2435 if not old:
2436 old = self.rev_parse(name)
2437 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002438 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002439
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002440 def rev_list(self, *args, **kw):
2441 if 'format' in kw:
2442 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2443 else:
2444 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002445 cmdv.extend(args)
2446 p = GitCommand(self._project,
2447 cmdv,
2448 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002449 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002450 capture_stdout = True,
2451 capture_stderr = True)
2452 r = []
2453 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002454 if line[-1] == '\n':
2455 line = line[:-1]
2456 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002457 if p.Wait() != 0:
2458 raise GitError('%s rev-list %s: %s' % (
2459 self._project.name,
2460 str(args),
2461 p.stderr))
2462 return r
2463
2464 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002465 """Allow arbitrary git commands using pythonic syntax.
2466
2467 This allows you to do things like:
2468 git_obj.rev_parse('HEAD')
2469
2470 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2471 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002472 Any other positional arguments will be passed to the git command, and the
2473 following keyword arguments are supported:
2474 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002475
2476 Args:
2477 name: The name of the git command to call. Any '_' characters will
2478 be replaced with '-'.
2479
2480 Returns:
2481 A callable object that will try to call git with the named command.
2482 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002483 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002484 def runner(*args, **kwargs):
2485 cmdv = []
2486 config = kwargs.pop('config', None)
2487 for k in kwargs:
2488 raise TypeError('%s() got an unexpected keyword argument %r'
2489 % (name, k))
2490 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002491 if not git_require((1, 7, 2)):
2492 raise ValueError('cannot set config on command line for %s()'
2493 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302494 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002495 cmdv.append('-c')
2496 cmdv.append('%s=%s' % (k, v))
2497 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002498 cmdv.extend(args)
2499 p = GitCommand(self._project,
2500 cmdv,
2501 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002502 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002503 capture_stdout = True,
2504 capture_stderr = True)
2505 if p.Wait() != 0:
2506 raise GitError('%s %s: %s' % (
2507 self._project.name,
2508 name,
2509 p.stderr))
2510 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302511 try:
Conley Owensedd01512013-09-26 12:59:58 -07002512 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302513 except AttributeError:
2514 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002515 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2516 return r[:-1]
2517 return r
2518 return runner
2519
2520
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002521class _PriorSyncFailedError(Exception):
2522 def __str__(self):
2523 return 'prior sync failed; rebase still in progress'
2524
2525class _DirtyError(Exception):
2526 def __str__(self):
2527 return 'contains uncommitted changes'
2528
2529class _InfoMessage(object):
2530 def __init__(self, project, text):
2531 self.project = project
2532 self.text = text
2533
2534 def Print(self, syncbuf):
2535 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2536 syncbuf.out.nl()
2537
2538class _Failure(object):
2539 def __init__(self, project, why):
2540 self.project = project
2541 self.why = why
2542
2543 def Print(self, syncbuf):
2544 syncbuf.out.fail('error: %s/: %s',
2545 self.project.relpath,
2546 str(self.why))
2547 syncbuf.out.nl()
2548
2549class _Later(object):
2550 def __init__(self, project, action):
2551 self.project = project
2552 self.action = action
2553
2554 def Run(self, syncbuf):
2555 out = syncbuf.out
2556 out.project('project %s/', self.project.relpath)
2557 out.nl()
2558 try:
2559 self.action()
2560 out.nl()
2561 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002562 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002563 out.nl()
2564 return False
2565
2566class _SyncColoring(Coloring):
2567 def __init__(self, config):
2568 Coloring.__init__(self, config, 'reposync')
2569 self.project = self.printer('header', attr = 'bold')
2570 self.info = self.printer('info')
2571 self.fail = self.printer('fail', fg='red')
2572
2573class SyncBuffer(object):
2574 def __init__(self, config, detach_head=False):
2575 self._messages = []
2576 self._failures = []
2577 self._later_queue1 = []
2578 self._later_queue2 = []
2579
2580 self.out = _SyncColoring(config)
2581 self.out.redirect(sys.stderr)
2582
2583 self.detach_head = detach_head
2584 self.clean = True
2585
2586 def info(self, project, fmt, *args):
2587 self._messages.append(_InfoMessage(project, fmt % args))
2588
2589 def fail(self, project, err=None):
2590 self._failures.append(_Failure(project, err))
2591 self.clean = False
2592
2593 def later1(self, project, what):
2594 self._later_queue1.append(_Later(project, what))
2595
2596 def later2(self, project, what):
2597 self._later_queue2.append(_Later(project, what))
2598
2599 def Finish(self):
2600 self._PrintMessages()
2601 self._RunLater()
2602 self._PrintMessages()
2603 return self.clean
2604
2605 def _RunLater(self):
2606 for q in ['_later_queue1', '_later_queue2']:
2607 if not self._RunQueue(q):
2608 return
2609
2610 def _RunQueue(self, queue):
2611 for m in getattr(self, queue):
2612 if not m.Run(self):
2613 self.clean = False
2614 return False
2615 setattr(self, queue, [])
2616 return True
2617
2618 def _PrintMessages(self):
2619 for m in self._messages:
2620 m.Print(self)
2621 for m in self._failures:
2622 m.Print(self)
2623
2624 self._messages = []
2625 self._failures = []
2626
2627
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002628class MetaProject(Project):
2629 """A special project housed under .repo.
2630 """
2631 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002632 Project.__init__(self,
2633 manifest = manifest,
2634 name = name,
2635 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002636 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002637 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002638 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002639 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002640 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002641 revisionId = None,
2642 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002643
2644 def PreSync(self):
2645 if self.Exists:
2646 cb = self.CurrentBranch
2647 if cb:
2648 base = self.GetBranch(cb).merge
2649 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002650 self.revisionExpr = base
2651 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002652
Florian Vallee5d016502012-06-07 17:19:26 +02002653 def MetaBranchSwitch(self, target):
2654 """ Prepare MetaProject for manifest branch switch
2655 """
2656
2657 # detach and delete manifest branch, allowing a new
2658 # branch to take over
2659 syncbuf = SyncBuffer(self.config, detach_head = True)
2660 self.Sync_LocalHalf(syncbuf)
2661 syncbuf.Finish()
2662
2663 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002664 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002665 capture_stdout = True,
2666 capture_stderr = True).Wait() == 0
2667
2668
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002669 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002670 def LastFetch(self):
2671 try:
2672 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2673 return os.path.getmtime(fh)
2674 except OSError:
2675 return 0
2676
2677 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002678 def HasChanges(self):
2679 """Has the remote received new commits not yet checked out?
2680 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002681 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002682 return False
2683
David Pursehouse8a68ff92012-09-24 12:15:13 +09002684 all_refs = self.bare_ref.all
2685 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002686 head = self.work_git.GetHead()
2687 if head.startswith(R_HEADS):
2688 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002689 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002690 except KeyError:
2691 head = None
2692
2693 if revid == head:
2694 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002695 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002696 return True
2697 return False