blob: 0d491119cbefcab58e6a19e83f068218099cf9b1 [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:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080086 d = os.path.abspath(os.path.dirname(__file__))
87 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
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700234class RemoteSpec(object):
235 def __init__(self,
236 name,
237 url = None,
238 review = None):
239 self.name = name
240 self.url = url
241 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242
Doug Anderson37282b42011-03-04 11:54:18 -0800243class RepoHook(object):
244 """A RepoHook contains information about a script to run as a hook.
245
246 Hooks are used to run a python script before running an upload (for instance,
247 to run presubmit checks). Eventually, we may have hooks for other actions.
248
249 This shouldn't be confused with files in the 'repo/hooks' directory. Those
250 files are copied into each '.git/hooks' folder for each project. Repo-level
251 hooks are associated instead with repo actions.
252
253 Hooks are always python. When a hook is run, we will load the hook into the
254 interpreter and execute its main() function.
255 """
256 def __init__(self,
257 hook_type,
258 hooks_project,
259 topdir,
260 abort_if_user_denies=False):
261 """RepoHook constructor.
262
263 Params:
264 hook_type: A string representing the type of hook. This is also used
265 to figure out the name of the file containing the hook. For
266 example: 'pre-upload'.
267 hooks_project: The project containing the repo hooks. If you have a
268 manifest, this is manifest.repo_hooks_project. OK if this is None,
269 which will make the hook a no-op.
270 topdir: Repo's top directory (the one containing the .repo directory).
271 Scripts will run with CWD as this directory. If you have a manifest,
272 this is manifest.topdir
273 abort_if_user_denies: If True, we'll throw a HookError() if the user
274 doesn't allow us to run the hook.
275 """
276 self._hook_type = hook_type
277 self._hooks_project = hooks_project
278 self._topdir = topdir
279 self._abort_if_user_denies = abort_if_user_denies
280
281 # Store the full path to the script for convenience.
282 if self._hooks_project:
283 self._script_fullpath = os.path.join(self._hooks_project.worktree,
284 self._hook_type + '.py')
285 else:
286 self._script_fullpath = None
287
288 def _GetHash(self):
289 """Return a hash of the contents of the hooks directory.
290
291 We'll just use git to do this. This hash has the property that if anything
292 changes in the directory we will return a different has.
293
294 SECURITY CONSIDERATION:
295 This hash only represents the contents of files in the hook directory, not
296 any other files imported or called by hooks. Changes to imported files
297 can change the script behavior without affecting the hash.
298
299 Returns:
300 A string representing the hash. This will always be ASCII so that it can
301 be printed to the user easily.
302 """
303 assert self._hooks_project, "Must have hooks to calculate their hash."
304
305 # We will use the work_git object rather than just calling GetRevisionId().
306 # That gives us a hash of the latest checked in version of the files that
307 # the user will actually be executing. Specifically, GetRevisionId()
308 # doesn't appear to change even if a user checks out a different version
309 # of the hooks repo (via git checkout) nor if a user commits their own revs.
310 #
311 # NOTE: Local (non-committed) changes will not be factored into this hash.
312 # I think this is OK, since we're really only worried about warning the user
313 # about upstream changes.
314 return self._hooks_project.work_git.rev_parse('HEAD')
315
316 def _GetMustVerb(self):
317 """Return 'must' if the hook is required; 'should' if not."""
318 if self._abort_if_user_denies:
319 return 'must'
320 else:
321 return 'should'
322
323 def _CheckForHookApproval(self):
324 """Check to see whether this hook has been approved.
325
326 We'll look at the hash of all of the hooks. If this matches the hash that
327 the user last approved, we're done. If it doesn't, we'll ask the user
328 about approval.
329
330 Note that we ask permission for each individual hook even though we use
331 the hash of all hooks when detecting changes. We'd like the user to be
332 able to approve / deny each hook individually. We only use the hash of all
333 hooks because there is no other easy way to detect changes to local imports.
334
335 Returns:
336 True if this hook is approved to run; False otherwise.
337
338 Raises:
339 HookError: Raised if the user doesn't approve and abort_if_user_denies
340 was passed to the consturctor.
341 """
Doug Anderson37282b42011-03-04 11:54:18 -0800342 hooks_config = self._hooks_project.config
343 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
344
345 # Get the last hash that the user approved for this hook; may be None.
346 old_hash = hooks_config.GetString(git_approval_key)
347
348 # Get the current hash so we can tell if scripts changed since approval.
349 new_hash = self._GetHash()
350
351 if old_hash is not None:
352 # User previously approved hook and asked not to be prompted again.
353 if new_hash == old_hash:
354 # Approval matched. We're done.
355 return True
356 else:
357 # Give the user a reason why we're prompting, since they last told
358 # us to "never ask again".
359 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
360 self._hook_type)
361 else:
362 prompt = ''
363
364 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
365 if sys.stdout.isatty():
366 prompt += ('Repo %s run the script:\n'
367 ' %s\n'
368 '\n'
369 'Do you want to allow this script to run '
370 '(yes/yes-never-ask-again/NO)? ') % (
371 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530372 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900373 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800374
375 # User is doing a one-time approval.
376 if response in ('y', 'yes'):
377 return True
378 elif response == 'yes-never-ask-again':
379 hooks_config.SetString(git_approval_key, new_hash)
380 return True
381
382 # For anything else, we'll assume no approval.
383 if self._abort_if_user_denies:
384 raise HookError('You must allow the %s hook or use --no-verify.' %
385 self._hook_type)
386
387 return False
388
389 def _ExecuteHook(self, **kwargs):
390 """Actually execute the given hook.
391
392 This will run the hook's 'main' function in our python interpreter.
393
394 Args:
395 kwargs: Keyword arguments to pass to the hook. These are often specific
396 to the hook type. For instance, pre-upload hooks will contain
397 a project_list.
398 """
399 # Keep sys.path and CWD stashed away so that we can always restore them
400 # upon function exit.
401 orig_path = os.getcwd()
402 orig_syspath = sys.path
403
404 try:
405 # Always run hooks with CWD as topdir.
406 os.chdir(self._topdir)
407
408 # Put the hook dir as the first item of sys.path so hooks can do
409 # relative imports. We want to replace the repo dir as [0] so
410 # hooks can't import repo files.
411 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
412
413 # Exec, storing global context in the context dict. We catch exceptions
414 # and convert to a HookError w/ just the failing traceback.
415 context = {}
416 try:
417 execfile(self._script_fullpath, context)
418 except Exception:
419 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
420 traceback.format_exc(), self._hook_type))
421
422 # Running the script should have defined a main() function.
423 if 'main' not in context:
424 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
425
426
427 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
428 # We don't actually want hooks to define their main with this argument--
429 # it's there to remind them that their hook should always take **kwargs.
430 # For instance, a pre-upload hook should be defined like:
431 # def main(project_list, **kwargs):
432 #
433 # This allows us to later expand the API without breaking old hooks.
434 kwargs = kwargs.copy()
435 kwargs['hook_should_take_kwargs'] = True
436
437 # Call the main function in the hook. If the hook should cause the
438 # build to fail, it will raise an Exception. We'll catch that convert
439 # to a HookError w/ just the failing traceback.
440 try:
441 context['main'](**kwargs)
442 except Exception:
443 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
444 'above.' % (
445 traceback.format_exc(), self._hook_type))
446 finally:
447 # Restore sys.path and CWD.
448 sys.path = orig_syspath
449 os.chdir(orig_path)
450
451 def Run(self, user_allows_all_hooks, **kwargs):
452 """Run the hook.
453
454 If the hook doesn't exist (because there is no hooks project or because
455 this particular hook is not enabled), this is a no-op.
456
457 Args:
458 user_allows_all_hooks: If True, we will never prompt about running the
459 hook--we'll just assume it's OK to run it.
460 kwargs: Keyword arguments to pass to the hook. These are often specific
461 to the hook type. For instance, pre-upload hooks will contain
462 a project_list.
463
464 Raises:
465 HookError: If there was a problem finding the hook or the user declined
466 to run a required hook (from _CheckForHookApproval).
467 """
468 # No-op if there is no hooks project or if hook is disabled.
469 if ((not self._hooks_project) or
470 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
471 return
472
473 # Bail with a nice error if we can't find the hook.
474 if not os.path.isfile(self._script_fullpath):
475 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
476
477 # Make sure the user is OK with running the hook.
478 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
479 return
480
481 # Run the hook with the same version of python we're using.
482 self._ExecuteHook(**kwargs)
483
484
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700485class Project(object):
486 def __init__(self,
487 manifest,
488 name,
489 remote,
490 gitdir,
491 worktree,
492 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700493 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800494 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700495 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700496 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700497 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800498 sync_s = False,
David Pursehouseede7f122012-11-27 22:25:30 +0900499 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800500 upstream = None,
501 parent = None,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400502 is_derived = False,
503 dest_branch = None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800504 """Init a Project object.
505
506 Args:
507 manifest: The XmlManifest object.
508 name: The `name` attribute of manifest.xml's project element.
509 remote: RemoteSpec object specifying its remote's properties.
510 gitdir: Absolute path of git directory.
511 worktree: Absolute path of git working tree.
512 relpath: Relative path of git working tree to repo's top directory.
513 revisionExpr: The `revision` attribute of manifest.xml's project element.
514 revisionId: git commit id for checking out.
515 rebase: The `rebase` attribute of manifest.xml's project element.
516 groups: The `groups` attribute of manifest.xml's project element.
517 sync_c: The `sync-c` attribute of manifest.xml's project element.
518 sync_s: The `sync-s` attribute of manifest.xml's project element.
519 upstream: The `upstream` attribute of manifest.xml's project element.
520 parent: The parent Project object.
521 is_derived: False if the project was explicitly defined in the manifest;
522 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400523 dest_branch: The branch to which to push changes for review by default.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800524 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700525 self.manifest = manifest
526 self.name = name
527 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800528 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800529 if worktree:
530 self.worktree = worktree.replace('\\', '/')
531 else:
532 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700533 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700534 self.revisionExpr = revisionExpr
535
536 if revisionId is None \
537 and revisionExpr \
538 and IsId(revisionExpr):
539 self.revisionId = revisionExpr
540 else:
541 self.revisionId = revisionId
542
Mike Pontillod3153822012-02-28 11:53:24 -0800543 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700544 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700545 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800546 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900547 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700548 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800549 self.parent = parent
550 self.is_derived = is_derived
551 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800552
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700553 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700554 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500555 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700556 self.config = GitConfig.ForRepository(
557 gitdir = self.gitdir,
558 defaults = self.manifest.globalConfig)
559
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800560 if self.worktree:
561 self.work_git = self._GitGetByExec(self, bare=False)
562 else:
563 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700564 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700565 self.bare_ref = GitRefs(gitdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400566 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567
Doug Anderson37282b42011-03-04 11:54:18 -0800568 # This will be filled in if a project is later identified to be the
569 # project containing repo hooks.
570 self.enabled_repo_hooks = []
571
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700572 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800573 def Derived(self):
574 return self.is_derived
575
576 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700577 def Exists(self):
578 return os.path.isdir(self.gitdir)
579
580 @property
581 def CurrentBranch(self):
582 """Obtain the name of the currently checked out branch.
583 The branch name omits the 'refs/heads/' prefix.
584 None is returned if the project is on a detached HEAD.
585 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700586 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700587 if b.startswith(R_HEADS):
588 return b[len(R_HEADS):]
589 return None
590
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700591 def IsRebaseInProgress(self):
592 w = self.worktree
593 g = os.path.join(w, '.git')
594 return os.path.exists(os.path.join(g, 'rebase-apply')) \
595 or os.path.exists(os.path.join(g, 'rebase-merge')) \
596 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200597
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700598 def IsDirty(self, consider_untracked=True):
599 """Is the working directory modified in some way?
600 """
601 self.work_git.update_index('-q',
602 '--unmerged',
603 '--ignore-missing',
604 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900605 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700606 return True
607 if self.work_git.DiffZ('diff-files'):
608 return True
609 if consider_untracked and self.work_git.LsOthers():
610 return True
611 return False
612
613 _userident_name = None
614 _userident_email = None
615
616 @property
617 def UserName(self):
618 """Obtain the user's personal name.
619 """
620 if self._userident_name is None:
621 self._LoadUserIdentity()
622 return self._userident_name
623
624 @property
625 def UserEmail(self):
626 """Obtain the user's email address. This is very likely
627 to be their Gerrit login.
628 """
629 if self._userident_email is None:
630 self._LoadUserIdentity()
631 return self._userident_email
632
633 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900634 u = self.bare_git.var('GIT_COMMITTER_IDENT')
635 m = re.compile("^(.*) <([^>]*)> ").match(u)
636 if m:
637 self._userident_name = m.group(1)
638 self._userident_email = m.group(2)
639 else:
640 self._userident_name = ''
641 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700642
643 def GetRemote(self, name):
644 """Get the configuration for a single remote.
645 """
646 return self.config.GetRemote(name)
647
648 def GetBranch(self, name):
649 """Get the configuration for a single branch.
650 """
651 return self.config.GetBranch(name)
652
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700653 def GetBranches(self):
654 """Get all existing local branches.
655 """
656 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900657 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700658 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700659
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530660 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700661 if name.startswith(R_HEADS):
662 name = name[len(R_HEADS):]
663 b = self.GetBranch(name)
664 b.current = name == current
665 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900666 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700667 heads[name] = b
668
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530669 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700670 if name.startswith(R_PUB):
671 name = name[len(R_PUB):]
672 b = heads.get(name)
673 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900674 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700675
676 return heads
677
Colin Cross5acde752012-03-28 20:15:45 -0700678 def MatchesGroups(self, manifest_groups):
679 """Returns true if the manifest groups specified at init should cause
680 this project to be synced.
681 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700682 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700683
Conley Owens971de8e2012-04-16 10:36:08 -0700684 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700685 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700686 manifest_groups: "-group1,group2"
687 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500688
689 The special manifest group "default" will match any project that
690 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700691 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500692 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700693 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500694 if not 'notdefault' in expanded_project_groups:
695 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700696
Conley Owens971de8e2012-04-16 10:36:08 -0700697 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700698 for group in expanded_manifest_groups:
699 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700700 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700701 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700702 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700703
Conley Owens971de8e2012-04-16 10:36:08 -0700704 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700705
706## Status Display ##
707
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500708 def HasChanges(self):
709 """Returns true if there are uncommitted changes.
710 """
711 self.work_git.update_index('-q',
712 '--unmerged',
713 '--ignore-missing',
714 '--refresh')
715 if self.IsRebaseInProgress():
716 return True
717
718 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
719 return True
720
721 if self.work_git.DiffZ('diff-files'):
722 return True
723
724 if self.work_git.LsOthers():
725 return True
726
727 return False
728
Terence Haddock4655e812011-03-31 12:33:34 +0200729 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700730 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200731
732 Args:
733 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700734 """
735 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200736 if output_redir == None:
737 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700738 print(file=output_redir)
739 print('project %s/' % self.relpath, file=output_redir)
740 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741 return
742
743 self.work_git.update_index('-q',
744 '--unmerged',
745 '--ignore-missing',
746 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700747 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
749 df = self.work_git.DiffZ('diff-files')
750 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100751 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700752 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700753
754 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200755 if not output_redir == None:
756 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 out.project('project %-40s', self.relpath + '/')
758
759 branch = self.CurrentBranch
760 if branch is None:
761 out.nobranch('(*** NO BRANCH ***)')
762 else:
763 out.branch('branch %s', branch)
764 out.nl()
765
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700766 if rb:
767 out.important('prior sync failed; rebase still in progress')
768 out.nl()
769
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770 paths = list()
771 paths.extend(di.keys())
772 paths.extend(df.keys())
773 paths.extend(do)
774
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530775 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900776 try:
777 i = di[p]
778 except KeyError:
779 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900781 try:
782 f = df[p]
783 except KeyError:
784 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200785
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900786 if i:
787 i_status = i.status.upper()
788 else:
789 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700790
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900791 if f:
792 f_status = f.status.lower()
793 else:
794 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700795
796 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800797 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700798 i.src_path, p, i.level)
799 else:
800 line = ' %s%s\t%s' % (i_status, f_status, p)
801
802 if i and not f:
803 out.added('%s', line)
804 elif (i and f) or (not i and f):
805 out.changed('%s', line)
806 elif not i and not f:
807 out.untracked('%s', line)
808 else:
809 out.write('%s', line)
810 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200811
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700812 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700813
pelyad67872d2012-03-28 14:49:58 +0300814 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700815 """Prints the status of the repository to stdout.
816 """
817 out = DiffColoring(self.config)
818 cmd = ['diff']
819 if out.is_on:
820 cmd.append('--color')
821 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300822 if absolute_paths:
823 cmd.append('--src-prefix=a/%s/' % self.relpath)
824 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700825 cmd.append('--')
826 p = GitCommand(self,
827 cmd,
828 capture_stdout = True,
829 capture_stderr = True)
830 has_diff = False
831 for line in p.process.stdout:
832 if not has_diff:
833 out.nl()
834 out.project('project %s/' % self.relpath)
835 out.nl()
836 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700837 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 p.Wait()
839
840
841## Publish / Upload ##
842
David Pursehouse8a68ff92012-09-24 12:15:13 +0900843 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844 """Was the branch published (uploaded) for code review?
845 If so, returns the SHA-1 hash of the last published
846 state for the branch.
847 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700848 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900849 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700850 try:
851 return self.bare_git.rev_parse(key)
852 except GitError:
853 return None
854 else:
855 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900856 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700857 except KeyError:
858 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700859
David Pursehouse8a68ff92012-09-24 12:15:13 +0900860 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700861 """Prunes any stale published refs.
862 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900863 if all_refs is None:
864 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865 heads = set()
866 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530867 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868 if name.startswith(R_HEADS):
869 heads.add(name)
870 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900871 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530873 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874 n = name[len(R_PUB):]
875 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900876 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700878 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879 """List any branches which can be uploaded for review.
880 """
881 heads = {}
882 pubed = {}
883
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530884 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700885 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900886 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700887 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900888 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889
890 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530891 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900892 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700894 if selected_branch and branch != selected_branch:
895 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800897 rb = self.GetUploadableBranch(branch)
898 if rb:
899 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900 return ready
901
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800902 def GetUploadableBranch(self, branch_name):
903 """Get a single uploadable branch, or None.
904 """
905 branch = self.GetBranch(branch_name)
906 base = branch.LocalMerge
907 if branch.LocalMerge:
908 rb = ReviewableBranch(self, branch, base)
909 if rb.commits:
910 return rb
911 return None
912
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700913 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700914 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700915 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400916 draft=False,
917 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918 """Uploads the named branch for code review.
919 """
920 if branch is None:
921 branch = self.CurrentBranch
922 if branch is None:
923 raise GitError('not currently on a branch')
924
925 branch = self.GetBranch(branch)
926 if not branch.LocalMerge:
927 raise GitError('branch %s does not track a remote' % branch.name)
928 if not branch.remote.review:
929 raise GitError('remote %s has no review url' % branch.remote.name)
930
Bryan Jacobsf609f912013-05-06 13:36:24 -0400931 if dest_branch is None:
932 dest_branch = self.dest_branch
933 if dest_branch is None:
934 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700935 if not dest_branch.startswith(R_HEADS):
936 dest_branch = R_HEADS + dest_branch
937
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800938 if not branch.remote.projectname:
939 branch.remote.projectname = self.name
940 branch.remote.Save()
941
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800942 url = branch.remote.ReviewUrl(self.UserEmail)
943 if url is None:
944 raise UploadError('review not configured')
945 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800946
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800947 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800948 rp = ['gerrit receive-pack']
949 for e in people[0]:
950 rp.append('--reviewer=%s' % sq(e))
951 for e in people[1]:
952 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800953 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700954
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800955 cmd.append(url)
956
957 if dest_branch.startswith(R_HEADS):
958 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700959
960 upload_type = 'for'
961 if draft:
962 upload_type = 'drafts'
963
964 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
965 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800966 if auto_topic:
967 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800968 if not url.startswith('ssh://'):
969 rp = ['r=%s' % p for p in people[0]] + \
970 ['cc=%s' % p for p in people[1]]
971 if rp:
972 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800973 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800974
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800975 if GitCommand(self, cmd, bare = True).Wait() != 0:
976 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700977
978 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
979 self.bare_git.UpdateRef(R_PUB + branch.name,
980 R_HEADS + branch.name,
981 message = msg)
982
983
984## Sync ##
985
Julien Campergue335f5ef2013-10-16 11:02:35 +0200986 def _ExtractArchive(self, tarpath, path=None):
987 """Extract the given tar on its current location
988
989 Args:
990 - tarpath: The path to the actual tar file
991
992 """
993 try:
994 with tarfile.open(tarpath, 'r') as tar:
995 tar.extractall(path=path)
996 return True
997 except (IOError, tarfile.TarError) as e:
998 print("error: Cannot extract archive %s: "
999 "%s" % (tarpath, str(e)), file=sys.stderr)
1000 return False
1001
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001002 def Sync_NetworkHalf(self,
1003 quiet=False,
1004 is_new=None,
1005 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001006 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001007 no_tags=False,
1008 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001009 """Perform only the network IO portion of the sync process.
1010 Local working directory/branch state is not affected.
1011 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001012 if archive and not isinstance(self, MetaProject):
1013 if self.remote.url.startswith(('http://', 'https://')):
1014 print("error: %s: Cannot fetch archives from http/https "
1015 "remotes." % self.name, file=sys.stderr)
1016 return False
1017
1018 name = self.relpath.replace('\\', '/')
1019 name = name.replace('/', '_')
1020 tarpath = '%s.tar' % name
1021 topdir = self.manifest.topdir
1022
1023 try:
1024 self._FetchArchive(tarpath, cwd=topdir)
1025 except GitError as e:
1026 print('error: %s' % str(e), file=sys.stderr)
1027 return False
1028
1029 # From now on, we only need absolute tarpath
1030 tarpath = os.path.join(topdir, tarpath)
1031
1032 if not self._ExtractArchive(tarpath, path=topdir):
1033 return False
1034 try:
1035 os.remove(tarpath)
1036 except OSError as e:
1037 print("warn: Cannot remove archive %s: "
1038 "%s" % (tarpath, str(e)), file=sys.stderr)
1039 self._CopyFiles()
1040 return True
1041
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001042 if is_new is None:
1043 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001044 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001045 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001046 else:
1047 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001048 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001049
1050 if is_new:
1051 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1052 try:
1053 fd = open(alt, 'rb')
1054 try:
1055 alt_dir = fd.readline().rstrip()
1056 finally:
1057 fd.close()
1058 except IOError:
1059 alt_dir = None
1060 else:
1061 alt_dir = None
1062
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001063 if clone_bundle \
1064 and alt_dir is None \
1065 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001066 is_new = False
1067
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001068 if not current_branch_only:
1069 if self.sync_c:
1070 current_branch_only = True
1071 elif not self.manifest._loaded:
1072 # Manifest cannot check defaults until it syncs.
1073 current_branch_only = False
1074 elif self.manifest.default.sync_c:
1075 current_branch_only = True
1076
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001077 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001078 current_branch_only=current_branch_only,
1079 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001081
1082 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001083 self._InitMRef()
1084 else:
1085 self._InitMirrorHead()
1086 try:
1087 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1088 except OSError:
1089 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001091
1092 def PostRepoUpgrade(self):
1093 self._InitHooks()
1094
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001095 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001096 for copyfile in self.copyfiles:
1097 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001098
David Pursehouse8a68ff92012-09-24 12:15:13 +09001099 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001100 if self.revisionId:
1101 return self.revisionId
1102
1103 rem = self.GetRemote(self.remote.name)
1104 rev = rem.ToLocal(self.revisionExpr)
1105
David Pursehouse8a68ff92012-09-24 12:15:13 +09001106 if all_refs is not None and rev in all_refs:
1107 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001108
1109 try:
1110 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1111 except GitError:
1112 raise ManifestInvalidRevisionError(
1113 'revision %s in %s not found' % (self.revisionExpr,
1114 self.name))
1115
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001116 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117 """Perform only the local IO portion of the sync process.
1118 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001120 all_refs = self.bare_ref.all
1121 self.CleanPublishedCache(all_refs)
1122 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001123
David Pursehouse1d947b32012-10-25 12:23:11 +09001124 def _doff():
1125 self._FastForward(revid)
1126 self._CopyFiles()
1127
Skyler Kaufman835cd682011-03-08 12:14:41 -08001128 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001129 head = self.work_git.GetHead()
1130 if head.startswith(R_HEADS):
1131 branch = head[len(R_HEADS):]
1132 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001133 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001134 except KeyError:
1135 head = None
1136 else:
1137 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001138
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001139 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001140 # Currently on a detached HEAD. The user is assumed to
1141 # not have any local modifications worth worrying about.
1142 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001143 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001144 syncbuf.fail(self, _PriorSyncFailedError())
1145 return
1146
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001147 if head == revid:
1148 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001149 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001150 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001151 if not syncbuf.detach_head:
1152 return
1153 else:
1154 lost = self._revlist(not_rev(revid), HEAD)
1155 if lost:
1156 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001157
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001158 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001159 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001160 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001161 syncbuf.fail(self, e)
1162 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001164 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001165
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001166 if head == revid:
1167 # No changes; don't do anything further.
1168 #
1169 return
1170
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001171 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001173 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001174 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001175 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001177 syncbuf.info(self,
1178 "leaving %s; does not track upstream",
1179 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001180 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001181 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001182 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001183 syncbuf.fail(self, e)
1184 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001185 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001186 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001188 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001189 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001191 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192 if not_merged:
1193 if upstream_gain:
1194 # The user has published this branch and some of those
1195 # commits are not yet merged upstream. We do not want
1196 # to rewrite the published commits so we punt.
1197 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001198 syncbuf.fail(self,
1199 "branch %s is published (but not merged) and is now %d commits behind"
1200 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001201 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001202 elif pub == head:
1203 # All published commits are merged, and thus we are a
1204 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001205 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001206 syncbuf.later1(self, _doff)
1207 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001208
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001209 # Examine the local commits not in the remote. Find the
1210 # last one attributed to this user, if any.
1211 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001212 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001213 last_mine = None
1214 cnt_mine = 0
1215 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301216 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001217 if committer_email == self.UserEmail:
1218 last_mine = commit_id
1219 cnt_mine += 1
1220
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001221 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001222 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001223
1224 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001225 syncbuf.fail(self, _DirtyError())
1226 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001228 # If the upstream switched on us, warn the user.
1229 #
1230 if branch.merge != self.revisionExpr:
1231 if branch.merge and self.revisionExpr:
1232 syncbuf.info(self,
1233 'manifest switched %s...%s',
1234 branch.merge,
1235 self.revisionExpr)
1236 elif branch.merge:
1237 syncbuf.info(self,
1238 'manifest no longer tracks %s',
1239 branch.merge)
1240
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001241 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001243 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001244 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001245 syncbuf.info(self,
1246 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001247 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001248
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001249 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001250 if not ID_RE.match(self.revisionExpr):
1251 # in case of manifest sync the revisionExpr might be a SHA1
1252 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001253 branch.Save()
1254
Mike Pontillod3153822012-02-28 11:53:24 -08001255 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001256 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001257 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001258 self._CopyFiles()
1259 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001260 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001261 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001262 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001263 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001264 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001265 syncbuf.fail(self, e)
1266 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001267 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001268 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001269
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001270 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271 # dest should already be an absolute path, but src is project relative
1272 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001273 abssrc = os.path.join(self.worktree, src)
1274 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001275
James W. Mills24c13082012-04-12 15:04:13 -05001276 def AddAnnotation(self, name, value, keep):
1277 self.annotations.append(_Annotation(name, value, keep))
1278
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001279 def DownloadPatchSet(self, change_id, patch_id):
1280 """Download a single patch set of a single change to FETCH_HEAD.
1281 """
1282 remote = self.GetRemote(self.remote.name)
1283
1284 cmd = ['fetch', remote.name]
1285 cmd.append('refs/changes/%2.2d/%d/%d' \
1286 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001287 if GitCommand(self, cmd, bare=True).Wait() != 0:
1288 return None
1289 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001290 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001291 change_id,
1292 patch_id,
1293 self.bare_git.rev_parse('FETCH_HEAD'))
1294
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001295
1296## Branch Management ##
1297
1298 def StartBranch(self, name):
1299 """Create a new branch off the manifest's revision.
1300 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001301 head = self.work_git.GetHead()
1302 if head == (R_HEADS + name):
1303 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001304
David Pursehouse8a68ff92012-09-24 12:15:13 +09001305 all_refs = self.bare_ref.all
1306 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001307 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001308 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001309 capture_stdout = True,
1310 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001311
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001312 branch = self.GetBranch(name)
1313 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001314 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001315 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001316
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001317 if head.startswith(R_HEADS):
1318 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001319 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001320 except KeyError:
1321 head = None
1322
1323 if revid and head and revid == head:
1324 ref = os.path.join(self.gitdir, R_HEADS + name)
1325 try:
1326 os.makedirs(os.path.dirname(ref))
1327 except OSError:
1328 pass
1329 _lwrite(ref, '%s\n' % revid)
1330 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1331 'ref: %s%s\n' % (R_HEADS, name))
1332 branch.Save()
1333 return True
1334
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001335 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001336 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001337 capture_stdout = True,
1338 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001339 branch.Save()
1340 return True
1341 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342
Wink Saville02d79452009-04-10 13:01:24 -07001343 def CheckoutBranch(self, name):
1344 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001345
1346 Args:
1347 name: The name of the branch to checkout.
1348
1349 Returns:
1350 True if the checkout succeeded; False if it didn't; None if the branch
1351 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001352 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001353 rev = R_HEADS + name
1354 head = self.work_git.GetHead()
1355 if head == rev:
1356 # Already on the branch
1357 #
1358 return True
Wink Saville02d79452009-04-10 13:01:24 -07001359
David Pursehouse8a68ff92012-09-24 12:15:13 +09001360 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001361 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001362 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001363 except KeyError:
1364 # Branch does not exist in this project
1365 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001366 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001367
1368 if head.startswith(R_HEADS):
1369 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001370 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001371 except KeyError:
1372 head = None
1373
1374 if head == revid:
1375 # Same revision; just update HEAD to point to the new
1376 # target branch, but otherwise take no other action.
1377 #
1378 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1379 'ref: %s%s\n' % (R_HEADS, name))
1380 return True
Wink Saville02d79452009-04-10 13:01:24 -07001381
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001382 return GitCommand(self,
1383 ['checkout', name, '--'],
1384 capture_stdout = True,
1385 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001386
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001387 def AbandonBranch(self, name):
1388 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001389
1390 Args:
1391 name: The name of the branch to abandon.
1392
1393 Returns:
1394 True if the abandon succeeded; False if it didn't; None if the branch
1395 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001396 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001397 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001398 all_refs = self.bare_ref.all
1399 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001400 # Doesn't exist
1401 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001402
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001403 head = self.work_git.GetHead()
1404 if head == rev:
1405 # We can't destroy the branch while we are sitting
1406 # on it. Switch to a detached HEAD.
1407 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001408 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001409
David Pursehouse8a68ff92012-09-24 12:15:13 +09001410 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001411 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001412 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1413 '%s\n' % revid)
1414 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001415 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001416
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001417 return GitCommand(self,
1418 ['branch', '-D', name],
1419 capture_stdout = True,
1420 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001421
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001422 def PruneHeads(self):
1423 """Prune any topic branches already merged into upstream.
1424 """
1425 cb = self.CurrentBranch
1426 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001427 left = self._allrefs
1428 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001429 if name.startswith(R_HEADS):
1430 name = name[len(R_HEADS):]
1431 if cb is None or name != cb:
1432 kill.append(name)
1433
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001434 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001435 if cb is not None \
1436 and not self._revlist(HEAD + '...' + rev) \
1437 and not self.IsDirty(consider_untracked = False):
1438 self.work_git.DetachHead(HEAD)
1439 kill.append(cb)
1440
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001441 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001442 old = self.bare_git.GetHead()
1443 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001444 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1445
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001446 try:
1447 self.bare_git.DetachHead(rev)
1448
1449 b = ['branch', '-d']
1450 b.extend(kill)
1451 b = GitCommand(self, b, bare=True,
1452 capture_stdout=True,
1453 capture_stderr=True)
1454 b.Wait()
1455 finally:
1456 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001457 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001458
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001459 for branch in kill:
1460 if (R_HEADS + branch) not in left:
1461 self.CleanPublishedCache()
1462 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001463
1464 if cb and cb not in kill:
1465 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001466 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001467
1468 kept = []
1469 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001470 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001471 branch = self.GetBranch(branch)
1472 base = branch.LocalMerge
1473 if not base:
1474 base = rev
1475 kept.append(ReviewableBranch(self, branch, base))
1476 return kept
1477
1478
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001479## Submodule Management ##
1480
1481 def GetRegisteredSubprojects(self):
1482 result = []
1483 def rec(subprojects):
1484 if not subprojects:
1485 return
1486 result.extend(subprojects)
1487 for p in subprojects:
1488 rec(p.subprojects)
1489 rec(self.subprojects)
1490 return result
1491
1492 def _GetSubmodules(self):
1493 # Unfortunately we cannot call `git submodule status --recursive` here
1494 # because the working tree might not exist yet, and it cannot be used
1495 # without a working tree in its current implementation.
1496
1497 def get_submodules(gitdir, rev):
1498 # Parse .gitmodules for submodule sub_paths and sub_urls
1499 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1500 if not sub_paths:
1501 return []
1502 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1503 # revision of submodule repository
1504 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1505 submodules = []
1506 for sub_path, sub_url in zip(sub_paths, sub_urls):
1507 try:
1508 sub_rev = sub_revs[sub_path]
1509 except KeyError:
1510 # Ignore non-exist submodules
1511 continue
1512 submodules.append((sub_rev, sub_path, sub_url))
1513 return submodules
1514
1515 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1516 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1517 def parse_gitmodules(gitdir, rev):
1518 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1519 try:
1520 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1521 bare = True, gitdir = gitdir)
1522 except GitError:
1523 return [], []
1524 if p.Wait() != 0:
1525 return [], []
1526
1527 gitmodules_lines = []
1528 fd, temp_gitmodules_path = tempfile.mkstemp()
1529 try:
1530 os.write(fd, p.stdout)
1531 os.close(fd)
1532 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1533 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1534 bare = True, gitdir = gitdir)
1535 if p.Wait() != 0:
1536 return [], []
1537 gitmodules_lines = p.stdout.split('\n')
1538 except GitError:
1539 return [], []
1540 finally:
1541 os.remove(temp_gitmodules_path)
1542
1543 names = set()
1544 paths = {}
1545 urls = {}
1546 for line in gitmodules_lines:
1547 if not line:
1548 continue
1549 m = re_path.match(line)
1550 if m:
1551 names.add(m.group(1))
1552 paths[m.group(1)] = m.group(2)
1553 continue
1554 m = re_url.match(line)
1555 if m:
1556 names.add(m.group(1))
1557 urls[m.group(1)] = m.group(2)
1558 continue
1559 names = sorted(names)
1560 return ([paths.get(name, '') for name in names],
1561 [urls.get(name, '') for name in names])
1562
1563 def git_ls_tree(gitdir, rev, paths):
1564 cmd = ['ls-tree', rev, '--']
1565 cmd.extend(paths)
1566 try:
1567 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1568 bare = True, gitdir = gitdir)
1569 except GitError:
1570 return []
1571 if p.Wait() != 0:
1572 return []
1573 objects = {}
1574 for line in p.stdout.split('\n'):
1575 if not line.strip():
1576 continue
1577 object_rev, object_path = line.split()[2:4]
1578 objects[object_path] = object_rev
1579 return objects
1580
1581 try:
1582 rev = self.GetRevisionId()
1583 except GitError:
1584 return []
1585 return get_submodules(self.gitdir, rev)
1586
1587 def GetDerivedSubprojects(self):
1588 result = []
1589 if not self.Exists:
1590 # If git repo does not exist yet, querying its submodules will
1591 # mess up its states; so return here.
1592 return result
1593 for rev, path, url in self._GetSubmodules():
1594 name = self.manifest.GetSubprojectName(self, path)
1595 project = self.manifest.projects.get(name)
1596 if project:
1597 result.extend(project.GetDerivedSubprojects())
1598 continue
1599 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1600 remote = RemoteSpec(self.remote.name,
1601 url = url,
1602 review = self.remote.review)
1603 subproject = Project(manifest = self.manifest,
1604 name = name,
1605 remote = remote,
1606 gitdir = gitdir,
1607 worktree = worktree,
1608 relpath = relpath,
1609 revisionExpr = self.revisionExpr,
1610 revisionId = rev,
1611 rebase = self.rebase,
1612 groups = self.groups,
1613 sync_c = self.sync_c,
1614 sync_s = self.sync_s,
1615 parent = self,
1616 is_derived = True)
1617 result.append(subproject)
1618 result.extend(subproject.GetDerivedSubprojects())
1619 return result
1620
1621
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001622## Direct Git Commands ##
1623
Julien Campergue335f5ef2013-10-16 11:02:35 +02001624 def _FetchArchive(self, tarpath, cwd=None):
1625 cmd = ['archive', '-v', '-o', tarpath]
1626 cmd.append('--remote=%s' % self.remote.url)
1627 cmd.append('--prefix=%s/' % self.relpath)
1628 cmd.append(self.revisionExpr)
1629
1630 command = GitCommand(self, cmd, cwd=cwd,
1631 capture_stdout=True,
1632 capture_stderr=True)
1633
1634 if command.Wait() != 0:
1635 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1636
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001637 def _RemoteFetch(self, name=None,
1638 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001639 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001640 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001641 alt_dir=None,
1642 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001643
1644 is_sha1 = False
1645 tag_name = None
1646
Brian Harring14a66742012-09-28 20:21:57 -07001647 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001648 try:
1649 # if revision (sha or tag) is not present then following function
1650 # throws an error.
1651 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1652 return True
1653 except GitError:
1654 # There is no such persistent revision. We have to fetch it.
1655 return False
Brian Harring14a66742012-09-28 20:21:57 -07001656
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001657 if current_branch_only:
1658 if ID_RE.match(self.revisionExpr) is not None:
1659 is_sha1 = True
1660 elif self.revisionExpr.startswith(R_TAGS):
1661 # this is a tag and its sha1 value should never change
1662 tag_name = self.revisionExpr[len(R_TAGS):]
1663
1664 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001665 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001666 return True
Brian Harring14a66742012-09-28 20:21:57 -07001667 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1668 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001669
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001670 if not name:
1671 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001672
1673 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001674 remote = self.GetRemote(name)
1675 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001676 ssh_proxy = True
1677
Shawn O. Pearce88443382010-10-08 10:02:09 +02001678 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001679 if alt_dir and 'objects' == os.path.basename(alt_dir):
1680 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001681 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1682 remote = self.GetRemote(name)
1683
David Pursehouse8a68ff92012-09-24 12:15:13 +09001684 all_refs = self.bare_ref.all
1685 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001686 tmp = set()
1687
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301688 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001689 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001690 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001691 all_refs[r] = ref_id
1692 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001693 continue
1694
David Pursehouse8a68ff92012-09-24 12:15:13 +09001695 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001696 continue
1697
David Pursehouse8a68ff92012-09-24 12:15:13 +09001698 r = 'refs/_alt/%s' % ref_id
1699 all_refs[r] = ref_id
1700 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001701 tmp.add(r)
1702
Shawn O. Pearce88443382010-10-08 10:02:09 +02001703 tmp_packed = ''
1704 old_packed = ''
1705
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301706 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001707 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001708 tmp_packed += line
1709 if r not in tmp:
1710 old_packed += line
1711
1712 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001713 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001714 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001715
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001716 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001717
1718 # The --depth option only affects the initial fetch; after that we'll do
1719 # full fetches of changes.
David Pursehouseede7f122012-11-27 22:25:30 +09001720 if self.clone_depth:
1721 depth = self.clone_depth
1722 else:
1723 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Doug Anderson30d45292011-05-04 15:01:04 -07001724 if depth and initial:
1725 cmd.append('--depth=%s' % depth)
1726
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001727 if quiet:
1728 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001729 if not self.worktree:
1730 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001731 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001732
Brian Harring14a66742012-09-28 20:21:57 -07001733 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001734 # Fetch whole repo
Jimmie Wester2f992cb2012-12-07 12:49:51 +01001735 # If using depth then we should not get all the tags since they may
1736 # be outside of the depth.
1737 if no_tags or depth:
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001738 cmd.append('--no-tags')
1739 else:
1740 cmd.append('--tags')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301741 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001742 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001743 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001744 cmd.append(tag_name)
1745 else:
1746 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001747 if is_sha1:
1748 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001749 if branch.startswith(R_HEADS):
1750 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301751 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001752
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001753 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001754 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001755 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1756 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001757 ok = True
1758 break
Brian Harring14a66742012-09-28 20:21:57 -07001759 elif current_branch_only and is_sha1 and ret == 128:
1760 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1761 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1762 # abort the optimization attempt and do a full sync.
1763 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001764 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001765
1766 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001767 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001768 if old_packed != '':
1769 _lwrite(packed_refs, old_packed)
1770 else:
1771 os.remove(packed_refs)
1772 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001773
1774 if is_sha1 and current_branch_only and self.upstream:
1775 # We just synced the upstream given branch; verify we
1776 # got what we wanted, else trigger a second run of all
1777 # refs.
1778 if not CheckForSha1():
1779 return self._RemoteFetch(name=name, current_branch_only=False,
1780 initial=False, quiet=quiet, alt_dir=alt_dir)
1781
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001782 return ok
1783
1784 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001785 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001786 return False
1787
1788 remote = self.GetRemote(self.remote.name)
1789 bundle_url = remote.url + '/clone.bundle'
1790 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001791 if GetSchemeFromUrl(bundle_url) not in (
1792 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001793 return False
1794
1795 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1796 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001797
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001798 exist_dst = os.path.exists(bundle_dst)
1799 exist_tmp = os.path.exists(bundle_tmp)
1800
1801 if not initial and not exist_dst and not exist_tmp:
1802 return False
1803
1804 if not exist_dst:
1805 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1806 if not exist_dst:
1807 return False
1808
1809 cmd = ['fetch']
1810 if quiet:
1811 cmd.append('--quiet')
1812 if not self.worktree:
1813 cmd.append('--update-head-ok')
1814 cmd.append(bundle_dst)
1815 for f in remote.fetch:
1816 cmd.append(str(f))
1817 cmd.append('refs/tags/*:refs/tags/*')
1818
1819 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001820 if os.path.exists(bundle_dst):
1821 os.remove(bundle_dst)
1822 if os.path.exists(bundle_tmp):
1823 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001824 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001825
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001826 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001827 if os.path.exists(dstPath):
1828 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001829
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001830 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001831 if quiet:
1832 cmd += ['--silent']
1833 if os.path.exists(tmpPath):
1834 size = os.stat(tmpPath).st_size
1835 if size >= 1024:
1836 cmd += ['--continue-at', '%d' % (size,)]
1837 else:
1838 os.remove(tmpPath)
1839 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1840 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001841 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001842 if cookiefile:
1843 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001844 if srcUrl.startswith('persistent-'):
1845 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001846 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001847
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001848 if IsTrace():
1849 Trace('%s', ' '.join(cmd))
1850 try:
1851 proc = subprocess.Popen(cmd)
1852 except OSError:
1853 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001854
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001855 curlret = proc.wait()
1856
1857 if curlret == 22:
1858 # From curl man page:
1859 # 22: HTTP page not retrieved. The requested url was not found or
1860 # returned another error with the HTTP error code being 400 or above.
1861 # This return code only appears if -f, --fail is used.
1862 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001863 print("Server does not provide clone.bundle; ignoring.",
1864 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001865 return False
1866
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001867 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001868 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001869 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001870 return True
1871 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001872 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001873 return False
1874 else:
1875 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001876
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001877 def _IsValidBundle(self, path):
1878 try:
1879 with open(path) as f:
1880 if f.read(16) == '# v2 git bundle\n':
1881 return True
1882 else:
1883 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1884 return False
1885 except OSError:
1886 return False
1887
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001888 def _GetBundleCookieFile(self, url):
1889 if url.startswith('persistent-'):
1890 try:
1891 p = subprocess.Popen(
1892 ['git-remote-persistent-https', '-print_config', url],
1893 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1894 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001895 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001896 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001897 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001898 for line in p.stdout:
1899 line = line.strip()
1900 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001901 cookiefile = line[len(prefix):]
1902 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001903 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001904 err_msg = p.stderr.read()
1905 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001906 pass # Persistent proxy doesn't support -print_config.
1907 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001908 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001909 if cookiefile:
1910 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001911 except OSError as e:
1912 if e.errno == errno.ENOENT:
1913 pass # No persistent proxy.
1914 raise
1915 return GitConfig.ForUser().GetString('http.cookiefile')
1916
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001917 def _Checkout(self, rev, quiet=False):
1918 cmd = ['checkout']
1919 if quiet:
1920 cmd.append('-q')
1921 cmd.append(rev)
1922 cmd.append('--')
1923 if GitCommand(self, cmd).Wait() != 0:
1924 if self._allrefs:
1925 raise GitError('%s checkout %s ' % (self.name, rev))
1926
Pierre Tardye5a21222011-03-24 16:28:18 +01001927 def _CherryPick(self, rev, quiet=False):
1928 cmd = ['cherry-pick']
1929 cmd.append(rev)
1930 cmd.append('--')
1931 if GitCommand(self, cmd).Wait() != 0:
1932 if self._allrefs:
1933 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1934
Erwan Mahea94f1622011-08-19 13:56:09 +02001935 def _Revert(self, rev, quiet=False):
1936 cmd = ['revert']
1937 cmd.append('--no-edit')
1938 cmd.append(rev)
1939 cmd.append('--')
1940 if GitCommand(self, cmd).Wait() != 0:
1941 if self._allrefs:
1942 raise GitError('%s revert %s ' % (self.name, rev))
1943
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001944 def _ResetHard(self, rev, quiet=True):
1945 cmd = ['reset', '--hard']
1946 if quiet:
1947 cmd.append('-q')
1948 cmd.append(rev)
1949 if GitCommand(self, cmd).Wait() != 0:
1950 raise GitError('%s reset --hard %s ' % (self.name, rev))
1951
1952 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001953 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001954 if onto is not None:
1955 cmd.extend(['--onto', onto])
1956 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001957 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001958 raise GitError('%s rebase %s ' % (self.name, upstream))
1959
Pierre Tardy3d125942012-05-04 12:18:12 +02001960 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001961 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001962 if ffonly:
1963 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001964 if GitCommand(self, cmd).Wait() != 0:
1965 raise GitError('%s merge %s ' % (self.name, head))
1966
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001967 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001968 if not os.path.exists(self.gitdir):
1969 os.makedirs(self.gitdir)
1970 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001971
Shawn O. Pearce88443382010-10-08 10:02:09 +02001972 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001973 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02001974
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001975 if ref_dir or mirror_git:
1976 if not mirror_git:
1977 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001978 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1979 self.relpath + '.git')
1980
1981 if os.path.exists(mirror_git):
1982 ref_dir = mirror_git
1983
1984 elif os.path.exists(repo_git):
1985 ref_dir = repo_git
1986
1987 else:
1988 ref_dir = None
1989
1990 if ref_dir:
1991 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1992 os.path.join(ref_dir, 'objects') + '\n')
1993
Jimmie Westera0444582012-10-24 13:44:42 +02001994 self._UpdateHooks()
1995
1996 m = self.manifest.manifestProject.config
1997 for key in ['user.name', 'user.email']:
1998 if m.Has(key, include_defaults = False):
1999 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002000 if self.manifest.IsMirror:
2001 self.config.SetString('core.bare', 'true')
2002 else:
2003 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002004
Jimmie Westera0444582012-10-24 13:44:42 +02002005 def _UpdateHooks(self):
2006 if os.path.exists(self.gitdir):
2007 # Always recreate hooks since they can have been changed
2008 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002009 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002010 try:
2011 to_rm = os.listdir(hooks)
2012 except OSError:
2013 to_rm = []
2014 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002015 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002016 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002017
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002018 def _InitHooks(self):
2019 hooks = self._gitdir_path('hooks')
2020 if not os.path.exists(hooks):
2021 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002022 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002023 name = os.path.basename(stock_hook)
2024
Victor Boivie65e0f352011-04-18 11:23:29 +02002025 if name in ('commit-msg',) and not self.remote.review \
2026 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002027 # Don't install a Gerrit Code Review hook if this
2028 # project does not appear to use it for reviews.
2029 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002030 # Since the manifest project is one of those, but also
2031 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002032 continue
2033
2034 dst = os.path.join(hooks, name)
2035 if os.path.islink(dst):
2036 continue
2037 if os.path.exists(dst):
2038 if filecmp.cmp(stock_hook, dst, shallow=False):
2039 os.remove(dst)
2040 else:
2041 _error("%s: Not replacing %s hook", self.relpath, name)
2042 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002043 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002044 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002045 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002046 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002047 raise GitError('filesystem must support symlinks')
2048 else:
2049 raise
2050
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002051 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002052 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002053 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002054 remote.url = self.remote.url
2055 remote.review = self.remote.review
2056 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002057
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002058 if self.worktree:
2059 remote.ResetFetch(mirror=False)
2060 else:
2061 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002062 remote.Save()
2063
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002064 def _InitMRef(self):
2065 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002066 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002067
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002068 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002069 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002070
2071 def _InitAnyMRef(self, ref):
2072 cur = self.bare_ref.symref(ref)
2073
2074 if self.revisionId:
2075 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2076 msg = 'manifest set to %s' % self.revisionId
2077 dst = self.revisionId + '^0'
2078 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2079 else:
2080 remote = self.GetRemote(self.remote.name)
2081 dst = remote.ToLocal(self.revisionExpr)
2082 if cur != dst:
2083 msg = 'manifest set to %s' % self.revisionExpr
2084 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002085
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002086 def _InitWorkTree(self):
2087 dotgit = os.path.join(self.worktree, '.git')
2088 if not os.path.exists(dotgit):
2089 os.makedirs(dotgit)
2090
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002091 for name in ['config',
2092 'description',
2093 'hooks',
2094 'info',
2095 'logs',
2096 'objects',
2097 'packed-refs',
2098 'refs',
2099 'rr-cache',
2100 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08002101 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002102 src = os.path.join(self.gitdir, name)
2103 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08002104 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002105 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08002106 else:
2107 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07002108 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08002109 if e.errno == errno.EPERM:
2110 raise GitError('filesystem must support symlinks')
2111 else:
2112 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002113
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002114 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002115
2116 cmd = ['read-tree', '--reset', '-u']
2117 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002118 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002119 if GitCommand(self, cmd).Wait() != 0:
2120 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002121
2122 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2123 if not os.path.exists(rr_cache):
2124 os.makedirs(rr_cache)
2125
Shawn O. Pearce93609662009-04-21 10:50:33 -07002126 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002127
2128 def _gitdir_path(self, path):
2129 return os.path.join(self.gitdir, path)
2130
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002131 def _revlist(self, *args, **kw):
2132 a = []
2133 a.extend(args)
2134 a.append('--')
2135 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002136
2137 @property
2138 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002139 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002140
2141 class _GitGetByExec(object):
2142 def __init__(self, project, bare):
2143 self._project = project
2144 self._bare = bare
2145
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002146 def LsOthers(self):
2147 p = GitCommand(self._project,
2148 ['ls-files',
2149 '-z',
2150 '--others',
2151 '--exclude-standard'],
2152 bare = False,
2153 capture_stdout = True,
2154 capture_stderr = True)
2155 if p.Wait() == 0:
2156 out = p.stdout
2157 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002158 return out[:-1].split('\0') # pylint: disable=W1401
2159 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002160 return []
2161
2162 def DiffZ(self, name, *args):
2163 cmd = [name]
2164 cmd.append('-z')
2165 cmd.extend(args)
2166 p = GitCommand(self._project,
2167 cmd,
2168 bare = False,
2169 capture_stdout = True,
2170 capture_stderr = True)
2171 try:
2172 out = p.process.stdout.read()
2173 r = {}
2174 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002175 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002176 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002177 try:
2178 info = out.next()
2179 path = out.next()
2180 except StopIteration:
2181 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002182
2183 class _Info(object):
2184 def __init__(self, path, omode, nmode, oid, nid, state):
2185 self.path = path
2186 self.src_path = None
2187 self.old_mode = omode
2188 self.new_mode = nmode
2189 self.old_id = oid
2190 self.new_id = nid
2191
2192 if len(state) == 1:
2193 self.status = state
2194 self.level = None
2195 else:
2196 self.status = state[:1]
2197 self.level = state[1:]
2198 while self.level.startswith('0'):
2199 self.level = self.level[1:]
2200
2201 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002202 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002203 if info.status in ('R', 'C'):
2204 info.src_path = info.path
2205 info.path = out.next()
2206 r[info.path] = info
2207 return r
2208 finally:
2209 p.Wait()
2210
2211 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002212 if self._bare:
2213 path = os.path.join(self._project.gitdir, HEAD)
2214 else:
2215 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002216 try:
2217 fd = open(path, 'rb')
2218 except IOError:
2219 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002220 try:
2221 line = fd.read()
2222 finally:
2223 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302224 try:
2225 line = line.decode()
2226 except AttributeError:
2227 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002228 if line.startswith('ref: '):
2229 return line[5:-1]
2230 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002231
2232 def SetHead(self, ref, message=None):
2233 cmdv = []
2234 if message is not None:
2235 cmdv.extend(['-m', message])
2236 cmdv.append(HEAD)
2237 cmdv.append(ref)
2238 self.symbolic_ref(*cmdv)
2239
2240 def DetachHead(self, new, message=None):
2241 cmdv = ['--no-deref']
2242 if message is not None:
2243 cmdv.extend(['-m', message])
2244 cmdv.append(HEAD)
2245 cmdv.append(new)
2246 self.update_ref(*cmdv)
2247
2248 def UpdateRef(self, name, new, old=None,
2249 message=None,
2250 detach=False):
2251 cmdv = []
2252 if message is not None:
2253 cmdv.extend(['-m', message])
2254 if detach:
2255 cmdv.append('--no-deref')
2256 cmdv.append(name)
2257 cmdv.append(new)
2258 if old is not None:
2259 cmdv.append(old)
2260 self.update_ref(*cmdv)
2261
2262 def DeleteRef(self, name, old=None):
2263 if not old:
2264 old = self.rev_parse(name)
2265 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002266 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002267
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002268 def rev_list(self, *args, **kw):
2269 if 'format' in kw:
2270 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2271 else:
2272 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002273 cmdv.extend(args)
2274 p = GitCommand(self._project,
2275 cmdv,
2276 bare = self._bare,
2277 capture_stdout = True,
2278 capture_stderr = True)
2279 r = []
2280 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002281 if line[-1] == '\n':
2282 line = line[:-1]
2283 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002284 if p.Wait() != 0:
2285 raise GitError('%s rev-list %s: %s' % (
2286 self._project.name,
2287 str(args),
2288 p.stderr))
2289 return r
2290
2291 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002292 """Allow arbitrary git commands using pythonic syntax.
2293
2294 This allows you to do things like:
2295 git_obj.rev_parse('HEAD')
2296
2297 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2298 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002299 Any other positional arguments will be passed to the git command, and the
2300 following keyword arguments are supported:
2301 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002302
2303 Args:
2304 name: The name of the git command to call. Any '_' characters will
2305 be replaced with '-'.
2306
2307 Returns:
2308 A callable object that will try to call git with the named command.
2309 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002310 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002311 def runner(*args, **kwargs):
2312 cmdv = []
2313 config = kwargs.pop('config', None)
2314 for k in kwargs:
2315 raise TypeError('%s() got an unexpected keyword argument %r'
2316 % (name, k))
2317 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002318 if not git_require((1, 7, 2)):
2319 raise ValueError('cannot set config on command line for %s()'
2320 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302321 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002322 cmdv.append('-c')
2323 cmdv.append('%s=%s' % (k, v))
2324 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002325 cmdv.extend(args)
2326 p = GitCommand(self._project,
2327 cmdv,
2328 bare = self._bare,
2329 capture_stdout = True,
2330 capture_stderr = True)
2331 if p.Wait() != 0:
2332 raise GitError('%s %s: %s' % (
2333 self._project.name,
2334 name,
2335 p.stderr))
2336 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302337 try:
Conley Owensedd01512013-09-26 12:59:58 -07002338 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302339 except AttributeError:
2340 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002341 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2342 return r[:-1]
2343 return r
2344 return runner
2345
2346
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002347class _PriorSyncFailedError(Exception):
2348 def __str__(self):
2349 return 'prior sync failed; rebase still in progress'
2350
2351class _DirtyError(Exception):
2352 def __str__(self):
2353 return 'contains uncommitted changes'
2354
2355class _InfoMessage(object):
2356 def __init__(self, project, text):
2357 self.project = project
2358 self.text = text
2359
2360 def Print(self, syncbuf):
2361 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2362 syncbuf.out.nl()
2363
2364class _Failure(object):
2365 def __init__(self, project, why):
2366 self.project = project
2367 self.why = why
2368
2369 def Print(self, syncbuf):
2370 syncbuf.out.fail('error: %s/: %s',
2371 self.project.relpath,
2372 str(self.why))
2373 syncbuf.out.nl()
2374
2375class _Later(object):
2376 def __init__(self, project, action):
2377 self.project = project
2378 self.action = action
2379
2380 def Run(self, syncbuf):
2381 out = syncbuf.out
2382 out.project('project %s/', self.project.relpath)
2383 out.nl()
2384 try:
2385 self.action()
2386 out.nl()
2387 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002388 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002389 out.nl()
2390 return False
2391
2392class _SyncColoring(Coloring):
2393 def __init__(self, config):
2394 Coloring.__init__(self, config, 'reposync')
2395 self.project = self.printer('header', attr = 'bold')
2396 self.info = self.printer('info')
2397 self.fail = self.printer('fail', fg='red')
2398
2399class SyncBuffer(object):
2400 def __init__(self, config, detach_head=False):
2401 self._messages = []
2402 self._failures = []
2403 self._later_queue1 = []
2404 self._later_queue2 = []
2405
2406 self.out = _SyncColoring(config)
2407 self.out.redirect(sys.stderr)
2408
2409 self.detach_head = detach_head
2410 self.clean = True
2411
2412 def info(self, project, fmt, *args):
2413 self._messages.append(_InfoMessage(project, fmt % args))
2414
2415 def fail(self, project, err=None):
2416 self._failures.append(_Failure(project, err))
2417 self.clean = False
2418
2419 def later1(self, project, what):
2420 self._later_queue1.append(_Later(project, what))
2421
2422 def later2(self, project, what):
2423 self._later_queue2.append(_Later(project, what))
2424
2425 def Finish(self):
2426 self._PrintMessages()
2427 self._RunLater()
2428 self._PrintMessages()
2429 return self.clean
2430
2431 def _RunLater(self):
2432 for q in ['_later_queue1', '_later_queue2']:
2433 if not self._RunQueue(q):
2434 return
2435
2436 def _RunQueue(self, queue):
2437 for m in getattr(self, queue):
2438 if not m.Run(self):
2439 self.clean = False
2440 return False
2441 setattr(self, queue, [])
2442 return True
2443
2444 def _PrintMessages(self):
2445 for m in self._messages:
2446 m.Print(self)
2447 for m in self._failures:
2448 m.Print(self)
2449
2450 self._messages = []
2451 self._failures = []
2452
2453
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002454class MetaProject(Project):
2455 """A special project housed under .repo.
2456 """
2457 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002458 Project.__init__(self,
2459 manifest = manifest,
2460 name = name,
2461 gitdir = gitdir,
2462 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002463 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002464 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002465 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002466 revisionId = None,
2467 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002468
2469 def PreSync(self):
2470 if self.Exists:
2471 cb = self.CurrentBranch
2472 if cb:
2473 base = self.GetBranch(cb).merge
2474 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002475 self.revisionExpr = base
2476 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002477
Florian Vallee5d016502012-06-07 17:19:26 +02002478 def MetaBranchSwitch(self, target):
2479 """ Prepare MetaProject for manifest branch switch
2480 """
2481
2482 # detach and delete manifest branch, allowing a new
2483 # branch to take over
2484 syncbuf = SyncBuffer(self.config, detach_head = True)
2485 self.Sync_LocalHalf(syncbuf)
2486 syncbuf.Finish()
2487
2488 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002489 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002490 capture_stdout = True,
2491 capture_stderr = True).Wait() == 0
2492
2493
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002494 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002495 def LastFetch(self):
2496 try:
2497 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2498 return os.path.getmtime(fh)
2499 except OSError:
2500 return 0
2501
2502 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002503 def HasChanges(self):
2504 """Has the remote received new commits not yet checked out?
2505 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002506 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002507 return False
2508
David Pursehouse8a68ff92012-09-24 12:15:13 +09002509 all_refs = self.bare_ref.all
2510 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002511 head = self.work_git.GetHead()
2512 if head.startswith(R_HEADS):
2513 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002514 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002515 except KeyError:
2516 head = None
2517
2518 if revid == head:
2519 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002520 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002521 return True
2522 return False