blob: f9f1f75deea8530f6e6b3a0c049ce5ae351bf3c6 [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
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080026import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070027import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070028
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070029from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070030from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070031from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090032from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080033from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080034from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070035from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036
Shawn O. Pearced237b692009-04-17 18:49:50 -070037from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070038
David Pursehouse59bbb582013-05-17 10:49:33 +090039from pyversion import is_python3
40if not is_python3():
41 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053042 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090043 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053044
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070045def _lwrite(path, content):
46 lock = '%s.lock' % path
47
48 fd = open(lock, 'wb')
49 try:
50 fd.write(content)
51 finally:
52 fd.close()
53
54 try:
55 os.rename(lock, path)
56 except OSError:
57 os.remove(lock)
58 raise
59
Shawn O. Pearce48244782009-04-16 08:25:57 -070060def _error(fmt, *args):
61 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070062 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070063
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070064def not_rev(r):
65 return '^' + r
66
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080067def sq(r):
68 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080069
Doug Anderson8ced8642011-01-10 14:16:30 -080070_project_hook_list = None
71def _ProjectHooks():
72 """List the hooks present in the 'hooks' directory.
73
74 These hooks are project hooks and are copied to the '.git/hooks' directory
75 of all subprojects.
76
77 This function caches the list of hooks (based on the contents of the
78 'repo/hooks' directory) on the first call.
79
80 Returns:
81 A list of absolute paths to all of the files in the hooks directory.
82 """
83 global _project_hook_list
84 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080085 d = os.path.abspath(os.path.dirname(__file__))
86 d = os.path.join(d , 'hooks')
Chirayu Desai217ea7d2013-03-01 19:14:38 +053087 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
Doug Anderson8ced8642011-01-10 14:16:30 -080088 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080089
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080090
Shawn O. Pearce632768b2008-10-23 11:58:52 -070091class DownloadedChange(object):
92 _commit_cache = None
93
94 def __init__(self, project, base, change_id, ps_id, commit):
95 self.project = project
96 self.base = base
97 self.change_id = change_id
98 self.ps_id = ps_id
99 self.commit = commit
100
101 @property
102 def commits(self):
103 if self._commit_cache is None:
104 self._commit_cache = self.project.bare_git.rev_list(
105 '--abbrev=8',
106 '--abbrev-commit',
107 '--pretty=oneline',
108 '--reverse',
109 '--date-order',
110 not_rev(self.base),
111 self.commit,
112 '--')
113 return self._commit_cache
114
115
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700116class ReviewableBranch(object):
117 _commit_cache = None
118
119 def __init__(self, project, branch, base):
120 self.project = project
121 self.branch = branch
122 self.base = base
123
124 @property
125 def name(self):
126 return self.branch.name
127
128 @property
129 def commits(self):
130 if self._commit_cache is None:
131 self._commit_cache = self.project.bare_git.rev_list(
132 '--abbrev=8',
133 '--abbrev-commit',
134 '--pretty=oneline',
135 '--reverse',
136 '--date-order',
137 not_rev(self.base),
138 R_HEADS + self.name,
139 '--')
140 return self._commit_cache
141
142 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800143 def unabbrev_commits(self):
144 r = dict()
145 for commit in self.project.bare_git.rev_list(
146 not_rev(self.base),
147 R_HEADS + self.name,
148 '--'):
149 r[commit[0:8]] = commit
150 return r
151
152 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700153 def date(self):
154 return self.project.bare_git.log(
155 '--pretty=format:%cd',
156 '-n', '1',
157 R_HEADS + self.name,
158 '--')
159
Bryan Jacobsf609f912013-05-06 13:36:24 -0400160 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800161 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700162 people,
Brian Harring435370c2012-07-28 15:37:04 -0700163 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400164 draft=draft,
165 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700166
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700167 def GetPublishedRefs(self):
168 refs = {}
169 output = self.project.bare_git.ls_remote(
170 self.branch.remote.SshReviewUrl(self.project.UserEmail),
171 'refs/changes/*')
172 for line in output.split('\n'):
173 try:
174 (sha, ref) = line.split()
175 refs[sha] = ref
176 except ValueError:
177 pass
178
179 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700180
181class StatusColoring(Coloring):
182 def __init__(self, config):
183 Coloring.__init__(self, config, 'status')
184 self.project = self.printer('header', attr = 'bold')
185 self.branch = self.printer('header', attr = 'bold')
186 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700187 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700188
189 self.added = self.printer('added', fg = 'green')
190 self.changed = self.printer('changed', fg = 'red')
191 self.untracked = self.printer('untracked', fg = 'red')
192
193
194class DiffColoring(Coloring):
195 def __init__(self, config):
196 Coloring.__init__(self, config, 'diff')
197 self.project = self.printer('header', attr = 'bold')
198
James W. Mills24c13082012-04-12 15:04:13 -0500199class _Annotation:
200 def __init__(self, name, value, keep):
201 self.name = name
202 self.value = value
203 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204
205class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800206 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207 self.src = src
208 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800209 self.abs_src = abssrc
210 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211
212 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800213 src = self.abs_src
214 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700215 # copy file if it does not exist or is out of date
216 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
217 try:
218 # remove existing file first, since it might be read-only
219 if os.path.exists(dest):
220 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400221 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200222 dest_dir = os.path.dirname(dest)
223 if not os.path.isdir(dest_dir):
224 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225 shutil.copy(src, dest)
226 # make the file read-only
227 mode = os.stat(dest)[stat.ST_MODE]
228 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
229 os.chmod(dest, mode)
230 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700231 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700233class RemoteSpec(object):
234 def __init__(self,
235 name,
236 url = None,
237 review = None):
238 self.name = name
239 self.url = url
240 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241
Doug Anderson37282b42011-03-04 11:54:18 -0800242class RepoHook(object):
243 """A RepoHook contains information about a script to run as a hook.
244
245 Hooks are used to run a python script before running an upload (for instance,
246 to run presubmit checks). Eventually, we may have hooks for other actions.
247
248 This shouldn't be confused with files in the 'repo/hooks' directory. Those
249 files are copied into each '.git/hooks' folder for each project. Repo-level
250 hooks are associated instead with repo actions.
251
252 Hooks are always python. When a hook is run, we will load the hook into the
253 interpreter and execute its main() function.
254 """
255 def __init__(self,
256 hook_type,
257 hooks_project,
258 topdir,
259 abort_if_user_denies=False):
260 """RepoHook constructor.
261
262 Params:
263 hook_type: A string representing the type of hook. This is also used
264 to figure out the name of the file containing the hook. For
265 example: 'pre-upload'.
266 hooks_project: The project containing the repo hooks. If you have a
267 manifest, this is manifest.repo_hooks_project. OK if this is None,
268 which will make the hook a no-op.
269 topdir: Repo's top directory (the one containing the .repo directory).
270 Scripts will run with CWD as this directory. If you have a manifest,
271 this is manifest.topdir
272 abort_if_user_denies: If True, we'll throw a HookError() if the user
273 doesn't allow us to run the hook.
274 """
275 self._hook_type = hook_type
276 self._hooks_project = hooks_project
277 self._topdir = topdir
278 self._abort_if_user_denies = abort_if_user_denies
279
280 # Store the full path to the script for convenience.
281 if self._hooks_project:
282 self._script_fullpath = os.path.join(self._hooks_project.worktree,
283 self._hook_type + '.py')
284 else:
285 self._script_fullpath = None
286
287 def _GetHash(self):
288 """Return a hash of the contents of the hooks directory.
289
290 We'll just use git to do this. This hash has the property that if anything
291 changes in the directory we will return a different has.
292
293 SECURITY CONSIDERATION:
294 This hash only represents the contents of files in the hook directory, not
295 any other files imported or called by hooks. Changes to imported files
296 can change the script behavior without affecting the hash.
297
298 Returns:
299 A string representing the hash. This will always be ASCII so that it can
300 be printed to the user easily.
301 """
302 assert self._hooks_project, "Must have hooks to calculate their hash."
303
304 # We will use the work_git object rather than just calling GetRevisionId().
305 # That gives us a hash of the latest checked in version of the files that
306 # the user will actually be executing. Specifically, GetRevisionId()
307 # doesn't appear to change even if a user checks out a different version
308 # of the hooks repo (via git checkout) nor if a user commits their own revs.
309 #
310 # NOTE: Local (non-committed) changes will not be factored into this hash.
311 # I think this is OK, since we're really only worried about warning the user
312 # about upstream changes.
313 return self._hooks_project.work_git.rev_parse('HEAD')
314
315 def _GetMustVerb(self):
316 """Return 'must' if the hook is required; 'should' if not."""
317 if self._abort_if_user_denies:
318 return 'must'
319 else:
320 return 'should'
321
322 def _CheckForHookApproval(self):
323 """Check to see whether this hook has been approved.
324
325 We'll look at the hash of all of the hooks. If this matches the hash that
326 the user last approved, we're done. If it doesn't, we'll ask the user
327 about approval.
328
329 Note that we ask permission for each individual hook even though we use
330 the hash of all hooks when detecting changes. We'd like the user to be
331 able to approve / deny each hook individually. We only use the hash of all
332 hooks because there is no other easy way to detect changes to local imports.
333
334 Returns:
335 True if this hook is approved to run; False otherwise.
336
337 Raises:
338 HookError: Raised if the user doesn't approve and abort_if_user_denies
339 was passed to the consturctor.
340 """
Doug Anderson37282b42011-03-04 11:54:18 -0800341 hooks_config = self._hooks_project.config
342 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
343
344 # Get the last hash that the user approved for this hook; may be None.
345 old_hash = hooks_config.GetString(git_approval_key)
346
347 # Get the current hash so we can tell if scripts changed since approval.
348 new_hash = self._GetHash()
349
350 if old_hash is not None:
351 # User previously approved hook and asked not to be prompted again.
352 if new_hash == old_hash:
353 # Approval matched. We're done.
354 return True
355 else:
356 # Give the user a reason why we're prompting, since they last told
357 # us to "never ask again".
358 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
359 self._hook_type)
360 else:
361 prompt = ''
362
363 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
364 if sys.stdout.isatty():
365 prompt += ('Repo %s run the script:\n'
366 ' %s\n'
367 '\n'
368 'Do you want to allow this script to run '
369 '(yes/yes-never-ask-again/NO)? ') % (
370 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530371 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900372 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800373
374 # User is doing a one-time approval.
375 if response in ('y', 'yes'):
376 return True
377 elif response == 'yes-never-ask-again':
378 hooks_config.SetString(git_approval_key, new_hash)
379 return True
380
381 # For anything else, we'll assume no approval.
382 if self._abort_if_user_denies:
383 raise HookError('You must allow the %s hook or use --no-verify.' %
384 self._hook_type)
385
386 return False
387
388 def _ExecuteHook(self, **kwargs):
389 """Actually execute the given hook.
390
391 This will run the hook's 'main' function in our python interpreter.
392
393 Args:
394 kwargs: Keyword arguments to pass to the hook. These are often specific
395 to the hook type. For instance, pre-upload hooks will contain
396 a project_list.
397 """
398 # Keep sys.path and CWD stashed away so that we can always restore them
399 # upon function exit.
400 orig_path = os.getcwd()
401 orig_syspath = sys.path
402
403 try:
404 # Always run hooks with CWD as topdir.
405 os.chdir(self._topdir)
406
407 # Put the hook dir as the first item of sys.path so hooks can do
408 # relative imports. We want to replace the repo dir as [0] so
409 # hooks can't import repo files.
410 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
411
412 # Exec, storing global context in the context dict. We catch exceptions
413 # and convert to a HookError w/ just the failing traceback.
414 context = {}
415 try:
416 execfile(self._script_fullpath, context)
417 except Exception:
418 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
419 traceback.format_exc(), self._hook_type))
420
421 # Running the script should have defined a main() function.
422 if 'main' not in context:
423 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
424
425
426 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
427 # We don't actually want hooks to define their main with this argument--
428 # it's there to remind them that their hook should always take **kwargs.
429 # For instance, a pre-upload hook should be defined like:
430 # def main(project_list, **kwargs):
431 #
432 # This allows us to later expand the API without breaking old hooks.
433 kwargs = kwargs.copy()
434 kwargs['hook_should_take_kwargs'] = True
435
436 # Call the main function in the hook. If the hook should cause the
437 # build to fail, it will raise an Exception. We'll catch that convert
438 # to a HookError w/ just the failing traceback.
439 try:
440 context['main'](**kwargs)
441 except Exception:
442 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
443 'above.' % (
444 traceback.format_exc(), self._hook_type))
445 finally:
446 # Restore sys.path and CWD.
447 sys.path = orig_syspath
448 os.chdir(orig_path)
449
450 def Run(self, user_allows_all_hooks, **kwargs):
451 """Run the hook.
452
453 If the hook doesn't exist (because there is no hooks project or because
454 this particular hook is not enabled), this is a no-op.
455
456 Args:
457 user_allows_all_hooks: If True, we will never prompt about running the
458 hook--we'll just assume it's OK to run it.
459 kwargs: Keyword arguments to pass to the hook. These are often specific
460 to the hook type. For instance, pre-upload hooks will contain
461 a project_list.
462
463 Raises:
464 HookError: If there was a problem finding the hook or the user declined
465 to run a required hook (from _CheckForHookApproval).
466 """
467 # No-op if there is no hooks project or if hook is disabled.
468 if ((not self._hooks_project) or
469 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
470 return
471
472 # Bail with a nice error if we can't find the hook.
473 if not os.path.isfile(self._script_fullpath):
474 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
475
476 # Make sure the user is OK with running the hook.
477 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
478 return
479
480 # Run the hook with the same version of python we're using.
481 self._ExecuteHook(**kwargs)
482
483
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700484class Project(object):
485 def __init__(self,
486 manifest,
487 name,
488 remote,
489 gitdir,
David James8d201162013-10-11 17:03:19 -0700490 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700491 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.
David James8d201162013-10-11 17:03:19 -0700511 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800512 worktree: Absolute path of git working tree.
513 relpath: Relative path of git working tree to repo's top directory.
514 revisionExpr: The `revision` attribute of manifest.xml's project element.
515 revisionId: git commit id for checking out.
516 rebase: The `rebase` attribute of manifest.xml's project element.
517 groups: The `groups` attribute of manifest.xml's project element.
518 sync_c: The `sync-c` attribute of manifest.xml's project element.
519 sync_s: The `sync-s` attribute of manifest.xml's project element.
520 upstream: The `upstream` attribute of manifest.xml's project element.
521 parent: The parent Project object.
522 is_derived: False if the project was explicitly defined in the manifest;
523 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400524 dest_branch: The branch to which to push changes for review by default.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800525 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700526 self.manifest = manifest
527 self.name = name
528 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800529 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700530 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800531 if worktree:
532 self.worktree = worktree.replace('\\', '/')
533 else:
534 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700535 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700536 self.revisionExpr = revisionExpr
537
538 if revisionId is None \
539 and revisionExpr \
540 and IsId(revisionExpr):
541 self.revisionId = revisionExpr
542 else:
543 self.revisionId = revisionId
544
Mike Pontillod3153822012-02-28 11:53:24 -0800545 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700546 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700547 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800548 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900549 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700550 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800551 self.parent = parent
552 self.is_derived = is_derived
553 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800554
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700555 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700556 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500557 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700558 self.config = GitConfig.ForRepository(
559 gitdir = self.gitdir,
560 defaults = self.manifest.globalConfig)
561
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800562 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700563 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800564 else:
565 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700566 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700567 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700568 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400569 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700570
Doug Anderson37282b42011-03-04 11:54:18 -0800571 # This will be filled in if a project is later identified to be the
572 # project containing repo hooks.
573 self.enabled_repo_hooks = []
574
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700575 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800576 def Derived(self):
577 return self.is_derived
578
579 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700580 def Exists(self):
581 return os.path.isdir(self.gitdir)
582
583 @property
584 def CurrentBranch(self):
585 """Obtain the name of the currently checked out branch.
586 The branch name omits the 'refs/heads/' prefix.
587 None is returned if the project is on a detached HEAD.
588 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700589 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700590 if b.startswith(R_HEADS):
591 return b[len(R_HEADS):]
592 return None
593
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700594 def IsRebaseInProgress(self):
595 w = self.worktree
596 g = os.path.join(w, '.git')
597 return os.path.exists(os.path.join(g, 'rebase-apply')) \
598 or os.path.exists(os.path.join(g, 'rebase-merge')) \
599 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200600
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700601 def IsDirty(self, consider_untracked=True):
602 """Is the working directory modified in some way?
603 """
604 self.work_git.update_index('-q',
605 '--unmerged',
606 '--ignore-missing',
607 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900608 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700609 return True
610 if self.work_git.DiffZ('diff-files'):
611 return True
612 if consider_untracked and self.work_git.LsOthers():
613 return True
614 return False
615
616 _userident_name = None
617 _userident_email = None
618
619 @property
620 def UserName(self):
621 """Obtain the user's personal name.
622 """
623 if self._userident_name is None:
624 self._LoadUserIdentity()
625 return self._userident_name
626
627 @property
628 def UserEmail(self):
629 """Obtain the user's email address. This is very likely
630 to be their Gerrit login.
631 """
632 if self._userident_email is None:
633 self._LoadUserIdentity()
634 return self._userident_email
635
636 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900637 u = self.bare_git.var('GIT_COMMITTER_IDENT')
638 m = re.compile("^(.*) <([^>]*)> ").match(u)
639 if m:
640 self._userident_name = m.group(1)
641 self._userident_email = m.group(2)
642 else:
643 self._userident_name = ''
644 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645
646 def GetRemote(self, name):
647 """Get the configuration for a single remote.
648 """
649 return self.config.GetRemote(name)
650
651 def GetBranch(self, name):
652 """Get the configuration for a single branch.
653 """
654 return self.config.GetBranch(name)
655
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700656 def GetBranches(self):
657 """Get all existing local branches.
658 """
659 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900660 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700661 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700662
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530663 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700664 if name.startswith(R_HEADS):
665 name = name[len(R_HEADS):]
666 b = self.GetBranch(name)
667 b.current = name == current
668 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900669 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700670 heads[name] = b
671
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530672 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700673 if name.startswith(R_PUB):
674 name = name[len(R_PUB):]
675 b = heads.get(name)
676 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900677 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700678
679 return heads
680
Colin Cross5acde752012-03-28 20:15:45 -0700681 def MatchesGroups(self, manifest_groups):
682 """Returns true if the manifest groups specified at init should cause
683 this project to be synced.
684 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700685 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700686
687 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700688 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700689 manifest_groups: "-group1,group2"
690 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500691
692 The special manifest group "default" will match any project that
693 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700694 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500695 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700696 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500697 if not 'notdefault' in expanded_project_groups:
698 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700699
Conley Owens971de8e2012-04-16 10:36:08 -0700700 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700701 for group in expanded_manifest_groups:
702 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700703 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700704 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700705 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700706
Conley Owens971de8e2012-04-16 10:36:08 -0700707 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708
709## Status Display ##
710
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500711 def HasChanges(self):
712 """Returns true if there are uncommitted changes.
713 """
714 self.work_git.update_index('-q',
715 '--unmerged',
716 '--ignore-missing',
717 '--refresh')
718 if self.IsRebaseInProgress():
719 return True
720
721 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
722 return True
723
724 if self.work_git.DiffZ('diff-files'):
725 return True
726
727 if self.work_git.LsOthers():
728 return True
729
730 return False
731
Terence Haddock4655e812011-03-31 12:33:34 +0200732 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200734
735 Args:
736 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737 """
738 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200739 if output_redir == None:
740 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700741 print(file=output_redir)
742 print('project %s/' % self.relpath, file=output_redir)
743 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744 return
745
746 self.work_git.update_index('-q',
747 '--unmerged',
748 '--ignore-missing',
749 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700750 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
752 df = self.work_git.DiffZ('diff-files')
753 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100754 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700755 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700756
757 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200758 if not output_redir == None:
759 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700760 out.project('project %-40s', self.relpath + '/')
761
762 branch = self.CurrentBranch
763 if branch is None:
764 out.nobranch('(*** NO BRANCH ***)')
765 else:
766 out.branch('branch %s', branch)
767 out.nl()
768
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700769 if rb:
770 out.important('prior sync failed; rebase still in progress')
771 out.nl()
772
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700773 paths = list()
774 paths.extend(di.keys())
775 paths.extend(df.keys())
776 paths.extend(do)
777
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530778 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900779 try:
780 i = di[p]
781 except KeyError:
782 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700783
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900784 try:
785 f = df[p]
786 except KeyError:
787 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200788
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900789 if i:
790 i_status = i.status.upper()
791 else:
792 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700793
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900794 if f:
795 f_status = f.status.lower()
796 else:
797 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700798
799 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800800 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700801 i.src_path, p, i.level)
802 else:
803 line = ' %s%s\t%s' % (i_status, f_status, p)
804
805 if i and not f:
806 out.added('%s', line)
807 elif (i and f) or (not i and f):
808 out.changed('%s', line)
809 elif not i and not f:
810 out.untracked('%s', line)
811 else:
812 out.write('%s', line)
813 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200814
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700815 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816
pelyad67872d2012-03-28 14:49:58 +0300817 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700818 """Prints the status of the repository to stdout.
819 """
820 out = DiffColoring(self.config)
821 cmd = ['diff']
822 if out.is_on:
823 cmd.append('--color')
824 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300825 if absolute_paths:
826 cmd.append('--src-prefix=a/%s/' % self.relpath)
827 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828 cmd.append('--')
829 p = GitCommand(self,
830 cmd,
831 capture_stdout = True,
832 capture_stderr = True)
833 has_diff = False
834 for line in p.process.stdout:
835 if not has_diff:
836 out.nl()
837 out.project('project %s/' % self.relpath)
838 out.nl()
839 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700840 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841 p.Wait()
842
843
844## Publish / Upload ##
845
David Pursehouse8a68ff92012-09-24 12:15:13 +0900846 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700847 """Was the branch published (uploaded) for code review?
848 If so, returns the SHA-1 hash of the last published
849 state for the branch.
850 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700851 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900852 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700853 try:
854 return self.bare_git.rev_parse(key)
855 except GitError:
856 return None
857 else:
858 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900859 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700860 except KeyError:
861 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862
David Pursehouse8a68ff92012-09-24 12:15:13 +0900863 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700864 """Prunes any stale published refs.
865 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900866 if all_refs is None:
867 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868 heads = set()
869 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530870 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871 if name.startswith(R_HEADS):
872 heads.add(name)
873 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900874 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700875
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530876 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877 n = name[len(R_PUB):]
878 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900879 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700880
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700881 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700882 """List any branches which can be uploaded for review.
883 """
884 heads = {}
885 pubed = {}
886
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530887 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900889 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900891 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892
893 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530894 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900895 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700897 if selected_branch and branch != selected_branch:
898 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800900 rb = self.GetUploadableBranch(branch)
901 if rb:
902 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700903 return ready
904
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800905 def GetUploadableBranch(self, branch_name):
906 """Get a single uploadable branch, or None.
907 """
908 branch = self.GetBranch(branch_name)
909 base = branch.LocalMerge
910 if branch.LocalMerge:
911 rb = ReviewableBranch(self, branch, base)
912 if rb.commits:
913 return rb
914 return None
915
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700916 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700917 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700918 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400919 draft=False,
920 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921 """Uploads the named branch for code review.
922 """
923 if branch is None:
924 branch = self.CurrentBranch
925 if branch is None:
926 raise GitError('not currently on a branch')
927
928 branch = self.GetBranch(branch)
929 if not branch.LocalMerge:
930 raise GitError('branch %s does not track a remote' % branch.name)
931 if not branch.remote.review:
932 raise GitError('remote %s has no review url' % branch.remote.name)
933
Bryan Jacobsf609f912013-05-06 13:36:24 -0400934 if dest_branch is None:
935 dest_branch = self.dest_branch
936 if dest_branch is None:
937 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700938 if not dest_branch.startswith(R_HEADS):
939 dest_branch = R_HEADS + dest_branch
940
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800941 if not branch.remote.projectname:
942 branch.remote.projectname = self.name
943 branch.remote.Save()
944
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800945 url = branch.remote.ReviewUrl(self.UserEmail)
946 if url is None:
947 raise UploadError('review not configured')
948 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800949
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800950 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800951 rp = ['gerrit receive-pack']
952 for e in people[0]:
953 rp.append('--reviewer=%s' % sq(e))
954 for e in people[1]:
955 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800956 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700957
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800958 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800959
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800960 if dest_branch.startswith(R_HEADS):
961 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700962
963 upload_type = 'for'
964 if draft:
965 upload_type = 'drafts'
966
967 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
968 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800969 if auto_topic:
970 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800971 if not url.startswith('ssh://'):
972 rp = ['r=%s' % p for p in people[0]] + \
973 ['cc=%s' % p for p in people[1]]
974 if rp:
975 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800976 cmd.append(ref_spec)
977
978 if GitCommand(self, cmd, bare = True).Wait() != 0:
979 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980
981 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
982 self.bare_git.UpdateRef(R_PUB + branch.name,
983 R_HEADS + branch.name,
984 message = msg)
985
986
987## Sync ##
988
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700989 def Sync_NetworkHalf(self,
990 quiet=False,
991 is_new=None,
992 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700993 clone_bundle=True,
994 no_tags=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700995 """Perform only the network IO portion of the sync process.
996 Local working directory/branch state is not affected.
997 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700998 if is_new is None:
999 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001000 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001002 else:
1003 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001004 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001005
1006 if is_new:
1007 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1008 try:
1009 fd = open(alt, 'rb')
1010 try:
1011 alt_dir = fd.readline().rstrip()
1012 finally:
1013 fd.close()
1014 except IOError:
1015 alt_dir = None
1016 else:
1017 alt_dir = None
1018
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001019 if clone_bundle \
1020 and alt_dir is None \
1021 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001022 is_new = False
1023
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001024 if not current_branch_only:
1025 if self.sync_c:
1026 current_branch_only = True
1027 elif not self.manifest._loaded:
1028 # Manifest cannot check defaults until it syncs.
1029 current_branch_only = False
1030 elif self.manifest.default.sync_c:
1031 current_branch_only = True
1032
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001033 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001034 current_branch_only=current_branch_only,
1035 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001037
1038 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001039 self._InitMRef()
1040 else:
1041 self._InitMirrorHead()
1042 try:
1043 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1044 except OSError:
1045 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001046 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001047
1048 def PostRepoUpgrade(self):
1049 self._InitHooks()
1050
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001052 for copyfile in self.copyfiles:
1053 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054
David Pursehouse8a68ff92012-09-24 12:15:13 +09001055 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001056 if self.revisionId:
1057 return self.revisionId
1058
1059 rem = self.GetRemote(self.remote.name)
1060 rev = rem.ToLocal(self.revisionExpr)
1061
David Pursehouse8a68ff92012-09-24 12:15:13 +09001062 if all_refs is not None and rev in all_refs:
1063 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001064
1065 try:
1066 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1067 except GitError:
1068 raise ManifestInvalidRevisionError(
1069 'revision %s in %s not found' % (self.revisionExpr,
1070 self.name))
1071
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001072 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001073 """Perform only the local IO portion of the sync process.
1074 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075 """
David James8d201162013-10-11 17:03:19 -07001076 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001077 all_refs = self.bare_ref.all
1078 self.CleanPublishedCache(all_refs)
1079 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001080
David Pursehouse1d947b32012-10-25 12:23:11 +09001081 def _doff():
1082 self._FastForward(revid)
1083 self._CopyFiles()
1084
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001085 head = self.work_git.GetHead()
1086 if head.startswith(R_HEADS):
1087 branch = head[len(R_HEADS):]
1088 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001089 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001090 except KeyError:
1091 head = None
1092 else:
1093 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001094
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001095 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001096 # Currently on a detached HEAD. The user is assumed to
1097 # not have any local modifications worth worrying about.
1098 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001099 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001100 syncbuf.fail(self, _PriorSyncFailedError())
1101 return
1102
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001103 if head == revid:
1104 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001105 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001106 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001107 if not syncbuf.detach_head:
1108 return
1109 else:
1110 lost = self._revlist(not_rev(revid), HEAD)
1111 if lost:
1112 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001113
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001114 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001115 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001116 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001117 syncbuf.fail(self, e)
1118 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001120 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001122 if head == revid:
1123 # No changes; don't do anything further.
1124 #
1125 return
1126
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001127 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001128
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001129 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001130 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001131 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001132 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001133 syncbuf.info(self,
1134 "leaving %s; does not track upstream",
1135 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001137 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001138 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001139 syncbuf.fail(self, e)
1140 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001141 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001142 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001143
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001144 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001145 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001147 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001148 if not_merged:
1149 if upstream_gain:
1150 # The user has published this branch and some of those
1151 # commits are not yet merged upstream. We do not want
1152 # to rewrite the published commits so we punt.
1153 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001154 syncbuf.fail(self,
1155 "branch %s is published (but not merged) and is now %d commits behind"
1156 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001157 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001158 elif pub == head:
1159 # All published commits are merged, and thus we are a
1160 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001161 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001162 syncbuf.later1(self, _doff)
1163 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001164
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001165 # Examine the local commits not in the remote. Find the
1166 # last one attributed to this user, if any.
1167 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001168 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001169 last_mine = None
1170 cnt_mine = 0
1171 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001172 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001173 if committer_email == self.UserEmail:
1174 last_mine = commit_id
1175 cnt_mine += 1
1176
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001177 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001178 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179
1180 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001181 syncbuf.fail(self, _DirtyError())
1182 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001183
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001184 # If the upstream switched on us, warn the user.
1185 #
1186 if branch.merge != self.revisionExpr:
1187 if branch.merge and self.revisionExpr:
1188 syncbuf.info(self,
1189 'manifest switched %s...%s',
1190 branch.merge,
1191 self.revisionExpr)
1192 elif branch.merge:
1193 syncbuf.info(self,
1194 'manifest no longer tracks %s',
1195 branch.merge)
1196
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001197 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001198 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001199 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001201 syncbuf.info(self,
1202 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001203 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001205 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001206 if not ID_RE.match(self.revisionExpr):
1207 # in case of manifest sync the revisionExpr might be a SHA1
1208 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001209 branch.Save()
1210
Mike Pontillod3153822012-02-28 11:53:24 -08001211 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001212 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001213 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001214 self._CopyFiles()
1215 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001216 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001217 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001218 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001219 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001220 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001221 syncbuf.fail(self, e)
1222 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001223 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001224 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001225
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001226 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227 # dest should already be an absolute path, but src is project relative
1228 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001229 abssrc = os.path.join(self.worktree, src)
1230 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001231
James W. Mills24c13082012-04-12 15:04:13 -05001232 def AddAnnotation(self, name, value, keep):
1233 self.annotations.append(_Annotation(name, value, keep))
1234
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001235 def DownloadPatchSet(self, change_id, patch_id):
1236 """Download a single patch set of a single change to FETCH_HEAD.
1237 """
1238 remote = self.GetRemote(self.remote.name)
1239
1240 cmd = ['fetch', remote.name]
1241 cmd.append('refs/changes/%2.2d/%d/%d' \
1242 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001243 if GitCommand(self, cmd, bare=True).Wait() != 0:
1244 return None
1245 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001246 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001247 change_id,
1248 patch_id,
1249 self.bare_git.rev_parse('FETCH_HEAD'))
1250
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001251
1252## Branch Management ##
1253
1254 def StartBranch(self, name):
1255 """Create a new branch off the manifest's revision.
1256 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001257 head = self.work_git.GetHead()
1258 if head == (R_HEADS + name):
1259 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001260
David Pursehouse8a68ff92012-09-24 12:15:13 +09001261 all_refs = self.bare_ref.all
1262 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001263 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001264 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001265 capture_stdout = True,
1266 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001267
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001268 branch = self.GetBranch(name)
1269 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001270 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001271 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001272
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001273 if head.startswith(R_HEADS):
1274 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001275 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001276 except KeyError:
1277 head = None
1278
1279 if revid and head and revid == head:
1280 ref = os.path.join(self.gitdir, R_HEADS + name)
1281 try:
1282 os.makedirs(os.path.dirname(ref))
1283 except OSError:
1284 pass
1285 _lwrite(ref, '%s\n' % revid)
1286 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1287 'ref: %s%s\n' % (R_HEADS, name))
1288 branch.Save()
1289 return True
1290
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001291 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001292 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001293 capture_stdout = True,
1294 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001295 branch.Save()
1296 return True
1297 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298
Wink Saville02d79452009-04-10 13:01:24 -07001299 def CheckoutBranch(self, name):
1300 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001301
1302 Args:
1303 name: The name of the branch to checkout.
1304
1305 Returns:
1306 True if the checkout succeeded; False if it didn't; None if the branch
1307 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001308 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001309 rev = R_HEADS + name
1310 head = self.work_git.GetHead()
1311 if head == rev:
1312 # Already on the branch
1313 #
1314 return True
Wink Saville02d79452009-04-10 13:01:24 -07001315
David Pursehouse8a68ff92012-09-24 12:15:13 +09001316 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001317 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001318 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001319 except KeyError:
1320 # Branch does not exist in this project
1321 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001322 return None
Wink Saville02d79452009-04-10 13:01:24 -07001323
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001324 if head.startswith(R_HEADS):
1325 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001326 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001327 except KeyError:
1328 head = None
1329
1330 if head == revid:
1331 # Same revision; just update HEAD to point to the new
1332 # target branch, but otherwise take no other action.
1333 #
1334 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1335 'ref: %s%s\n' % (R_HEADS, name))
1336 return True
1337
1338 return GitCommand(self,
1339 ['checkout', name, '--'],
1340 capture_stdout = True,
1341 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001342
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001343 def AbandonBranch(self, name):
1344 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001345
1346 Args:
1347 name: The name of the branch to abandon.
1348
1349 Returns:
1350 True if the abandon succeeded; False if it didn't; None if the branch
1351 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001352 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001353 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001354 all_refs = self.bare_ref.all
1355 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001356 # Doesn't exist
1357 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001358
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001359 head = self.work_git.GetHead()
1360 if head == rev:
1361 # We can't destroy the branch while we are sitting
1362 # on it. Switch to a detached HEAD.
1363 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001364 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001365
David Pursehouse8a68ff92012-09-24 12:15:13 +09001366 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001367 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001368 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1369 '%s\n' % revid)
1370 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001371 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001372
1373 return GitCommand(self,
1374 ['branch', '-D', name],
1375 capture_stdout = True,
1376 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001377
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001378 def PruneHeads(self):
1379 """Prune any topic branches already merged into upstream.
1380 """
1381 cb = self.CurrentBranch
1382 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001383 left = self._allrefs
1384 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385 if name.startswith(R_HEADS):
1386 name = name[len(R_HEADS):]
1387 if cb is None or name != cb:
1388 kill.append(name)
1389
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001390 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391 if cb is not None \
1392 and not self._revlist(HEAD + '...' + rev) \
1393 and not self.IsDirty(consider_untracked = False):
1394 self.work_git.DetachHead(HEAD)
1395 kill.append(cb)
1396
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001397 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001398 old = self.bare_git.GetHead()
1399 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001400 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001402 try:
1403 self.bare_git.DetachHead(rev)
1404
1405 b = ['branch', '-d']
1406 b.extend(kill)
1407 b = GitCommand(self, b, bare=True,
1408 capture_stdout=True,
1409 capture_stderr=True)
1410 b.Wait()
1411 finally:
1412 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001413 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001414
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001415 for branch in kill:
1416 if (R_HEADS + branch) not in left:
1417 self.CleanPublishedCache()
1418 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001419
1420 if cb and cb not in kill:
1421 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001422 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001423
1424 kept = []
1425 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001426 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001427 branch = self.GetBranch(branch)
1428 base = branch.LocalMerge
1429 if not base:
1430 base = rev
1431 kept.append(ReviewableBranch(self, branch, base))
1432 return kept
1433
1434
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001435## Submodule Management ##
1436
1437 def GetRegisteredSubprojects(self):
1438 result = []
1439 def rec(subprojects):
1440 if not subprojects:
1441 return
1442 result.extend(subprojects)
1443 for p in subprojects:
1444 rec(p.subprojects)
1445 rec(self.subprojects)
1446 return result
1447
1448 def _GetSubmodules(self):
1449 # Unfortunately we cannot call `git submodule status --recursive` here
1450 # because the working tree might not exist yet, and it cannot be used
1451 # without a working tree in its current implementation.
1452
1453 def get_submodules(gitdir, rev):
1454 # Parse .gitmodules for submodule sub_paths and sub_urls
1455 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1456 if not sub_paths:
1457 return []
1458 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1459 # revision of submodule repository
1460 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1461 submodules = []
1462 for sub_path, sub_url in zip(sub_paths, sub_urls):
1463 try:
1464 sub_rev = sub_revs[sub_path]
1465 except KeyError:
1466 # Ignore non-exist submodules
1467 continue
1468 submodules.append((sub_rev, sub_path, sub_url))
1469 return submodules
1470
1471 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1472 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1473 def parse_gitmodules(gitdir, rev):
1474 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1475 try:
1476 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1477 bare = True, gitdir = gitdir)
1478 except GitError:
1479 return [], []
1480 if p.Wait() != 0:
1481 return [], []
1482
1483 gitmodules_lines = []
1484 fd, temp_gitmodules_path = tempfile.mkstemp()
1485 try:
1486 os.write(fd, p.stdout)
1487 os.close(fd)
1488 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1489 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1490 bare = True, gitdir = gitdir)
1491 if p.Wait() != 0:
1492 return [], []
1493 gitmodules_lines = p.stdout.split('\n')
1494 except GitError:
1495 return [], []
1496 finally:
1497 os.remove(temp_gitmodules_path)
1498
1499 names = set()
1500 paths = {}
1501 urls = {}
1502 for line in gitmodules_lines:
1503 if not line:
1504 continue
1505 m = re_path.match(line)
1506 if m:
1507 names.add(m.group(1))
1508 paths[m.group(1)] = m.group(2)
1509 continue
1510 m = re_url.match(line)
1511 if m:
1512 names.add(m.group(1))
1513 urls[m.group(1)] = m.group(2)
1514 continue
1515 names = sorted(names)
1516 return ([paths.get(name, '') for name in names],
1517 [urls.get(name, '') for name in names])
1518
1519 def git_ls_tree(gitdir, rev, paths):
1520 cmd = ['ls-tree', rev, '--']
1521 cmd.extend(paths)
1522 try:
1523 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1524 bare = True, gitdir = gitdir)
1525 except GitError:
1526 return []
1527 if p.Wait() != 0:
1528 return []
1529 objects = {}
1530 for line in p.stdout.split('\n'):
1531 if not line.strip():
1532 continue
1533 object_rev, object_path = line.split()[2:4]
1534 objects[object_path] = object_rev
1535 return objects
1536
1537 try:
1538 rev = self.GetRevisionId()
1539 except GitError:
1540 return []
1541 return get_submodules(self.gitdir, rev)
1542
1543 def GetDerivedSubprojects(self):
1544 result = []
1545 if not self.Exists:
1546 # If git repo does not exist yet, querying its submodules will
1547 # mess up its states; so return here.
1548 return result
1549 for rev, path, url in self._GetSubmodules():
1550 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001551 relpath, worktree, gitdir, objdir = \
1552 self.manifest.GetSubprojectPaths(self, name, path)
1553 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001554 if project:
1555 result.extend(project.GetDerivedSubprojects())
1556 continue
David James8d201162013-10-11 17:03:19 -07001557
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001558 remote = RemoteSpec(self.remote.name,
1559 url = url,
1560 review = self.remote.review)
1561 subproject = Project(manifest = self.manifest,
1562 name = name,
1563 remote = remote,
1564 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001565 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001566 worktree = worktree,
1567 relpath = relpath,
1568 revisionExpr = self.revisionExpr,
1569 revisionId = rev,
1570 rebase = self.rebase,
1571 groups = self.groups,
1572 sync_c = self.sync_c,
1573 sync_s = self.sync_s,
1574 parent = self,
1575 is_derived = True)
1576 result.append(subproject)
1577 result.extend(subproject.GetDerivedSubprojects())
1578 return result
1579
1580
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001581## Direct Git Commands ##
1582
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001583 def _RemoteFetch(self, name=None,
1584 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001585 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001586 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001587 alt_dir=None,
1588 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001589
1590 is_sha1 = False
1591 tag_name = None
1592
Brian Harring14a66742012-09-28 20:21:57 -07001593 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001594 try:
1595 # if revision (sha or tag) is not present then following function
1596 # throws an error.
1597 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1598 return True
1599 except GitError:
1600 # There is no such persistent revision. We have to fetch it.
1601 return False
Brian Harring14a66742012-09-28 20:21:57 -07001602
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001603 if current_branch_only:
1604 if ID_RE.match(self.revisionExpr) is not None:
1605 is_sha1 = True
1606 elif self.revisionExpr.startswith(R_TAGS):
1607 # this is a tag and its sha1 value should never change
1608 tag_name = self.revisionExpr[len(R_TAGS):]
1609
1610 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001611 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001612 return True
Brian Harring14a66742012-09-28 20:21:57 -07001613 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1614 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001615
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001616 if not name:
1617 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001618
1619 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001620 remote = self.GetRemote(name)
1621 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001622 ssh_proxy = True
1623
Shawn O. Pearce88443382010-10-08 10:02:09 +02001624 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001625 if alt_dir and 'objects' == os.path.basename(alt_dir):
1626 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001627 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1628 remote = self.GetRemote(name)
1629
David Pursehouse8a68ff92012-09-24 12:15:13 +09001630 all_refs = self.bare_ref.all
1631 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001632 tmp = set()
1633
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301634 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001635 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001636 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001637 all_refs[r] = ref_id
1638 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001639 continue
1640
David Pursehouse8a68ff92012-09-24 12:15:13 +09001641 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001642 continue
1643
David Pursehouse8a68ff92012-09-24 12:15:13 +09001644 r = 'refs/_alt/%s' % ref_id
1645 all_refs[r] = ref_id
1646 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001647 tmp.add(r)
1648
Shawn O. Pearce88443382010-10-08 10:02:09 +02001649 tmp_packed = ''
1650 old_packed = ''
1651
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301652 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001653 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001654 tmp_packed += line
1655 if r not in tmp:
1656 old_packed += line
1657
1658 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001659 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001660 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001661
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001662 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001663
1664 # The --depth option only affects the initial fetch; after that we'll do
1665 # full fetches of changes.
David Pursehouseede7f122012-11-27 22:25:30 +09001666 if self.clone_depth:
1667 depth = self.clone_depth
1668 else:
1669 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Doug Anderson30d45292011-05-04 15:01:04 -07001670 if depth and initial:
1671 cmd.append('--depth=%s' % depth)
1672
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001673 if quiet:
1674 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001675 if not self.worktree:
1676 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001677 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001678
Brian Harring14a66742012-09-28 20:21:57 -07001679 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001680 # Fetch whole repo
Jimmie Wester2f992cb2012-12-07 12:49:51 +01001681 # If using depth then we should not get all the tags since they may
1682 # be outside of the depth.
1683 if no_tags or depth:
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001684 cmd.append('--no-tags')
1685 else:
1686 cmd.append('--tags')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301687 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001688 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001689 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001690 cmd.append(tag_name)
1691 else:
1692 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001693 if is_sha1:
1694 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001695 if branch.startswith(R_HEADS):
1696 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301697 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001698
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001699 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001700 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001701 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1702 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001703 ok = True
1704 break
Brian Harring14a66742012-09-28 20:21:57 -07001705 elif current_branch_only and is_sha1 and ret == 128:
1706 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1707 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1708 # abort the optimization attempt and do a full sync.
1709 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001710 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001711
1712 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001713 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001714 if old_packed != '':
1715 _lwrite(packed_refs, old_packed)
1716 else:
1717 os.remove(packed_refs)
1718 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001719
1720 if is_sha1 and current_branch_only and self.upstream:
1721 # We just synced the upstream given branch; verify we
1722 # got what we wanted, else trigger a second run of all
1723 # refs.
1724 if not CheckForSha1():
1725 return self._RemoteFetch(name=name, current_branch_only=False,
1726 initial=False, quiet=quiet, alt_dir=alt_dir)
1727
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001728 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001729
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001730 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001731 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001732 return False
1733
1734 remote = self.GetRemote(self.remote.name)
1735 bundle_url = remote.url + '/clone.bundle'
1736 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001737 if GetSchemeFromUrl(bundle_url) not in (
1738 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001739 return False
1740
1741 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1742 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1743
1744 exist_dst = os.path.exists(bundle_dst)
1745 exist_tmp = os.path.exists(bundle_tmp)
1746
1747 if not initial and not exist_dst and not exist_tmp:
1748 return False
1749
1750 if not exist_dst:
1751 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1752 if not exist_dst:
1753 return False
1754
1755 cmd = ['fetch']
1756 if quiet:
1757 cmd.append('--quiet')
1758 if not self.worktree:
1759 cmd.append('--update-head-ok')
1760 cmd.append(bundle_dst)
1761 for f in remote.fetch:
1762 cmd.append(str(f))
1763 cmd.append('refs/tags/*:refs/tags/*')
1764
1765 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001766 if os.path.exists(bundle_dst):
1767 os.remove(bundle_dst)
1768 if os.path.exists(bundle_tmp):
1769 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001770 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001771
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001772 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001773 if os.path.exists(dstPath):
1774 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001775
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001776 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001777 if quiet:
1778 cmd += ['--silent']
1779 if os.path.exists(tmpPath):
1780 size = os.stat(tmpPath).st_size
1781 if size >= 1024:
1782 cmd += ['--continue-at', '%d' % (size,)]
1783 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001784 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001785 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1786 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001787 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001788 if cookiefile:
1789 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001790 if srcUrl.startswith('persistent-'):
1791 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001792 cmd += [srcUrl]
1793
1794 if IsTrace():
1795 Trace('%s', ' '.join(cmd))
1796 try:
1797 proc = subprocess.Popen(cmd)
1798 except OSError:
1799 return False
1800
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001801 curlret = proc.wait()
1802
1803 if curlret == 22:
1804 # From curl man page:
1805 # 22: HTTP page not retrieved. The requested url was not found or
1806 # returned another error with the HTTP error code being 400 or above.
1807 # This return code only appears if -f, --fail is used.
1808 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001809 print("Server does not provide clone.bundle; ignoring.",
1810 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001811 return False
1812
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001813 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001814 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001815 os.rename(tmpPath, dstPath)
1816 return True
1817 else:
1818 os.remove(tmpPath)
1819 return False
1820 else:
1821 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001822
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001823 def _IsValidBundle(self, path):
1824 try:
1825 with open(path) as f:
1826 if f.read(16) == '# v2 git bundle\n':
1827 return True
1828 else:
1829 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1830 return False
1831 except OSError:
1832 return False
1833
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001834 def _GetBundleCookieFile(self, url):
1835 if url.startswith('persistent-'):
1836 try:
1837 p = subprocess.Popen(
1838 ['git-remote-persistent-https', '-print_config', url],
1839 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1840 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001841 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001842 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001843 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001844 for line in p.stdout:
1845 line = line.strip()
1846 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001847 cookiefile = line[len(prefix):]
1848 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001849 if p.wait():
1850 line = iter(p.stderr).next()
1851 if ' -print_config' in line:
1852 pass # Persistent proxy doesn't support -print_config.
1853 else:
1854 print(line + p.stderr.read(), file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001855 if cookiefile:
1856 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001857 except OSError as e:
1858 if e.errno == errno.ENOENT:
1859 pass # No persistent proxy.
1860 raise
1861 return GitConfig.ForUser().GetString('http.cookiefile')
1862
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001863 def _Checkout(self, rev, quiet=False):
1864 cmd = ['checkout']
1865 if quiet:
1866 cmd.append('-q')
1867 cmd.append(rev)
1868 cmd.append('--')
1869 if GitCommand(self, cmd).Wait() != 0:
1870 if self._allrefs:
1871 raise GitError('%s checkout %s ' % (self.name, rev))
1872
Pierre Tardye5a21222011-03-24 16:28:18 +01001873 def _CherryPick(self, rev, quiet=False):
1874 cmd = ['cherry-pick']
1875 cmd.append(rev)
1876 cmd.append('--')
1877 if GitCommand(self, cmd).Wait() != 0:
1878 if self._allrefs:
1879 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1880
Erwan Mahea94f1622011-08-19 13:56:09 +02001881 def _Revert(self, rev, quiet=False):
1882 cmd = ['revert']
1883 cmd.append('--no-edit')
1884 cmd.append(rev)
1885 cmd.append('--')
1886 if GitCommand(self, cmd).Wait() != 0:
1887 if self._allrefs:
1888 raise GitError('%s revert %s ' % (self.name, rev))
1889
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001890 def _ResetHard(self, rev, quiet=True):
1891 cmd = ['reset', '--hard']
1892 if quiet:
1893 cmd.append('-q')
1894 cmd.append(rev)
1895 if GitCommand(self, cmd).Wait() != 0:
1896 raise GitError('%s reset --hard %s ' % (self.name, rev))
1897
1898 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001899 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001900 if onto is not None:
1901 cmd.extend(['--onto', onto])
1902 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001903 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001904 raise GitError('%s rebase %s ' % (self.name, upstream))
1905
Pierre Tardy3d125942012-05-04 12:18:12 +02001906 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001907 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001908 if ffonly:
1909 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001910 if GitCommand(self, cmd).Wait() != 0:
1911 raise GitError('%s merge %s ' % (self.name, head))
1912
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001913 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001914 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07001915
1916 # Initialize the bare repository, which contains all of the objects.
1917 if not os.path.exists(self.objdir):
1918 os.makedirs(self.objdir)
1919 self.bare_objdir.init()
1920
1921 # If we have a separate directory to hold refs, initialize it as well.
1922 if self.objdir != self.gitdir:
1923 os.makedirs(self.gitdir)
1924 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
1925 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001926
Shawn O. Pearce88443382010-10-08 10:02:09 +02001927 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001928 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02001929
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001930 if ref_dir or mirror_git:
1931 if not mirror_git:
1932 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001933 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1934 self.relpath + '.git')
1935
1936 if os.path.exists(mirror_git):
1937 ref_dir = mirror_git
1938
1939 elif os.path.exists(repo_git):
1940 ref_dir = repo_git
1941
1942 else:
1943 ref_dir = None
1944
1945 if ref_dir:
1946 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1947 os.path.join(ref_dir, 'objects') + '\n')
1948
Jimmie Westera0444582012-10-24 13:44:42 +02001949 self._UpdateHooks()
1950
1951 m = self.manifest.manifestProject.config
1952 for key in ['user.name', 'user.email']:
1953 if m.Has(key, include_defaults = False):
1954 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001955 if self.manifest.IsMirror:
1956 self.config.SetString('core.bare', 'true')
1957 else:
1958 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001959
Jimmie Westera0444582012-10-24 13:44:42 +02001960 def _UpdateHooks(self):
1961 if os.path.exists(self.gitdir):
1962 # Always recreate hooks since they can have been changed
1963 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001964 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001965 try:
1966 to_rm = os.listdir(hooks)
1967 except OSError:
1968 to_rm = []
1969 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001970 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001971 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001972
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001973 def _InitHooks(self):
1974 hooks = self._gitdir_path('hooks')
1975 if not os.path.exists(hooks):
1976 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001977 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001978 name = os.path.basename(stock_hook)
1979
Victor Boivie65e0f352011-04-18 11:23:29 +02001980 if name in ('commit-msg',) and not self.remote.review \
1981 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001982 # Don't install a Gerrit Code Review hook if this
1983 # project does not appear to use it for reviews.
1984 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001985 # Since the manifest project is one of those, but also
1986 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001987 continue
1988
1989 dst = os.path.join(hooks, name)
1990 if os.path.islink(dst):
1991 continue
1992 if os.path.exists(dst):
1993 if filecmp.cmp(stock_hook, dst, shallow=False):
1994 os.remove(dst)
1995 else:
1996 _error("%s: Not replacing %s hook", self.relpath, name)
1997 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001998 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001999 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002000 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002001 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002002 raise GitError('filesystem must support symlinks')
2003 else:
2004 raise
2005
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002006 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002007 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002008 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002009 remote.url = self.remote.url
2010 remote.review = self.remote.review
2011 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002012
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002013 if self.worktree:
2014 remote.ResetFetch(mirror=False)
2015 else:
2016 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002017 remote.Save()
2018
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002019 def _InitMRef(self):
2020 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002021 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002022
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002023 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002024 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002025
2026 def _InitAnyMRef(self, ref):
2027 cur = self.bare_ref.symref(ref)
2028
2029 if self.revisionId:
2030 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2031 msg = 'manifest set to %s' % self.revisionId
2032 dst = self.revisionId + '^0'
2033 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2034 else:
2035 remote = self.GetRemote(self.remote.name)
2036 dst = remote.ToLocal(self.revisionExpr)
2037 if cur != dst:
2038 msg = 'manifest set to %s' % self.revisionExpr
2039 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002040
David James8d201162013-10-11 17:03:19 -07002041 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2042 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2043
2044 Args:
2045 gitdir: The bare git repository. Must already be initialized.
2046 dotgit: The repository you would like to initialize.
2047 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2048 Only one work tree can store refs under a given |gitdir|.
2049 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2050 This saves you the effort of initializing |dotgit| yourself.
2051 """
2052 # These objects can be shared between several working trees.
2053 symlink_files = ['description', 'info']
2054 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2055 if share_refs:
2056 # These objects can only be used by a single working tree.
2057 symlink_files += ['config', 'packed-refs']
2058 symlink_dirs += ['logs', 'refs']
2059 to_symlink = symlink_files + symlink_dirs
2060
2061 to_copy = []
2062 if copy_all:
2063 to_copy = os.listdir(gitdir)
2064
2065 for name in set(to_copy).union(to_symlink):
2066 try:
2067 src = os.path.realpath(os.path.join(gitdir, name))
2068 dst = os.path.realpath(os.path.join(dotgit, name))
2069
2070 if os.path.lexists(dst) and not os.path.islink(dst):
2071 raise GitError('cannot overwrite a local work tree')
2072
2073 # If the source dir doesn't exist, create an empty dir.
2074 if name in symlink_dirs and not os.path.lexists(src):
2075 os.makedirs(src)
2076
2077 if name in to_symlink:
2078 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2079 elif copy_all and not os.path.islink(dst):
2080 if os.path.isdir(src):
2081 shutil.copytree(src, dst)
2082 elif os.path.isfile(src):
2083 shutil.copy(src, dst)
2084 except OSError as e:
2085 if e.errno == errno.EPERM:
2086 raise GitError('filesystem must support symlinks')
2087 else:
2088 raise
2089
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002090 def _InitWorkTree(self):
2091 dotgit = os.path.join(self.worktree, '.git')
2092 if not os.path.exists(dotgit):
2093 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002094 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2095 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002096
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002097 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002098
2099 cmd = ['read-tree', '--reset', '-u']
2100 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002101 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002102 if GitCommand(self, cmd).Wait() != 0:
2103 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002104
Shawn O. Pearce93609662009-04-21 10:50:33 -07002105 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002106
2107 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002108 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002109
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002110 def _revlist(self, *args, **kw):
2111 a = []
2112 a.extend(args)
2113 a.append('--')
2114 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002115
2116 @property
2117 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002118 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002119
2120 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002121 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002122 self._project = project
2123 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002124 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002125
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002126 def LsOthers(self):
2127 p = GitCommand(self._project,
2128 ['ls-files',
2129 '-z',
2130 '--others',
2131 '--exclude-standard'],
2132 bare = False,
David James8d201162013-10-11 17:03:19 -07002133 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134 capture_stdout = True,
2135 capture_stderr = True)
2136 if p.Wait() == 0:
2137 out = p.stdout
2138 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002139 return out[:-1].split('\0') # pylint: disable=W1401
2140 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002141 return []
2142
2143 def DiffZ(self, name, *args):
2144 cmd = [name]
2145 cmd.append('-z')
2146 cmd.extend(args)
2147 p = GitCommand(self._project,
2148 cmd,
David James8d201162013-10-11 17:03:19 -07002149 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002150 bare = False,
2151 capture_stdout = True,
2152 capture_stderr = True)
2153 try:
2154 out = p.process.stdout.read()
2155 r = {}
2156 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002157 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002158 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002159 try:
2160 info = out.next()
2161 path = out.next()
2162 except StopIteration:
2163 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002164
2165 class _Info(object):
2166 def __init__(self, path, omode, nmode, oid, nid, state):
2167 self.path = path
2168 self.src_path = None
2169 self.old_mode = omode
2170 self.new_mode = nmode
2171 self.old_id = oid
2172 self.new_id = nid
2173
2174 if len(state) == 1:
2175 self.status = state
2176 self.level = None
2177 else:
2178 self.status = state[:1]
2179 self.level = state[1:]
2180 while self.level.startswith('0'):
2181 self.level = self.level[1:]
2182
2183 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002184 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002185 if info.status in ('R', 'C'):
2186 info.src_path = info.path
2187 info.path = out.next()
2188 r[info.path] = info
2189 return r
2190 finally:
2191 p.Wait()
2192
2193 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002194 if self._bare:
2195 path = os.path.join(self._project.gitdir, HEAD)
2196 else:
2197 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002198 try:
2199 fd = open(path, 'rb')
2200 except IOError:
2201 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002202 try:
2203 line = fd.read()
2204 finally:
2205 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302206 try:
2207 line = line.decode()
2208 except AttributeError:
2209 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002210 if line.startswith('ref: '):
2211 return line[5:-1]
2212 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002213
2214 def SetHead(self, ref, message=None):
2215 cmdv = []
2216 if message is not None:
2217 cmdv.extend(['-m', message])
2218 cmdv.append(HEAD)
2219 cmdv.append(ref)
2220 self.symbolic_ref(*cmdv)
2221
2222 def DetachHead(self, new, message=None):
2223 cmdv = ['--no-deref']
2224 if message is not None:
2225 cmdv.extend(['-m', message])
2226 cmdv.append(HEAD)
2227 cmdv.append(new)
2228 self.update_ref(*cmdv)
2229
2230 def UpdateRef(self, name, new, old=None,
2231 message=None,
2232 detach=False):
2233 cmdv = []
2234 if message is not None:
2235 cmdv.extend(['-m', message])
2236 if detach:
2237 cmdv.append('--no-deref')
2238 cmdv.append(name)
2239 cmdv.append(new)
2240 if old is not None:
2241 cmdv.append(old)
2242 self.update_ref(*cmdv)
2243
2244 def DeleteRef(self, name, old=None):
2245 if not old:
2246 old = self.rev_parse(name)
2247 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002248 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002249
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002250 def rev_list(self, *args, **kw):
2251 if 'format' in kw:
2252 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2253 else:
2254 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002255 cmdv.extend(args)
2256 p = GitCommand(self._project,
2257 cmdv,
2258 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002259 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002260 capture_stdout = True,
2261 capture_stderr = True)
2262 r = []
2263 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002264 if line[-1] == '\n':
2265 line = line[:-1]
2266 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002267 if p.Wait() != 0:
2268 raise GitError('%s rev-list %s: %s' % (
2269 self._project.name,
2270 str(args),
2271 p.stderr))
2272 return r
2273
2274 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002275 """Allow arbitrary git commands using pythonic syntax.
2276
2277 This allows you to do things like:
2278 git_obj.rev_parse('HEAD')
2279
2280 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2281 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002282 Any other positional arguments will be passed to the git command, and the
2283 following keyword arguments are supported:
2284 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002285
2286 Args:
2287 name: The name of the git command to call. Any '_' characters will
2288 be replaced with '-'.
2289
2290 Returns:
2291 A callable object that will try to call git with the named command.
2292 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002293 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002294 def runner(*args, **kwargs):
2295 cmdv = []
2296 config = kwargs.pop('config', None)
2297 for k in kwargs:
2298 raise TypeError('%s() got an unexpected keyword argument %r'
2299 % (name, k))
2300 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002301 if not git_require((1, 7, 2)):
2302 raise ValueError('cannot set config on command line for %s()'
2303 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302304 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002305 cmdv.append('-c')
2306 cmdv.append('%s=%s' % (k, v))
2307 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002308 cmdv.extend(args)
2309 p = GitCommand(self._project,
2310 cmdv,
2311 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002312 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002313 capture_stdout = True,
2314 capture_stderr = True)
2315 if p.Wait() != 0:
2316 raise GitError('%s %s: %s' % (
2317 self._project.name,
2318 name,
2319 p.stderr))
2320 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302321 try:
Conley Owensedd01512013-09-26 12:59:58 -07002322 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302323 except AttributeError:
2324 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002325 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2326 return r[:-1]
2327 return r
2328 return runner
2329
2330
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002331class _PriorSyncFailedError(Exception):
2332 def __str__(self):
2333 return 'prior sync failed; rebase still in progress'
2334
2335class _DirtyError(Exception):
2336 def __str__(self):
2337 return 'contains uncommitted changes'
2338
2339class _InfoMessage(object):
2340 def __init__(self, project, text):
2341 self.project = project
2342 self.text = text
2343
2344 def Print(self, syncbuf):
2345 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2346 syncbuf.out.nl()
2347
2348class _Failure(object):
2349 def __init__(self, project, why):
2350 self.project = project
2351 self.why = why
2352
2353 def Print(self, syncbuf):
2354 syncbuf.out.fail('error: %s/: %s',
2355 self.project.relpath,
2356 str(self.why))
2357 syncbuf.out.nl()
2358
2359class _Later(object):
2360 def __init__(self, project, action):
2361 self.project = project
2362 self.action = action
2363
2364 def Run(self, syncbuf):
2365 out = syncbuf.out
2366 out.project('project %s/', self.project.relpath)
2367 out.nl()
2368 try:
2369 self.action()
2370 out.nl()
2371 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002372 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002373 out.nl()
2374 return False
2375
2376class _SyncColoring(Coloring):
2377 def __init__(self, config):
2378 Coloring.__init__(self, config, 'reposync')
2379 self.project = self.printer('header', attr = 'bold')
2380 self.info = self.printer('info')
2381 self.fail = self.printer('fail', fg='red')
2382
2383class SyncBuffer(object):
2384 def __init__(self, config, detach_head=False):
2385 self._messages = []
2386 self._failures = []
2387 self._later_queue1 = []
2388 self._later_queue2 = []
2389
2390 self.out = _SyncColoring(config)
2391 self.out.redirect(sys.stderr)
2392
2393 self.detach_head = detach_head
2394 self.clean = True
2395
2396 def info(self, project, fmt, *args):
2397 self._messages.append(_InfoMessage(project, fmt % args))
2398
2399 def fail(self, project, err=None):
2400 self._failures.append(_Failure(project, err))
2401 self.clean = False
2402
2403 def later1(self, project, what):
2404 self._later_queue1.append(_Later(project, what))
2405
2406 def later2(self, project, what):
2407 self._later_queue2.append(_Later(project, what))
2408
2409 def Finish(self):
2410 self._PrintMessages()
2411 self._RunLater()
2412 self._PrintMessages()
2413 return self.clean
2414
2415 def _RunLater(self):
2416 for q in ['_later_queue1', '_later_queue2']:
2417 if not self._RunQueue(q):
2418 return
2419
2420 def _RunQueue(self, queue):
2421 for m in getattr(self, queue):
2422 if not m.Run(self):
2423 self.clean = False
2424 return False
2425 setattr(self, queue, [])
2426 return True
2427
2428 def _PrintMessages(self):
2429 for m in self._messages:
2430 m.Print(self)
2431 for m in self._failures:
2432 m.Print(self)
2433
2434 self._messages = []
2435 self._failures = []
2436
2437
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002438class MetaProject(Project):
2439 """A special project housed under .repo.
2440 """
2441 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002442 Project.__init__(self,
2443 manifest = manifest,
2444 name = name,
2445 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002446 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002447 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002448 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002449 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002450 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002451 revisionId = None,
2452 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002453
2454 def PreSync(self):
2455 if self.Exists:
2456 cb = self.CurrentBranch
2457 if cb:
2458 base = self.GetBranch(cb).merge
2459 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002460 self.revisionExpr = base
2461 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002462
Florian Vallee5d016502012-06-07 17:19:26 +02002463 def MetaBranchSwitch(self, target):
2464 """ Prepare MetaProject for manifest branch switch
2465 """
2466
2467 # detach and delete manifest branch, allowing a new
2468 # branch to take over
2469 syncbuf = SyncBuffer(self.config, detach_head = True)
2470 self.Sync_LocalHalf(syncbuf)
2471 syncbuf.Finish()
2472
2473 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002474 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002475 capture_stdout = True,
2476 capture_stderr = True).Wait() == 0
2477
2478
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002479 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002480 def LastFetch(self):
2481 try:
2482 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2483 return os.path.getmtime(fh)
2484 except OSError:
2485 return 0
2486
2487 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002488 def HasChanges(self):
2489 """Has the remote received new commits not yet checked out?
2490 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002491 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002492 return False
2493
David Pursehouse8a68ff92012-09-24 12:15:13 +09002494 all_refs = self.bare_ref.all
2495 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002496 head = self.work_git.GetHead()
2497 if head.startswith(R_HEADS):
2498 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002499 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002500 except KeyError:
2501 head = None
2502
2503 if revid == head:
2504 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002505 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002506 return True
2507 return False