blob: e0703519cb1e16ea8e11dcb2533822117524a3eb [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
49 fd = open(lock, 'wb')
50 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
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001709 def _RemoteFetch(self, name=None,
1710 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001711 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001712 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001713 alt_dir=None,
1714 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001715
1716 is_sha1 = False
1717 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001718 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001719
David Pursehouse9bc422f2014-04-15 10:28:56 +09001720 # The depth should not be used when fetching to a mirror because
1721 # it will result in a shallow repository that cannot be cloned or
1722 # fetched from.
1723 if not self.manifest.IsMirror:
1724 if self.clone_depth:
1725 depth = self.clone_depth
1726 else:
1727 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1728
Shawn Pearce69e04d82014-01-29 12:48:54 -08001729 if depth:
1730 current_branch_only = True
1731
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001732 if current_branch_only:
1733 if ID_RE.match(self.revisionExpr) is not None:
1734 is_sha1 = True
1735 elif self.revisionExpr.startswith(R_TAGS):
1736 # this is a tag and its sha1 value should never change
1737 tag_name = self.revisionExpr[len(R_TAGS):]
1738
1739 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001740 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001741 return True
Brian Harring14a66742012-09-28 20:21:57 -07001742 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1743 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001744
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001745 if not name:
1746 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001747
1748 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001749 remote = self.GetRemote(name)
1750 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001751 ssh_proxy = True
1752
Shawn O. Pearce88443382010-10-08 10:02:09 +02001753 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001754 if alt_dir and 'objects' == os.path.basename(alt_dir):
1755 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001756 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1757 remote = self.GetRemote(name)
1758
David Pursehouse8a68ff92012-09-24 12:15:13 +09001759 all_refs = self.bare_ref.all
1760 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001761 tmp = set()
1762
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301763 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001764 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001765 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001766 all_refs[r] = ref_id
1767 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001768 continue
1769
David Pursehouse8a68ff92012-09-24 12:15:13 +09001770 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001771 continue
1772
David Pursehouse8a68ff92012-09-24 12:15:13 +09001773 r = 'refs/_alt/%s' % ref_id
1774 all_refs[r] = ref_id
1775 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001776 tmp.add(r)
1777
Shawn O. Pearce88443382010-10-08 10:02:09 +02001778 tmp_packed = ''
1779 old_packed = ''
1780
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301781 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001782 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001783 tmp_packed += line
1784 if r not in tmp:
1785 old_packed += line
1786
1787 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001788 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001789 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001790
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001791 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001792
1793 # The --depth option only affects the initial fetch; after that we'll do
1794 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001795 if depth and initial:
1796 cmd.append('--depth=%s' % depth)
1797
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001798 if quiet:
1799 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001800 if not self.worktree:
1801 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001802 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001803
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001804 # If using depth then we should not get all the tags since they may
1805 # be outside of the depth.
1806 if no_tags or depth:
1807 cmd.append('--no-tags')
1808 else:
1809 cmd.append('--tags')
1810
Brian Harring14a66742012-09-28 20:21:57 -07001811 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001812 # Fetch whole repo
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301813 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001814 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001815 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001816 cmd.append(tag_name)
1817 else:
1818 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001819 if is_sha1:
1820 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001821 if branch.startswith(R_HEADS):
1822 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301823 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001824
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001825 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001826 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001827 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1828 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001829 ok = True
1830 break
Brian Harring14a66742012-09-28 20:21:57 -07001831 elif current_branch_only and is_sha1 and ret == 128:
1832 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1833 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1834 # abort the optimization attempt and do a full sync.
1835 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001836 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001837
1838 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001839 # Ensure that some refs exist. Otherwise, we probably aren't looking
1840 # at a real git repository and may have a bad url.
1841 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001842 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001843
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001844 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001845 if old_packed != '':
1846 _lwrite(packed_refs, old_packed)
1847 else:
1848 os.remove(packed_refs)
1849 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001850
1851 if is_sha1 and current_branch_only and self.upstream:
1852 # We just synced the upstream given branch; verify we
1853 # got what we wanted, else trigger a second run of all
1854 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001855 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001856 return self._RemoteFetch(name=name, current_branch_only=False,
1857 initial=False, quiet=quiet, alt_dir=alt_dir)
1858
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001859 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001860
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001861 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001862 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001863 return False
1864
1865 remote = self.GetRemote(self.remote.name)
1866 bundle_url = remote.url + '/clone.bundle'
1867 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001868 if GetSchemeFromUrl(bundle_url) not in (
1869 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001870 return False
1871
1872 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1873 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1874
1875 exist_dst = os.path.exists(bundle_dst)
1876 exist_tmp = os.path.exists(bundle_tmp)
1877
1878 if not initial and not exist_dst and not exist_tmp:
1879 return False
1880
1881 if not exist_dst:
1882 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1883 if not exist_dst:
1884 return False
1885
1886 cmd = ['fetch']
1887 if quiet:
1888 cmd.append('--quiet')
1889 if not self.worktree:
1890 cmd.append('--update-head-ok')
1891 cmd.append(bundle_dst)
1892 for f in remote.fetch:
1893 cmd.append(str(f))
1894 cmd.append('refs/tags/*:refs/tags/*')
1895
1896 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001897 if os.path.exists(bundle_dst):
1898 os.remove(bundle_dst)
1899 if os.path.exists(bundle_tmp):
1900 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001901 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001902
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001903 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001904 if os.path.exists(dstPath):
1905 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001906
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001907 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001908 if quiet:
1909 cmd += ['--silent']
1910 if os.path.exists(tmpPath):
1911 size = os.stat(tmpPath).st_size
1912 if size >= 1024:
1913 cmd += ['--continue-at', '%d' % (size,)]
1914 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001915 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001916 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1917 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001918 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001919 if cookiefile:
1920 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001921 if srcUrl.startswith('persistent-'):
1922 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001923 cmd += [srcUrl]
1924
1925 if IsTrace():
1926 Trace('%s', ' '.join(cmd))
1927 try:
1928 proc = subprocess.Popen(cmd)
1929 except OSError:
1930 return False
1931
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001932 curlret = proc.wait()
1933
1934 if curlret == 22:
1935 # From curl man page:
1936 # 22: HTTP page not retrieved. The requested url was not found or
1937 # returned another error with the HTTP error code being 400 or above.
1938 # This return code only appears if -f, --fail is used.
1939 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001940 print("Server does not provide clone.bundle; ignoring.",
1941 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001942 return False
1943
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001944 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001945 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001946 os.rename(tmpPath, dstPath)
1947 return True
1948 else:
1949 os.remove(tmpPath)
1950 return False
1951 else:
1952 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001953
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001954 def _IsValidBundle(self, path):
1955 try:
1956 with open(path) as f:
1957 if f.read(16) == '# v2 git bundle\n':
1958 return True
1959 else:
1960 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1961 return False
1962 except OSError:
1963 return False
1964
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001965 def _GetBundleCookieFile(self, url):
1966 if url.startswith('persistent-'):
1967 try:
1968 p = subprocess.Popen(
1969 ['git-remote-persistent-https', '-print_config', url],
1970 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1971 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001972 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001973 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001974 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001975 for line in p.stdout:
1976 line = line.strip()
1977 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001978 cookiefile = line[len(prefix):]
1979 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001980 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001981 err_msg = p.stderr.read()
1982 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001983 pass # Persistent proxy doesn't support -print_config.
1984 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001985 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001986 if cookiefile:
1987 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001988 except OSError as e:
1989 if e.errno == errno.ENOENT:
1990 pass # No persistent proxy.
1991 raise
1992 return GitConfig.ForUser().GetString('http.cookiefile')
1993
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001994 def _Checkout(self, rev, quiet=False):
1995 cmd = ['checkout']
1996 if quiet:
1997 cmd.append('-q')
1998 cmd.append(rev)
1999 cmd.append('--')
2000 if GitCommand(self, cmd).Wait() != 0:
2001 if self._allrefs:
2002 raise GitError('%s checkout %s ' % (self.name, rev))
2003
Pierre Tardye5a21222011-03-24 16:28:18 +01002004 def _CherryPick(self, rev, quiet=False):
2005 cmd = ['cherry-pick']
2006 cmd.append(rev)
2007 cmd.append('--')
2008 if GitCommand(self, cmd).Wait() != 0:
2009 if self._allrefs:
2010 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2011
Erwan Mahea94f1622011-08-19 13:56:09 +02002012 def _Revert(self, rev, quiet=False):
2013 cmd = ['revert']
2014 cmd.append('--no-edit')
2015 cmd.append(rev)
2016 cmd.append('--')
2017 if GitCommand(self, cmd).Wait() != 0:
2018 if self._allrefs:
2019 raise GitError('%s revert %s ' % (self.name, rev))
2020
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002021 def _ResetHard(self, rev, quiet=True):
2022 cmd = ['reset', '--hard']
2023 if quiet:
2024 cmd.append('-q')
2025 cmd.append(rev)
2026 if GitCommand(self, cmd).Wait() != 0:
2027 raise GitError('%s reset --hard %s ' % (self.name, rev))
2028
2029 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002030 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002031 if onto is not None:
2032 cmd.extend(['--onto', onto])
2033 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002034 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002035 raise GitError('%s rebase %s ' % (self.name, upstream))
2036
Pierre Tardy3d125942012-05-04 12:18:12 +02002037 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002038 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002039 if ffonly:
2040 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002041 if GitCommand(self, cmd).Wait() != 0:
2042 raise GitError('%s merge %s ' % (self.name, head))
2043
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002044 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002045 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002046
2047 # Initialize the bare repository, which contains all of the objects.
2048 if not os.path.exists(self.objdir):
2049 os.makedirs(self.objdir)
2050 self.bare_objdir.init()
2051
2052 # If we have a separate directory to hold refs, initialize it as well.
2053 if self.objdir != self.gitdir:
2054 os.makedirs(self.gitdir)
2055 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2056 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002057
Shawn O. Pearce88443382010-10-08 10:02:09 +02002058 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002059 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002060
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002061 if ref_dir or mirror_git:
2062 if not mirror_git:
2063 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002064 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2065 self.relpath + '.git')
2066
2067 if os.path.exists(mirror_git):
2068 ref_dir = mirror_git
2069
2070 elif os.path.exists(repo_git):
2071 ref_dir = repo_git
2072
2073 else:
2074 ref_dir = None
2075
2076 if ref_dir:
2077 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2078 os.path.join(ref_dir, 'objects') + '\n')
2079
Jimmie Westera0444582012-10-24 13:44:42 +02002080 self._UpdateHooks()
2081
2082 m = self.manifest.manifestProject.config
2083 for key in ['user.name', 'user.email']:
2084 if m.Has(key, include_defaults = False):
2085 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002086 if self.manifest.IsMirror:
2087 self.config.SetString('core.bare', 'true')
2088 else:
2089 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002090
Jimmie Westera0444582012-10-24 13:44:42 +02002091 def _UpdateHooks(self):
2092 if os.path.exists(self.gitdir):
2093 # Always recreate hooks since they can have been changed
2094 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002095 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002096 try:
2097 to_rm = os.listdir(hooks)
2098 except OSError:
2099 to_rm = []
2100 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002101 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002102 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002103
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002104 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002105 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002106 if not os.path.exists(hooks):
2107 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002108 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002109 name = os.path.basename(stock_hook)
2110
Victor Boivie65e0f352011-04-18 11:23:29 +02002111 if name in ('commit-msg',) and not self.remote.review \
2112 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002113 # Don't install a Gerrit Code Review hook if this
2114 # project does not appear to use it for reviews.
2115 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002116 # Since the manifest project is one of those, but also
2117 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002118 continue
2119
2120 dst = os.path.join(hooks, name)
2121 if os.path.islink(dst):
2122 continue
2123 if os.path.exists(dst):
2124 if filecmp.cmp(stock_hook, dst, shallow=False):
2125 os.remove(dst)
2126 else:
2127 _error("%s: Not replacing %s hook", self.relpath, name)
2128 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002129 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002130 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002131 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002132 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002133 raise GitError('filesystem must support symlinks')
2134 else:
2135 raise
2136
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002137 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002138 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002139 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002140 remote.url = self.remote.url
2141 remote.review = self.remote.review
2142 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002143
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002144 if self.worktree:
2145 remote.ResetFetch(mirror=False)
2146 else:
2147 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002148 remote.Save()
2149
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002150 def _InitMRef(self):
2151 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002152 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002153
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002154 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002155 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002156
2157 def _InitAnyMRef(self, ref):
2158 cur = self.bare_ref.symref(ref)
2159
2160 if self.revisionId:
2161 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2162 msg = 'manifest set to %s' % self.revisionId
2163 dst = self.revisionId + '^0'
2164 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2165 else:
2166 remote = self.GetRemote(self.remote.name)
2167 dst = remote.ToLocal(self.revisionExpr)
2168 if cur != dst:
2169 msg = 'manifest set to %s' % self.revisionExpr
2170 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002171
David James8d201162013-10-11 17:03:19 -07002172 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2173 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2174
2175 Args:
2176 gitdir: The bare git repository. Must already be initialized.
2177 dotgit: The repository you would like to initialize.
2178 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2179 Only one work tree can store refs under a given |gitdir|.
2180 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2181 This saves you the effort of initializing |dotgit| yourself.
2182 """
2183 # These objects can be shared between several working trees.
2184 symlink_files = ['description', 'info']
2185 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2186 if share_refs:
2187 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002188 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002189 symlink_dirs += ['logs', 'refs']
2190 to_symlink = symlink_files + symlink_dirs
2191
2192 to_copy = []
2193 if copy_all:
2194 to_copy = os.listdir(gitdir)
2195
2196 for name in set(to_copy).union(to_symlink):
2197 try:
2198 src = os.path.realpath(os.path.join(gitdir, name))
2199 dst = os.path.realpath(os.path.join(dotgit, name))
2200
2201 if os.path.lexists(dst) and not os.path.islink(dst):
2202 raise GitError('cannot overwrite a local work tree')
2203
2204 # If the source dir doesn't exist, create an empty dir.
2205 if name in symlink_dirs and not os.path.lexists(src):
2206 os.makedirs(src)
2207
2208 if name in to_symlink:
2209 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2210 elif copy_all and not os.path.islink(dst):
2211 if os.path.isdir(src):
2212 shutil.copytree(src, dst)
2213 elif os.path.isfile(src):
2214 shutil.copy(src, dst)
2215 except OSError as e:
2216 if e.errno == errno.EPERM:
2217 raise GitError('filesystem must support symlinks')
2218 else:
2219 raise
2220
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002221 def _InitWorkTree(self):
2222 dotgit = os.path.join(self.worktree, '.git')
2223 if not os.path.exists(dotgit):
2224 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002225 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2226 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002227
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002228 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002229
2230 cmd = ['read-tree', '--reset', '-u']
2231 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002232 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002233 if GitCommand(self, cmd).Wait() != 0:
2234 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002235
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002236 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002237
2238 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002239 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002240
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002241 def _revlist(self, *args, **kw):
2242 a = []
2243 a.extend(args)
2244 a.append('--')
2245 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002246
2247 @property
2248 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002249 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002250
Julien Camperguedd654222014-01-09 16:21:37 +01002251 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2252 """Get logs between two revisions of this project."""
2253 comp = '..'
2254 if rev1:
2255 revs = [rev1]
2256 if rev2:
2257 revs.extend([comp, rev2])
2258 cmd = ['log', ''.join(revs)]
2259 out = DiffColoring(self.config)
2260 if out.is_on and color:
2261 cmd.append('--color')
2262 if oneline:
2263 cmd.append('--oneline')
2264
2265 try:
2266 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2267 if log.Wait() == 0:
2268 return log.stdout
2269 except GitError:
2270 # worktree may not exist if groups changed for example. In that case,
2271 # try in gitdir instead.
2272 if not os.path.exists(self.worktree):
2273 return self.bare_git.log(*cmd[1:])
2274 else:
2275 raise
2276 return None
2277
2278 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2279 """Get the list of logs from this revision to given revisionId"""
2280 logs = {}
2281 selfId = self.GetRevisionId(self._allrefs)
2282 toId = toProject.GetRevisionId(toProject._allrefs)
2283
2284 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2285 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2286 return logs
2287
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002289 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002290 self._project = project
2291 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002292 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002293
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002294 def LsOthers(self):
2295 p = GitCommand(self._project,
2296 ['ls-files',
2297 '-z',
2298 '--others',
2299 '--exclude-standard'],
2300 bare = False,
David James8d201162013-10-11 17:03:19 -07002301 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002302 capture_stdout = True,
2303 capture_stderr = True)
2304 if p.Wait() == 0:
2305 out = p.stdout
2306 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002307 return out[:-1].split('\0') # pylint: disable=W1401
2308 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002309 return []
2310
2311 def DiffZ(self, name, *args):
2312 cmd = [name]
2313 cmd.append('-z')
2314 cmd.extend(args)
2315 p = GitCommand(self._project,
2316 cmd,
David James8d201162013-10-11 17:03:19 -07002317 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002318 bare = False,
2319 capture_stdout = True,
2320 capture_stderr = True)
2321 try:
2322 out = p.process.stdout.read()
2323 r = {}
2324 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002325 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002326 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002327 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002328 info = next(out)
2329 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002330 except StopIteration:
2331 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002332
2333 class _Info(object):
2334 def __init__(self, path, omode, nmode, oid, nid, state):
2335 self.path = path
2336 self.src_path = None
2337 self.old_mode = omode
2338 self.new_mode = nmode
2339 self.old_id = oid
2340 self.new_id = nid
2341
2342 if len(state) == 1:
2343 self.status = state
2344 self.level = None
2345 else:
2346 self.status = state[:1]
2347 self.level = state[1:]
2348 while self.level.startswith('0'):
2349 self.level = self.level[1:]
2350
2351 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002352 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002353 if info.status in ('R', 'C'):
2354 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002355 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002356 r[info.path] = info
2357 return r
2358 finally:
2359 p.Wait()
2360
2361 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002362 if self._bare:
2363 path = os.path.join(self._project.gitdir, HEAD)
2364 else:
2365 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002366 try:
2367 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002368 except IOError as e:
2369 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002370 try:
2371 line = fd.read()
2372 finally:
2373 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302374 try:
2375 line = line.decode()
2376 except AttributeError:
2377 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002378 if line.startswith('ref: '):
2379 return line[5:-1]
2380 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002381
2382 def SetHead(self, ref, message=None):
2383 cmdv = []
2384 if message is not None:
2385 cmdv.extend(['-m', message])
2386 cmdv.append(HEAD)
2387 cmdv.append(ref)
2388 self.symbolic_ref(*cmdv)
2389
2390 def DetachHead(self, new, message=None):
2391 cmdv = ['--no-deref']
2392 if message is not None:
2393 cmdv.extend(['-m', message])
2394 cmdv.append(HEAD)
2395 cmdv.append(new)
2396 self.update_ref(*cmdv)
2397
2398 def UpdateRef(self, name, new, old=None,
2399 message=None,
2400 detach=False):
2401 cmdv = []
2402 if message is not None:
2403 cmdv.extend(['-m', message])
2404 if detach:
2405 cmdv.append('--no-deref')
2406 cmdv.append(name)
2407 cmdv.append(new)
2408 if old is not None:
2409 cmdv.append(old)
2410 self.update_ref(*cmdv)
2411
2412 def DeleteRef(self, name, old=None):
2413 if not old:
2414 old = self.rev_parse(name)
2415 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002416 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002417
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002418 def rev_list(self, *args, **kw):
2419 if 'format' in kw:
2420 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2421 else:
2422 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002423 cmdv.extend(args)
2424 p = GitCommand(self._project,
2425 cmdv,
2426 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002427 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002428 capture_stdout = True,
2429 capture_stderr = True)
2430 r = []
2431 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002432 if line[-1] == '\n':
2433 line = line[:-1]
2434 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002435 if p.Wait() != 0:
2436 raise GitError('%s rev-list %s: %s' % (
2437 self._project.name,
2438 str(args),
2439 p.stderr))
2440 return r
2441
2442 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002443 """Allow arbitrary git commands using pythonic syntax.
2444
2445 This allows you to do things like:
2446 git_obj.rev_parse('HEAD')
2447
2448 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2449 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002450 Any other positional arguments will be passed to the git command, and the
2451 following keyword arguments are supported:
2452 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002453
2454 Args:
2455 name: The name of the git command to call. Any '_' characters will
2456 be replaced with '-'.
2457
2458 Returns:
2459 A callable object that will try to call git with the named command.
2460 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002461 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002462 def runner(*args, **kwargs):
2463 cmdv = []
2464 config = kwargs.pop('config', None)
2465 for k in kwargs:
2466 raise TypeError('%s() got an unexpected keyword argument %r'
2467 % (name, k))
2468 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002469 if not git_require((1, 7, 2)):
2470 raise ValueError('cannot set config on command line for %s()'
2471 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302472 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002473 cmdv.append('-c')
2474 cmdv.append('%s=%s' % (k, v))
2475 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002476 cmdv.extend(args)
2477 p = GitCommand(self._project,
2478 cmdv,
2479 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002480 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002481 capture_stdout = True,
2482 capture_stderr = True)
2483 if p.Wait() != 0:
2484 raise GitError('%s %s: %s' % (
2485 self._project.name,
2486 name,
2487 p.stderr))
2488 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302489 try:
Conley Owensedd01512013-09-26 12:59:58 -07002490 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302491 except AttributeError:
2492 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002493 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2494 return r[:-1]
2495 return r
2496 return runner
2497
2498
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002499class _PriorSyncFailedError(Exception):
2500 def __str__(self):
2501 return 'prior sync failed; rebase still in progress'
2502
2503class _DirtyError(Exception):
2504 def __str__(self):
2505 return 'contains uncommitted changes'
2506
2507class _InfoMessage(object):
2508 def __init__(self, project, text):
2509 self.project = project
2510 self.text = text
2511
2512 def Print(self, syncbuf):
2513 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2514 syncbuf.out.nl()
2515
2516class _Failure(object):
2517 def __init__(self, project, why):
2518 self.project = project
2519 self.why = why
2520
2521 def Print(self, syncbuf):
2522 syncbuf.out.fail('error: %s/: %s',
2523 self.project.relpath,
2524 str(self.why))
2525 syncbuf.out.nl()
2526
2527class _Later(object):
2528 def __init__(self, project, action):
2529 self.project = project
2530 self.action = action
2531
2532 def Run(self, syncbuf):
2533 out = syncbuf.out
2534 out.project('project %s/', self.project.relpath)
2535 out.nl()
2536 try:
2537 self.action()
2538 out.nl()
2539 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002540 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002541 out.nl()
2542 return False
2543
2544class _SyncColoring(Coloring):
2545 def __init__(self, config):
2546 Coloring.__init__(self, config, 'reposync')
2547 self.project = self.printer('header', attr = 'bold')
2548 self.info = self.printer('info')
2549 self.fail = self.printer('fail', fg='red')
2550
2551class SyncBuffer(object):
2552 def __init__(self, config, detach_head=False):
2553 self._messages = []
2554 self._failures = []
2555 self._later_queue1 = []
2556 self._later_queue2 = []
2557
2558 self.out = _SyncColoring(config)
2559 self.out.redirect(sys.stderr)
2560
2561 self.detach_head = detach_head
2562 self.clean = True
2563
2564 def info(self, project, fmt, *args):
2565 self._messages.append(_InfoMessage(project, fmt % args))
2566
2567 def fail(self, project, err=None):
2568 self._failures.append(_Failure(project, err))
2569 self.clean = False
2570
2571 def later1(self, project, what):
2572 self._later_queue1.append(_Later(project, what))
2573
2574 def later2(self, project, what):
2575 self._later_queue2.append(_Later(project, what))
2576
2577 def Finish(self):
2578 self._PrintMessages()
2579 self._RunLater()
2580 self._PrintMessages()
2581 return self.clean
2582
2583 def _RunLater(self):
2584 for q in ['_later_queue1', '_later_queue2']:
2585 if not self._RunQueue(q):
2586 return
2587
2588 def _RunQueue(self, queue):
2589 for m in getattr(self, queue):
2590 if not m.Run(self):
2591 self.clean = False
2592 return False
2593 setattr(self, queue, [])
2594 return True
2595
2596 def _PrintMessages(self):
2597 for m in self._messages:
2598 m.Print(self)
2599 for m in self._failures:
2600 m.Print(self)
2601
2602 self._messages = []
2603 self._failures = []
2604
2605
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002606class MetaProject(Project):
2607 """A special project housed under .repo.
2608 """
2609 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002610 Project.__init__(self,
2611 manifest = manifest,
2612 name = name,
2613 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002614 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002615 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002616 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002617 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002618 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002619 revisionId = None,
2620 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002621
2622 def PreSync(self):
2623 if self.Exists:
2624 cb = self.CurrentBranch
2625 if cb:
2626 base = self.GetBranch(cb).merge
2627 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002628 self.revisionExpr = base
2629 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002630
Florian Vallee5d016502012-06-07 17:19:26 +02002631 def MetaBranchSwitch(self, target):
2632 """ Prepare MetaProject for manifest branch switch
2633 """
2634
2635 # detach and delete manifest branch, allowing a new
2636 # branch to take over
2637 syncbuf = SyncBuffer(self.config, detach_head = True)
2638 self.Sync_LocalHalf(syncbuf)
2639 syncbuf.Finish()
2640
2641 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002642 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002643 capture_stdout = True,
2644 capture_stderr = True).Wait() == 0
2645
2646
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002647 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002648 def LastFetch(self):
2649 try:
2650 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2651 return os.path.getmtime(fh)
2652 except OSError:
2653 return 0
2654
2655 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002656 def HasChanges(self):
2657 """Has the remote received new commits not yet checked out?
2658 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002659 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002660 return False
2661
David Pursehouse8a68ff92012-09-24 12:15:13 +09002662 all_refs = self.bare_ref.all
2663 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002664 head = self.work_git.GetHead()
2665 if head.startswith(R_HEADS):
2666 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002667 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002668 except KeyError:
2669 head = None
2670
2671 if revid == head:
2672 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002673 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002674 return True
2675 return False