blob: b01a52bac2297fb35b3a94469a48fb0c6ac066a5 [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,
490 worktree,
491 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700492 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800493 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700494 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700495 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700496 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800497 sync_s = False,
David Pursehouseede7f122012-11-27 22:25:30 +0900498 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800499 upstream = None,
500 parent = None,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400501 is_derived = False,
502 dest_branch = None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800503 """Init a Project object.
504
505 Args:
506 manifest: The XmlManifest object.
507 name: The `name` attribute of manifest.xml's project element.
508 remote: RemoteSpec object specifying its remote's properties.
509 gitdir: Absolute path of git directory.
510 worktree: Absolute path of git working tree.
511 relpath: Relative path of git working tree to repo's top directory.
512 revisionExpr: The `revision` attribute of manifest.xml's project element.
513 revisionId: git commit id for checking out.
514 rebase: The `rebase` attribute of manifest.xml's project element.
515 groups: The `groups` attribute of manifest.xml's project element.
516 sync_c: The `sync-c` attribute of manifest.xml's project element.
517 sync_s: The `sync-s` attribute of manifest.xml's project element.
518 upstream: The `upstream` attribute of manifest.xml's project element.
519 parent: The parent Project object.
520 is_derived: False if the project was explicitly defined in the manifest;
521 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400522 dest_branch: The branch to which to push changes for review by default.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800523 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700524 self.manifest = manifest
525 self.name = name
526 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800527 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800528 if worktree:
529 self.worktree = worktree.replace('\\', '/')
530 else:
531 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700532 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700533 self.revisionExpr = revisionExpr
534
535 if revisionId is None \
536 and revisionExpr \
537 and IsId(revisionExpr):
538 self.revisionId = revisionExpr
539 else:
540 self.revisionId = revisionId
541
Mike Pontillod3153822012-02-28 11:53:24 -0800542 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700543 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700544 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800545 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900546 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700547 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800548 self.parent = parent
549 self.is_derived = is_derived
550 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800551
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700553 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500554 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700555 self.config = GitConfig.ForRepository(
556 gitdir = self.gitdir,
557 defaults = self.manifest.globalConfig)
558
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800559 if self.worktree:
560 self.work_git = self._GitGetByExec(self, bare=False)
561 else:
562 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700563 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700564 self.bare_ref = GitRefs(gitdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400565 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700566
Doug Anderson37282b42011-03-04 11:54:18 -0800567 # This will be filled in if a project is later identified to be the
568 # project containing repo hooks.
569 self.enabled_repo_hooks = []
570
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700571 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800572 def Derived(self):
573 return self.is_derived
574
575 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700576 def Exists(self):
577 return os.path.isdir(self.gitdir)
578
579 @property
580 def CurrentBranch(self):
581 """Obtain the name of the currently checked out branch.
582 The branch name omits the 'refs/heads/' prefix.
583 None is returned if the project is on a detached HEAD.
584 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700585 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700586 if b.startswith(R_HEADS):
587 return b[len(R_HEADS):]
588 return None
589
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700590 def IsRebaseInProgress(self):
591 w = self.worktree
592 g = os.path.join(w, '.git')
593 return os.path.exists(os.path.join(g, 'rebase-apply')) \
594 or os.path.exists(os.path.join(g, 'rebase-merge')) \
595 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200596
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700597 def IsDirty(self, consider_untracked=True):
598 """Is the working directory modified in some way?
599 """
600 self.work_git.update_index('-q',
601 '--unmerged',
602 '--ignore-missing',
603 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900604 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700605 return True
606 if self.work_git.DiffZ('diff-files'):
607 return True
608 if consider_untracked and self.work_git.LsOthers():
609 return True
610 return False
611
612 _userident_name = None
613 _userident_email = None
614
615 @property
616 def UserName(self):
617 """Obtain the user's personal name.
618 """
619 if self._userident_name is None:
620 self._LoadUserIdentity()
621 return self._userident_name
622
623 @property
624 def UserEmail(self):
625 """Obtain the user's email address. This is very likely
626 to be their Gerrit login.
627 """
628 if self._userident_email is None:
629 self._LoadUserIdentity()
630 return self._userident_email
631
632 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900633 u = self.bare_git.var('GIT_COMMITTER_IDENT')
634 m = re.compile("^(.*) <([^>]*)> ").match(u)
635 if m:
636 self._userident_name = m.group(1)
637 self._userident_email = m.group(2)
638 else:
639 self._userident_name = ''
640 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700641
642 def GetRemote(self, name):
643 """Get the configuration for a single remote.
644 """
645 return self.config.GetRemote(name)
646
647 def GetBranch(self, name):
648 """Get the configuration for a single branch.
649 """
650 return self.config.GetBranch(name)
651
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700652 def GetBranches(self):
653 """Get all existing local branches.
654 """
655 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900656 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700657 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700658
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530659 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700660 if name.startswith(R_HEADS):
661 name = name[len(R_HEADS):]
662 b = self.GetBranch(name)
663 b.current = name == current
664 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900665 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700666 heads[name] = b
667
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530668 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700669 if name.startswith(R_PUB):
670 name = name[len(R_PUB):]
671 b = heads.get(name)
672 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900673 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700674
675 return heads
676
Colin Cross5acde752012-03-28 20:15:45 -0700677 def MatchesGroups(self, manifest_groups):
678 """Returns true if the manifest groups specified at init should cause
679 this project to be synced.
680 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700681 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700682
Conley Owens971de8e2012-04-16 10:36:08 -0700683 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700684 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700685 manifest_groups: "-group1,group2"
686 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500687
688 The special manifest group "default" will match any project that
689 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700690 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500691 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700692 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500693 if not 'notdefault' in expanded_project_groups:
694 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700695
Conley Owens971de8e2012-04-16 10:36:08 -0700696 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700697 for group in expanded_manifest_groups:
698 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700699 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700700 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700701 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700702
Conley Owens971de8e2012-04-16 10:36:08 -0700703 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700704
705## Status Display ##
706
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500707 def HasChanges(self):
708 """Returns true if there are uncommitted changes.
709 """
710 self.work_git.update_index('-q',
711 '--unmerged',
712 '--ignore-missing',
713 '--refresh')
714 if self.IsRebaseInProgress():
715 return True
716
717 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
718 return True
719
720 if self.work_git.DiffZ('diff-files'):
721 return True
722
723 if self.work_git.LsOthers():
724 return True
725
726 return False
727
Terence Haddock4655e812011-03-31 12:33:34 +0200728 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700729 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200730
731 Args:
732 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733 """
734 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200735 if output_redir == None:
736 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700737 print(file=output_redir)
738 print('project %s/' % self.relpath, file=output_redir)
739 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700740 return
741
742 self.work_git.update_index('-q',
743 '--unmerged',
744 '--ignore-missing',
745 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700746 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700747 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
748 df = self.work_git.DiffZ('diff-files')
749 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100750 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700751 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700752
753 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200754 if not output_redir == None:
755 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700756 out.project('project %-40s', self.relpath + '/')
757
758 branch = self.CurrentBranch
759 if branch is None:
760 out.nobranch('(*** NO BRANCH ***)')
761 else:
762 out.branch('branch %s', branch)
763 out.nl()
764
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700765 if rb:
766 out.important('prior sync failed; rebase still in progress')
767 out.nl()
768
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700769 paths = list()
770 paths.extend(di.keys())
771 paths.extend(df.keys())
772 paths.extend(do)
773
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530774 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900775 try:
776 i = di[p]
777 except KeyError:
778 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700779
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900780 try:
781 f = df[p]
782 except KeyError:
783 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200784
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900785 if i:
786 i_status = i.status.upper()
787 else:
788 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700789
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900790 if f:
791 f_status = f.status.lower()
792 else:
793 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794
795 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800796 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700797 i.src_path, p, i.level)
798 else:
799 line = ' %s%s\t%s' % (i_status, f_status, p)
800
801 if i and not f:
802 out.added('%s', line)
803 elif (i and f) or (not i and f):
804 out.changed('%s', line)
805 elif not i and not f:
806 out.untracked('%s', line)
807 else:
808 out.write('%s', line)
809 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200810
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700811 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812
pelyad67872d2012-03-28 14:49:58 +0300813 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814 """Prints the status of the repository to stdout.
815 """
816 out = DiffColoring(self.config)
817 cmd = ['diff']
818 if out.is_on:
819 cmd.append('--color')
820 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300821 if absolute_paths:
822 cmd.append('--src-prefix=a/%s/' % self.relpath)
823 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824 cmd.append('--')
825 p = GitCommand(self,
826 cmd,
827 capture_stdout = True,
828 capture_stderr = True)
829 has_diff = False
830 for line in p.process.stdout:
831 if not has_diff:
832 out.nl()
833 out.project('project %s/' % self.relpath)
834 out.nl()
835 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700836 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700837 p.Wait()
838
839
840## Publish / Upload ##
841
David Pursehouse8a68ff92012-09-24 12:15:13 +0900842 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843 """Was the branch published (uploaded) for code review?
844 If so, returns the SHA-1 hash of the last published
845 state for the branch.
846 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700847 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900848 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700849 try:
850 return self.bare_git.rev_parse(key)
851 except GitError:
852 return None
853 else:
854 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900855 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700856 except KeyError:
857 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858
David Pursehouse8a68ff92012-09-24 12:15:13 +0900859 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700860 """Prunes any stale published refs.
861 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900862 if all_refs is None:
863 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700864 heads = set()
865 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530866 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867 if name.startswith(R_HEADS):
868 heads.add(name)
869 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900870 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530872 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700873 n = name[len(R_PUB):]
874 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900875 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700876
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700877 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878 """List any branches which can be uploaded for review.
879 """
880 heads = {}
881 pubed = {}
882
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530883 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700884 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900885 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900887 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888
889 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530890 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900891 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700893 if selected_branch and branch != selected_branch:
894 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700895
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800896 rb = self.GetUploadableBranch(branch)
897 if rb:
898 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899 return ready
900
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800901 def GetUploadableBranch(self, branch_name):
902 """Get a single uploadable branch, or None.
903 """
904 branch = self.GetBranch(branch_name)
905 base = branch.LocalMerge
906 if branch.LocalMerge:
907 rb = ReviewableBranch(self, branch, base)
908 if rb.commits:
909 return rb
910 return None
911
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700912 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700913 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700914 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400915 draft=False,
916 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917 """Uploads the named branch for code review.
918 """
919 if branch is None:
920 branch = self.CurrentBranch
921 if branch is None:
922 raise GitError('not currently on a branch')
923
924 branch = self.GetBranch(branch)
925 if not branch.LocalMerge:
926 raise GitError('branch %s does not track a remote' % branch.name)
927 if not branch.remote.review:
928 raise GitError('remote %s has no review url' % branch.remote.name)
929
Bryan Jacobsf609f912013-05-06 13:36:24 -0400930 if dest_branch is None:
931 dest_branch = self.dest_branch
932 if dest_branch is None:
933 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934 if not dest_branch.startswith(R_HEADS):
935 dest_branch = R_HEADS + dest_branch
936
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800937 if not branch.remote.projectname:
938 branch.remote.projectname = self.name
939 branch.remote.Save()
940
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800941 url = branch.remote.ReviewUrl(self.UserEmail)
942 if url is None:
943 raise UploadError('review not configured')
944 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800945
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800946 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800947 rp = ['gerrit receive-pack']
948 for e in people[0]:
949 rp.append('--reviewer=%s' % sq(e))
950 for e in people[1]:
951 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800952 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700953
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800954 cmd.append(url)
955
956 if dest_branch.startswith(R_HEADS):
957 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700958
959 upload_type = 'for'
960 if draft:
961 upload_type = 'drafts'
962
963 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
964 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800965 if auto_topic:
966 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800967 if not url.startswith('ssh://'):
968 rp = ['r=%s' % p for p in people[0]] + \
969 ['cc=%s' % p for p in people[1]]
970 if rp:
971 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800972 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800973
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800974 if GitCommand(self, cmd, bare = True).Wait() != 0:
975 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700976
977 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
978 self.bare_git.UpdateRef(R_PUB + branch.name,
979 R_HEADS + branch.name,
980 message = msg)
981
982
983## Sync ##
984
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700985 def Sync_NetworkHalf(self,
986 quiet=False,
987 is_new=None,
988 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700989 clone_bundle=True,
990 no_tags=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991 """Perform only the network IO portion of the sync process.
992 Local working directory/branch state is not affected.
993 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700994 if is_new is None:
995 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200996 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +0200998 else:
999 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001001
1002 if is_new:
1003 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1004 try:
1005 fd = open(alt, 'rb')
1006 try:
1007 alt_dir = fd.readline().rstrip()
1008 finally:
1009 fd.close()
1010 except IOError:
1011 alt_dir = None
1012 else:
1013 alt_dir = None
1014
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001015 if clone_bundle \
1016 and alt_dir is None \
1017 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001018 is_new = False
1019
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001020 if not current_branch_only:
1021 if self.sync_c:
1022 current_branch_only = True
1023 elif not self.manifest._loaded:
1024 # Manifest cannot check defaults until it syncs.
1025 current_branch_only = False
1026 elif self.manifest.default.sync_c:
1027 current_branch_only = True
1028
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001029 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001030 current_branch_only=current_branch_only,
1031 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001032 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001033
1034 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001035 self._InitMRef()
1036 else:
1037 self._InitMirrorHead()
1038 try:
1039 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1040 except OSError:
1041 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001043
1044 def PostRepoUpgrade(self):
1045 self._InitHooks()
1046
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001047 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001048 for copyfile in self.copyfiles:
1049 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001050
David Pursehouse8a68ff92012-09-24 12:15:13 +09001051 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001052 if self.revisionId:
1053 return self.revisionId
1054
1055 rem = self.GetRemote(self.remote.name)
1056 rev = rem.ToLocal(self.revisionExpr)
1057
David Pursehouse8a68ff92012-09-24 12:15:13 +09001058 if all_refs is not None and rev in all_refs:
1059 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001060
1061 try:
1062 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1063 except GitError:
1064 raise ManifestInvalidRevisionError(
1065 'revision %s in %s not found' % (self.revisionExpr,
1066 self.name))
1067
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001068 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069 """Perform only the local IO portion of the sync process.
1070 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001072 all_refs = self.bare_ref.all
1073 self.CleanPublishedCache(all_refs)
1074 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001075
David Pursehouse1d947b32012-10-25 12:23:11 +09001076 def _doff():
1077 self._FastForward(revid)
1078 self._CopyFiles()
1079
Skyler Kaufman835cd682011-03-08 12:14:41 -08001080 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001081 head = self.work_git.GetHead()
1082 if head.startswith(R_HEADS):
1083 branch = head[len(R_HEADS):]
1084 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001085 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001086 except KeyError:
1087 head = None
1088 else:
1089 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001091 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001092 # Currently on a detached HEAD. The user is assumed to
1093 # not have any local modifications worth worrying about.
1094 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001095 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001096 syncbuf.fail(self, _PriorSyncFailedError())
1097 return
1098
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001099 if head == revid:
1100 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001101 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001102 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001103 if not syncbuf.detach_head:
1104 return
1105 else:
1106 lost = self._revlist(not_rev(revid), HEAD)
1107 if lost:
1108 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001109
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001110 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001111 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001112 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001113 syncbuf.fail(self, e)
1114 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001115 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001116 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001118 if head == revid:
1119 # No changes; don't do anything further.
1120 #
1121 return
1122
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001123 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001124
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001125 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001127 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001128 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001129 syncbuf.info(self,
1130 "leaving %s; does not track upstream",
1131 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001132 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001133 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001134 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001135 syncbuf.fail(self, e)
1136 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001137 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001138 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001139
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001140 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001141 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001142 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001143 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001144 if not_merged:
1145 if upstream_gain:
1146 # The user has published this branch and some of those
1147 # commits are not yet merged upstream. We do not want
1148 # to rewrite the published commits so we punt.
1149 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001150 syncbuf.fail(self,
1151 "branch %s is published (but not merged) and is now %d commits behind"
1152 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001153 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001154 elif pub == head:
1155 # All published commits are merged, and thus we are a
1156 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001157 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001158 syncbuf.later1(self, _doff)
1159 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001161 # Examine the local commits not in the remote. Find the
1162 # last one attributed to this user, if any.
1163 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001164 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001165 last_mine = None
1166 cnt_mine = 0
1167 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301168 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001169 if committer_email == self.UserEmail:
1170 last_mine = commit_id
1171 cnt_mine += 1
1172
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001173 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001174 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001175
1176 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001177 syncbuf.fail(self, _DirtyError())
1178 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001180 # If the upstream switched on us, warn the user.
1181 #
1182 if branch.merge != self.revisionExpr:
1183 if branch.merge and self.revisionExpr:
1184 syncbuf.info(self,
1185 'manifest switched %s...%s',
1186 branch.merge,
1187 self.revisionExpr)
1188 elif branch.merge:
1189 syncbuf.info(self,
1190 'manifest no longer tracks %s',
1191 branch.merge)
1192
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001193 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001195 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001197 syncbuf.info(self,
1198 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001199 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001201 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001202 if not ID_RE.match(self.revisionExpr):
1203 # in case of manifest sync the revisionExpr might be a SHA1
1204 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001205 branch.Save()
1206
Mike Pontillod3153822012-02-28 11:53:24 -08001207 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001208 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001209 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001210 self._CopyFiles()
1211 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001212 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001214 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001215 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001216 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001217 syncbuf.fail(self, e)
1218 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001219 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001220 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001221
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001222 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001223 # dest should already be an absolute path, but src is project relative
1224 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001225 abssrc = os.path.join(self.worktree, src)
1226 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227
James W. Mills24c13082012-04-12 15:04:13 -05001228 def AddAnnotation(self, name, value, keep):
1229 self.annotations.append(_Annotation(name, value, keep))
1230
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001231 def DownloadPatchSet(self, change_id, patch_id):
1232 """Download a single patch set of a single change to FETCH_HEAD.
1233 """
1234 remote = self.GetRemote(self.remote.name)
1235
1236 cmd = ['fetch', remote.name]
1237 cmd.append('refs/changes/%2.2d/%d/%d' \
1238 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001239 if GitCommand(self, cmd, bare=True).Wait() != 0:
1240 return None
1241 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001242 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001243 change_id,
1244 patch_id,
1245 self.bare_git.rev_parse('FETCH_HEAD'))
1246
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247
1248## Branch Management ##
1249
1250 def StartBranch(self, name):
1251 """Create a new branch off the manifest's revision.
1252 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001253 head = self.work_git.GetHead()
1254 if head == (R_HEADS + name):
1255 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001256
David Pursehouse8a68ff92012-09-24 12:15:13 +09001257 all_refs = self.bare_ref.all
1258 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001259 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001260 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001261 capture_stdout = True,
1262 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001263
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001264 branch = self.GetBranch(name)
1265 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001266 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001267 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001268
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001269 if head.startswith(R_HEADS):
1270 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001271 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001272 except KeyError:
1273 head = None
1274
1275 if revid and head and revid == head:
1276 ref = os.path.join(self.gitdir, R_HEADS + name)
1277 try:
1278 os.makedirs(os.path.dirname(ref))
1279 except OSError:
1280 pass
1281 _lwrite(ref, '%s\n' % revid)
1282 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1283 'ref: %s%s\n' % (R_HEADS, name))
1284 branch.Save()
1285 return True
1286
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001287 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001288 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001289 capture_stdout = True,
1290 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001291 branch.Save()
1292 return True
1293 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294
Wink Saville02d79452009-04-10 13:01:24 -07001295 def CheckoutBranch(self, name):
1296 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001297
1298 Args:
1299 name: The name of the branch to checkout.
1300
1301 Returns:
1302 True if the checkout succeeded; False if it didn't; None if the branch
1303 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001304 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001305 rev = R_HEADS + name
1306 head = self.work_git.GetHead()
1307 if head == rev:
1308 # Already on the branch
1309 #
1310 return True
Wink Saville02d79452009-04-10 13:01:24 -07001311
David Pursehouse8a68ff92012-09-24 12:15:13 +09001312 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001313 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001314 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001315 except KeyError:
1316 # Branch does not exist in this project
1317 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001318 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001319
1320 if head.startswith(R_HEADS):
1321 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001322 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001323 except KeyError:
1324 head = None
1325
1326 if head == revid:
1327 # Same revision; just update HEAD to point to the new
1328 # target branch, but otherwise take no other action.
1329 #
1330 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1331 'ref: %s%s\n' % (R_HEADS, name))
1332 return True
Wink Saville02d79452009-04-10 13:01:24 -07001333
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001334 return GitCommand(self,
1335 ['checkout', name, '--'],
1336 capture_stdout = True,
1337 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001338
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001339 def AbandonBranch(self, name):
1340 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001341
1342 Args:
1343 name: The name of the branch to abandon.
1344
1345 Returns:
1346 True if the abandon succeeded; False if it didn't; None if the branch
1347 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001348 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001349 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001350 all_refs = self.bare_ref.all
1351 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001352 # Doesn't exist
1353 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001354
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001355 head = self.work_git.GetHead()
1356 if head == rev:
1357 # We can't destroy the branch while we are sitting
1358 # on it. Switch to a detached HEAD.
1359 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001360 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001361
David Pursehouse8a68ff92012-09-24 12:15:13 +09001362 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001363 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001364 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1365 '%s\n' % revid)
1366 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001367 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001368
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001369 return GitCommand(self,
1370 ['branch', '-D', name],
1371 capture_stdout = True,
1372 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001373
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001374 def PruneHeads(self):
1375 """Prune any topic branches already merged into upstream.
1376 """
1377 cb = self.CurrentBranch
1378 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001379 left = self._allrefs
1380 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381 if name.startswith(R_HEADS):
1382 name = name[len(R_HEADS):]
1383 if cb is None or name != cb:
1384 kill.append(name)
1385
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001386 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001387 if cb is not None \
1388 and not self._revlist(HEAD + '...' + rev) \
1389 and not self.IsDirty(consider_untracked = False):
1390 self.work_git.DetachHead(HEAD)
1391 kill.append(cb)
1392
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001394 old = self.bare_git.GetHead()
1395 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001396 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1397
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398 try:
1399 self.bare_git.DetachHead(rev)
1400
1401 b = ['branch', '-d']
1402 b.extend(kill)
1403 b = GitCommand(self, b, bare=True,
1404 capture_stdout=True,
1405 capture_stderr=True)
1406 b.Wait()
1407 finally:
1408 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001409 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001410
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001411 for branch in kill:
1412 if (R_HEADS + branch) not in left:
1413 self.CleanPublishedCache()
1414 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001415
1416 if cb and cb not in kill:
1417 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001418 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001419
1420 kept = []
1421 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001422 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001423 branch = self.GetBranch(branch)
1424 base = branch.LocalMerge
1425 if not base:
1426 base = rev
1427 kept.append(ReviewableBranch(self, branch, base))
1428 return kept
1429
1430
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001431## Submodule Management ##
1432
1433 def GetRegisteredSubprojects(self):
1434 result = []
1435 def rec(subprojects):
1436 if not subprojects:
1437 return
1438 result.extend(subprojects)
1439 for p in subprojects:
1440 rec(p.subprojects)
1441 rec(self.subprojects)
1442 return result
1443
1444 def _GetSubmodules(self):
1445 # Unfortunately we cannot call `git submodule status --recursive` here
1446 # because the working tree might not exist yet, and it cannot be used
1447 # without a working tree in its current implementation.
1448
1449 def get_submodules(gitdir, rev):
1450 # Parse .gitmodules for submodule sub_paths and sub_urls
1451 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1452 if not sub_paths:
1453 return []
1454 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1455 # revision of submodule repository
1456 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1457 submodules = []
1458 for sub_path, sub_url in zip(sub_paths, sub_urls):
1459 try:
1460 sub_rev = sub_revs[sub_path]
1461 except KeyError:
1462 # Ignore non-exist submodules
1463 continue
1464 submodules.append((sub_rev, sub_path, sub_url))
1465 return submodules
1466
1467 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1468 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1469 def parse_gitmodules(gitdir, rev):
1470 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1471 try:
1472 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1473 bare = True, gitdir = gitdir)
1474 except GitError:
1475 return [], []
1476 if p.Wait() != 0:
1477 return [], []
1478
1479 gitmodules_lines = []
1480 fd, temp_gitmodules_path = tempfile.mkstemp()
1481 try:
1482 os.write(fd, p.stdout)
1483 os.close(fd)
1484 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1485 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1486 bare = True, gitdir = gitdir)
1487 if p.Wait() != 0:
1488 return [], []
1489 gitmodules_lines = p.stdout.split('\n')
1490 except GitError:
1491 return [], []
1492 finally:
1493 os.remove(temp_gitmodules_path)
1494
1495 names = set()
1496 paths = {}
1497 urls = {}
1498 for line in gitmodules_lines:
1499 if not line:
1500 continue
1501 m = re_path.match(line)
1502 if m:
1503 names.add(m.group(1))
1504 paths[m.group(1)] = m.group(2)
1505 continue
1506 m = re_url.match(line)
1507 if m:
1508 names.add(m.group(1))
1509 urls[m.group(1)] = m.group(2)
1510 continue
1511 names = sorted(names)
1512 return ([paths.get(name, '') for name in names],
1513 [urls.get(name, '') for name in names])
1514
1515 def git_ls_tree(gitdir, rev, paths):
1516 cmd = ['ls-tree', rev, '--']
1517 cmd.extend(paths)
1518 try:
1519 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1520 bare = True, gitdir = gitdir)
1521 except GitError:
1522 return []
1523 if p.Wait() != 0:
1524 return []
1525 objects = {}
1526 for line in p.stdout.split('\n'):
1527 if not line.strip():
1528 continue
1529 object_rev, object_path = line.split()[2:4]
1530 objects[object_path] = object_rev
1531 return objects
1532
1533 try:
1534 rev = self.GetRevisionId()
1535 except GitError:
1536 return []
1537 return get_submodules(self.gitdir, rev)
1538
1539 def GetDerivedSubprojects(self):
1540 result = []
1541 if not self.Exists:
1542 # If git repo does not exist yet, querying its submodules will
1543 # mess up its states; so return here.
1544 return result
1545 for rev, path, url in self._GetSubmodules():
1546 name = self.manifest.GetSubprojectName(self, path)
1547 project = self.manifest.projects.get(name)
1548 if project:
1549 result.extend(project.GetDerivedSubprojects())
1550 continue
1551 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1552 remote = RemoteSpec(self.remote.name,
1553 url = url,
1554 review = self.remote.review)
1555 subproject = Project(manifest = self.manifest,
1556 name = name,
1557 remote = remote,
1558 gitdir = gitdir,
1559 worktree = worktree,
1560 relpath = relpath,
1561 revisionExpr = self.revisionExpr,
1562 revisionId = rev,
1563 rebase = self.rebase,
1564 groups = self.groups,
1565 sync_c = self.sync_c,
1566 sync_s = self.sync_s,
1567 parent = self,
1568 is_derived = True)
1569 result.append(subproject)
1570 result.extend(subproject.GetDerivedSubprojects())
1571 return result
1572
1573
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001574## Direct Git Commands ##
1575
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001576 def _RemoteFetch(self, name=None,
1577 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001578 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001579 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001580 alt_dir=None,
1581 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001582
1583 is_sha1 = False
1584 tag_name = None
1585
Brian Harring14a66742012-09-28 20:21:57 -07001586 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001587 try:
1588 # if revision (sha or tag) is not present then following function
1589 # throws an error.
1590 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1591 return True
1592 except GitError:
1593 # There is no such persistent revision. We have to fetch it.
1594 return False
Brian Harring14a66742012-09-28 20:21:57 -07001595
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001596 if current_branch_only:
1597 if ID_RE.match(self.revisionExpr) is not None:
1598 is_sha1 = True
1599 elif self.revisionExpr.startswith(R_TAGS):
1600 # this is a tag and its sha1 value should never change
1601 tag_name = self.revisionExpr[len(R_TAGS):]
1602
1603 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001604 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001605 return True
Brian Harring14a66742012-09-28 20:21:57 -07001606 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1607 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001608
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001609 if not name:
1610 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001611
1612 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001613 remote = self.GetRemote(name)
1614 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001615 ssh_proxy = True
1616
Shawn O. Pearce88443382010-10-08 10:02:09 +02001617 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001618 if alt_dir and 'objects' == os.path.basename(alt_dir):
1619 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001620 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1621 remote = self.GetRemote(name)
1622
David Pursehouse8a68ff92012-09-24 12:15:13 +09001623 all_refs = self.bare_ref.all
1624 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001625 tmp = set()
1626
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301627 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001628 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001629 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001630 all_refs[r] = ref_id
1631 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001632 continue
1633
David Pursehouse8a68ff92012-09-24 12:15:13 +09001634 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001635 continue
1636
David Pursehouse8a68ff92012-09-24 12:15:13 +09001637 r = 'refs/_alt/%s' % ref_id
1638 all_refs[r] = ref_id
1639 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001640 tmp.add(r)
1641
Shawn O. Pearce88443382010-10-08 10:02:09 +02001642 tmp_packed = ''
1643 old_packed = ''
1644
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301645 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001646 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001647 tmp_packed += line
1648 if r not in tmp:
1649 old_packed += line
1650
1651 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001652 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001653 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001654
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001655 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001656
1657 # The --depth option only affects the initial fetch; after that we'll do
1658 # full fetches of changes.
David Pursehouseede7f122012-11-27 22:25:30 +09001659 if self.clone_depth:
1660 depth = self.clone_depth
1661 else:
1662 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Doug Anderson30d45292011-05-04 15:01:04 -07001663 if depth and initial:
1664 cmd.append('--depth=%s' % depth)
1665
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001666 if quiet:
1667 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001668 if not self.worktree:
1669 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001670 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001671
Brian Harring14a66742012-09-28 20:21:57 -07001672 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001673 # Fetch whole repo
Jimmie Wester2f992cb2012-12-07 12:49:51 +01001674 # If using depth then we should not get all the tags since they may
1675 # be outside of the depth.
1676 if no_tags or depth:
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001677 cmd.append('--no-tags')
1678 else:
1679 cmd.append('--tags')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301680 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001681 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001682 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001683 cmd.append(tag_name)
1684 else:
1685 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001686 if is_sha1:
1687 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001688 if branch.startswith(R_HEADS):
1689 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301690 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001691
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001692 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001693 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001694 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1695 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001696 ok = True
1697 break
Brian Harring14a66742012-09-28 20:21:57 -07001698 elif current_branch_only and is_sha1 and ret == 128:
1699 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1700 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1701 # abort the optimization attempt and do a full sync.
1702 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001703 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001704
1705 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001706 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001707 if old_packed != '':
1708 _lwrite(packed_refs, old_packed)
1709 else:
1710 os.remove(packed_refs)
1711 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001712
1713 if is_sha1 and current_branch_only and self.upstream:
1714 # We just synced the upstream given branch; verify we
1715 # got what we wanted, else trigger a second run of all
1716 # refs.
1717 if not CheckForSha1():
1718 return self._RemoteFetch(name=name, current_branch_only=False,
1719 initial=False, quiet=quiet, alt_dir=alt_dir)
1720
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001721 return ok
1722
1723 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001724 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001725 return False
1726
1727 remote = self.GetRemote(self.remote.name)
1728 bundle_url = remote.url + '/clone.bundle'
1729 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001730 if GetSchemeFromUrl(bundle_url) not in (
1731 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001732 return False
1733
1734 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1735 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001736
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001737 exist_dst = os.path.exists(bundle_dst)
1738 exist_tmp = os.path.exists(bundle_tmp)
1739
1740 if not initial and not exist_dst and not exist_tmp:
1741 return False
1742
1743 if not exist_dst:
1744 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1745 if not exist_dst:
1746 return False
1747
1748 cmd = ['fetch']
1749 if quiet:
1750 cmd.append('--quiet')
1751 if not self.worktree:
1752 cmd.append('--update-head-ok')
1753 cmd.append(bundle_dst)
1754 for f in remote.fetch:
1755 cmd.append(str(f))
1756 cmd.append('refs/tags/*:refs/tags/*')
1757
1758 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001759 if os.path.exists(bundle_dst):
1760 os.remove(bundle_dst)
1761 if os.path.exists(bundle_tmp):
1762 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001763 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001764
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001765 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001766 if os.path.exists(dstPath):
1767 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001768
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001769 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001770 if quiet:
1771 cmd += ['--silent']
1772 if os.path.exists(tmpPath):
1773 size = os.stat(tmpPath).st_size
1774 if size >= 1024:
1775 cmd += ['--continue-at', '%d' % (size,)]
1776 else:
1777 os.remove(tmpPath)
1778 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1779 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001780 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001781 if cookiefile:
1782 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001783 if srcUrl.startswith('persistent-'):
1784 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001785 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001786
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001787 if IsTrace():
1788 Trace('%s', ' '.join(cmd))
1789 try:
1790 proc = subprocess.Popen(cmd)
1791 except OSError:
1792 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001793
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001794 curlret = proc.wait()
1795
1796 if curlret == 22:
1797 # From curl man page:
1798 # 22: HTTP page not retrieved. The requested url was not found or
1799 # returned another error with the HTTP error code being 400 or above.
1800 # This return code only appears if -f, --fail is used.
1801 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001802 print("Server does not provide clone.bundle; ignoring.",
1803 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001804 return False
1805
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001806 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001807 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001808 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001809 return True
1810 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001811 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001812 return False
1813 else:
1814 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001815
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001816 def _IsValidBundle(self, path):
1817 try:
1818 with open(path) as f:
1819 if f.read(16) == '# v2 git bundle\n':
1820 return True
1821 else:
1822 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1823 return False
1824 except OSError:
1825 return False
1826
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001827 def _GetBundleCookieFile(self, url):
1828 if url.startswith('persistent-'):
1829 try:
1830 p = subprocess.Popen(
1831 ['git-remote-persistent-https', '-print_config', url],
1832 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1833 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001834 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001835 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001836 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001837 for line in p.stdout:
1838 line = line.strip()
1839 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001840 cookiefile = line[len(prefix):]
1841 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001842 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001843 err_msg = p.stderr.read()
1844 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001845 pass # Persistent proxy doesn't support -print_config.
1846 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001847 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001848 if cookiefile:
1849 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001850 except OSError as e:
1851 if e.errno == errno.ENOENT:
1852 pass # No persistent proxy.
1853 raise
1854 return GitConfig.ForUser().GetString('http.cookiefile')
1855
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001856 def _Checkout(self, rev, quiet=False):
1857 cmd = ['checkout']
1858 if quiet:
1859 cmd.append('-q')
1860 cmd.append(rev)
1861 cmd.append('--')
1862 if GitCommand(self, cmd).Wait() != 0:
1863 if self._allrefs:
1864 raise GitError('%s checkout %s ' % (self.name, rev))
1865
Pierre Tardye5a21222011-03-24 16:28:18 +01001866 def _CherryPick(self, rev, quiet=False):
1867 cmd = ['cherry-pick']
1868 cmd.append(rev)
1869 cmd.append('--')
1870 if GitCommand(self, cmd).Wait() != 0:
1871 if self._allrefs:
1872 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1873
Erwan Mahea94f1622011-08-19 13:56:09 +02001874 def _Revert(self, rev, quiet=False):
1875 cmd = ['revert']
1876 cmd.append('--no-edit')
1877 cmd.append(rev)
1878 cmd.append('--')
1879 if GitCommand(self, cmd).Wait() != 0:
1880 if self._allrefs:
1881 raise GitError('%s revert %s ' % (self.name, rev))
1882
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001883 def _ResetHard(self, rev, quiet=True):
1884 cmd = ['reset', '--hard']
1885 if quiet:
1886 cmd.append('-q')
1887 cmd.append(rev)
1888 if GitCommand(self, cmd).Wait() != 0:
1889 raise GitError('%s reset --hard %s ' % (self.name, rev))
1890
1891 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001892 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001893 if onto is not None:
1894 cmd.extend(['--onto', onto])
1895 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001896 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001897 raise GitError('%s rebase %s ' % (self.name, upstream))
1898
Pierre Tardy3d125942012-05-04 12:18:12 +02001899 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001900 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001901 if ffonly:
1902 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001903 if GitCommand(self, cmd).Wait() != 0:
1904 raise GitError('%s merge %s ' % (self.name, head))
1905
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001906 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001907 if not os.path.exists(self.gitdir):
1908 os.makedirs(self.gitdir)
1909 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001910
Shawn O. Pearce88443382010-10-08 10:02:09 +02001911 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001912 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02001913
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001914 if ref_dir or mirror_git:
1915 if not mirror_git:
1916 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001917 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1918 self.relpath + '.git')
1919
1920 if os.path.exists(mirror_git):
1921 ref_dir = mirror_git
1922
1923 elif os.path.exists(repo_git):
1924 ref_dir = repo_git
1925
1926 else:
1927 ref_dir = None
1928
1929 if ref_dir:
1930 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1931 os.path.join(ref_dir, 'objects') + '\n')
1932
Jimmie Westera0444582012-10-24 13:44:42 +02001933 self._UpdateHooks()
1934
1935 m = self.manifest.manifestProject.config
1936 for key in ['user.name', 'user.email']:
1937 if m.Has(key, include_defaults = False):
1938 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001939 if self.manifest.IsMirror:
1940 self.config.SetString('core.bare', 'true')
1941 else:
1942 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001943
Jimmie Westera0444582012-10-24 13:44:42 +02001944 def _UpdateHooks(self):
1945 if os.path.exists(self.gitdir):
1946 # Always recreate hooks since they can have been changed
1947 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001948 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001949 try:
1950 to_rm = os.listdir(hooks)
1951 except OSError:
1952 to_rm = []
1953 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001954 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001955 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001956
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001957 def _InitHooks(self):
1958 hooks = self._gitdir_path('hooks')
1959 if not os.path.exists(hooks):
1960 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001961 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001962 name = os.path.basename(stock_hook)
1963
Victor Boivie65e0f352011-04-18 11:23:29 +02001964 if name in ('commit-msg',) and not self.remote.review \
1965 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001966 # Don't install a Gerrit Code Review hook if this
1967 # project does not appear to use it for reviews.
1968 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001969 # Since the manifest project is one of those, but also
1970 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001971 continue
1972
1973 dst = os.path.join(hooks, name)
1974 if os.path.islink(dst):
1975 continue
1976 if os.path.exists(dst):
1977 if filecmp.cmp(stock_hook, dst, shallow=False):
1978 os.remove(dst)
1979 else:
1980 _error("%s: Not replacing %s hook", self.relpath, name)
1981 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001982 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001983 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001984 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001985 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001986 raise GitError('filesystem must support symlinks')
1987 else:
1988 raise
1989
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001990 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001991 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001992 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001993 remote.url = self.remote.url
1994 remote.review = self.remote.review
1995 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001996
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001997 if self.worktree:
1998 remote.ResetFetch(mirror=False)
1999 else:
2000 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002001 remote.Save()
2002
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002003 def _InitMRef(self):
2004 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002005 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002006
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002007 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002008 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002009
2010 def _InitAnyMRef(self, ref):
2011 cur = self.bare_ref.symref(ref)
2012
2013 if self.revisionId:
2014 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2015 msg = 'manifest set to %s' % self.revisionId
2016 dst = self.revisionId + '^0'
2017 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2018 else:
2019 remote = self.GetRemote(self.remote.name)
2020 dst = remote.ToLocal(self.revisionExpr)
2021 if cur != dst:
2022 msg = 'manifest set to %s' % self.revisionExpr
2023 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002024
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002025 def _InitWorkTree(self):
2026 dotgit = os.path.join(self.worktree, '.git')
2027 if not os.path.exists(dotgit):
2028 os.makedirs(dotgit)
2029
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002030 for name in ['config',
2031 'description',
2032 'hooks',
2033 'info',
2034 'logs',
2035 'objects',
2036 'packed-refs',
2037 'refs',
2038 'rr-cache',
2039 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08002040 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002041 src = os.path.join(self.gitdir, name)
2042 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08002043 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002044 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08002045 else:
2046 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07002047 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08002048 if e.errno == errno.EPERM:
2049 raise GitError('filesystem must support symlinks')
2050 else:
2051 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002052
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002053 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002054
2055 cmd = ['read-tree', '--reset', '-u']
2056 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002057 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002058 if GitCommand(self, cmd).Wait() != 0:
2059 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002060
2061 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2062 if not os.path.exists(rr_cache):
2063 os.makedirs(rr_cache)
2064
Shawn O. Pearce93609662009-04-21 10:50:33 -07002065 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002066
2067 def _gitdir_path(self, path):
2068 return os.path.join(self.gitdir, path)
2069
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002070 def _revlist(self, *args, **kw):
2071 a = []
2072 a.extend(args)
2073 a.append('--')
2074 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002075
2076 @property
2077 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002078 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002079
2080 class _GitGetByExec(object):
2081 def __init__(self, project, bare):
2082 self._project = project
2083 self._bare = bare
2084
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002085 def LsOthers(self):
2086 p = GitCommand(self._project,
2087 ['ls-files',
2088 '-z',
2089 '--others',
2090 '--exclude-standard'],
2091 bare = False,
2092 capture_stdout = True,
2093 capture_stderr = True)
2094 if p.Wait() == 0:
2095 out = p.stdout
2096 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002097 return out[:-1].split('\0') # pylint: disable=W1401
2098 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002099 return []
2100
2101 def DiffZ(self, name, *args):
2102 cmd = [name]
2103 cmd.append('-z')
2104 cmd.extend(args)
2105 p = GitCommand(self._project,
2106 cmd,
2107 bare = False,
2108 capture_stdout = True,
2109 capture_stderr = True)
2110 try:
2111 out = p.process.stdout.read()
2112 r = {}
2113 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002114 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002115 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002116 try:
2117 info = out.next()
2118 path = out.next()
2119 except StopIteration:
2120 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002121
2122 class _Info(object):
2123 def __init__(self, path, omode, nmode, oid, nid, state):
2124 self.path = path
2125 self.src_path = None
2126 self.old_mode = omode
2127 self.new_mode = nmode
2128 self.old_id = oid
2129 self.new_id = nid
2130
2131 if len(state) == 1:
2132 self.status = state
2133 self.level = None
2134 else:
2135 self.status = state[:1]
2136 self.level = state[1:]
2137 while self.level.startswith('0'):
2138 self.level = self.level[1:]
2139
2140 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002141 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002142 if info.status in ('R', 'C'):
2143 info.src_path = info.path
2144 info.path = out.next()
2145 r[info.path] = info
2146 return r
2147 finally:
2148 p.Wait()
2149
2150 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002151 if self._bare:
2152 path = os.path.join(self._project.gitdir, HEAD)
2153 else:
2154 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002155 try:
2156 fd = open(path, 'rb')
2157 except IOError:
2158 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002159 try:
2160 line = fd.read()
2161 finally:
2162 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302163 try:
2164 line = line.decode()
2165 except AttributeError:
2166 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002167 if line.startswith('ref: '):
2168 return line[5:-1]
2169 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002170
2171 def SetHead(self, ref, message=None):
2172 cmdv = []
2173 if message is not None:
2174 cmdv.extend(['-m', message])
2175 cmdv.append(HEAD)
2176 cmdv.append(ref)
2177 self.symbolic_ref(*cmdv)
2178
2179 def DetachHead(self, new, message=None):
2180 cmdv = ['--no-deref']
2181 if message is not None:
2182 cmdv.extend(['-m', message])
2183 cmdv.append(HEAD)
2184 cmdv.append(new)
2185 self.update_ref(*cmdv)
2186
2187 def UpdateRef(self, name, new, old=None,
2188 message=None,
2189 detach=False):
2190 cmdv = []
2191 if message is not None:
2192 cmdv.extend(['-m', message])
2193 if detach:
2194 cmdv.append('--no-deref')
2195 cmdv.append(name)
2196 cmdv.append(new)
2197 if old is not None:
2198 cmdv.append(old)
2199 self.update_ref(*cmdv)
2200
2201 def DeleteRef(self, name, old=None):
2202 if not old:
2203 old = self.rev_parse(name)
2204 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002205 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002206
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002207 def rev_list(self, *args, **kw):
2208 if 'format' in kw:
2209 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2210 else:
2211 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002212 cmdv.extend(args)
2213 p = GitCommand(self._project,
2214 cmdv,
2215 bare = self._bare,
2216 capture_stdout = True,
2217 capture_stderr = True)
2218 r = []
2219 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002220 if line[-1] == '\n':
2221 line = line[:-1]
2222 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002223 if p.Wait() != 0:
2224 raise GitError('%s rev-list %s: %s' % (
2225 self._project.name,
2226 str(args),
2227 p.stderr))
2228 return r
2229
2230 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002231 """Allow arbitrary git commands using pythonic syntax.
2232
2233 This allows you to do things like:
2234 git_obj.rev_parse('HEAD')
2235
2236 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2237 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002238 Any other positional arguments will be passed to the git command, and the
2239 following keyword arguments are supported:
2240 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002241
2242 Args:
2243 name: The name of the git command to call. Any '_' characters will
2244 be replaced with '-'.
2245
2246 Returns:
2247 A callable object that will try to call git with the named command.
2248 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002249 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002250 def runner(*args, **kwargs):
2251 cmdv = []
2252 config = kwargs.pop('config', None)
2253 for k in kwargs:
2254 raise TypeError('%s() got an unexpected keyword argument %r'
2255 % (name, k))
2256 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002257 if not git_require((1, 7, 2)):
2258 raise ValueError('cannot set config on command line for %s()'
2259 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302260 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002261 cmdv.append('-c')
2262 cmdv.append('%s=%s' % (k, v))
2263 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002264 cmdv.extend(args)
2265 p = GitCommand(self._project,
2266 cmdv,
2267 bare = self._bare,
2268 capture_stdout = True,
2269 capture_stderr = True)
2270 if p.Wait() != 0:
2271 raise GitError('%s %s: %s' % (
2272 self._project.name,
2273 name,
2274 p.stderr))
2275 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302276 try:
Conley Owensedd01512013-09-26 12:59:58 -07002277 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302278 except AttributeError:
2279 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002280 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2281 return r[:-1]
2282 return r
2283 return runner
2284
2285
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002286class _PriorSyncFailedError(Exception):
2287 def __str__(self):
2288 return 'prior sync failed; rebase still in progress'
2289
2290class _DirtyError(Exception):
2291 def __str__(self):
2292 return 'contains uncommitted changes'
2293
2294class _InfoMessage(object):
2295 def __init__(self, project, text):
2296 self.project = project
2297 self.text = text
2298
2299 def Print(self, syncbuf):
2300 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2301 syncbuf.out.nl()
2302
2303class _Failure(object):
2304 def __init__(self, project, why):
2305 self.project = project
2306 self.why = why
2307
2308 def Print(self, syncbuf):
2309 syncbuf.out.fail('error: %s/: %s',
2310 self.project.relpath,
2311 str(self.why))
2312 syncbuf.out.nl()
2313
2314class _Later(object):
2315 def __init__(self, project, action):
2316 self.project = project
2317 self.action = action
2318
2319 def Run(self, syncbuf):
2320 out = syncbuf.out
2321 out.project('project %s/', self.project.relpath)
2322 out.nl()
2323 try:
2324 self.action()
2325 out.nl()
2326 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002327 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002328 out.nl()
2329 return False
2330
2331class _SyncColoring(Coloring):
2332 def __init__(self, config):
2333 Coloring.__init__(self, config, 'reposync')
2334 self.project = self.printer('header', attr = 'bold')
2335 self.info = self.printer('info')
2336 self.fail = self.printer('fail', fg='red')
2337
2338class SyncBuffer(object):
2339 def __init__(self, config, detach_head=False):
2340 self._messages = []
2341 self._failures = []
2342 self._later_queue1 = []
2343 self._later_queue2 = []
2344
2345 self.out = _SyncColoring(config)
2346 self.out.redirect(sys.stderr)
2347
2348 self.detach_head = detach_head
2349 self.clean = True
2350
2351 def info(self, project, fmt, *args):
2352 self._messages.append(_InfoMessage(project, fmt % args))
2353
2354 def fail(self, project, err=None):
2355 self._failures.append(_Failure(project, err))
2356 self.clean = False
2357
2358 def later1(self, project, what):
2359 self._later_queue1.append(_Later(project, what))
2360
2361 def later2(self, project, what):
2362 self._later_queue2.append(_Later(project, what))
2363
2364 def Finish(self):
2365 self._PrintMessages()
2366 self._RunLater()
2367 self._PrintMessages()
2368 return self.clean
2369
2370 def _RunLater(self):
2371 for q in ['_later_queue1', '_later_queue2']:
2372 if not self._RunQueue(q):
2373 return
2374
2375 def _RunQueue(self, queue):
2376 for m in getattr(self, queue):
2377 if not m.Run(self):
2378 self.clean = False
2379 return False
2380 setattr(self, queue, [])
2381 return True
2382
2383 def _PrintMessages(self):
2384 for m in self._messages:
2385 m.Print(self)
2386 for m in self._failures:
2387 m.Print(self)
2388
2389 self._messages = []
2390 self._failures = []
2391
2392
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002393class MetaProject(Project):
2394 """A special project housed under .repo.
2395 """
2396 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002397 Project.__init__(self,
2398 manifest = manifest,
2399 name = name,
2400 gitdir = gitdir,
2401 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002402 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002403 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002404 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002405 revisionId = None,
2406 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002407
2408 def PreSync(self):
2409 if self.Exists:
2410 cb = self.CurrentBranch
2411 if cb:
2412 base = self.GetBranch(cb).merge
2413 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002414 self.revisionExpr = base
2415 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002416
Florian Vallee5d016502012-06-07 17:19:26 +02002417 def MetaBranchSwitch(self, target):
2418 """ Prepare MetaProject for manifest branch switch
2419 """
2420
2421 # detach and delete manifest branch, allowing a new
2422 # branch to take over
2423 syncbuf = SyncBuffer(self.config, detach_head = True)
2424 self.Sync_LocalHalf(syncbuf)
2425 syncbuf.Finish()
2426
2427 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002428 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002429 capture_stdout = True,
2430 capture_stderr = True).Wait() == 0
2431
2432
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002433 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002434 def LastFetch(self):
2435 try:
2436 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2437 return os.path.getmtime(fh)
2438 except OSError:
2439 return 0
2440
2441 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002442 def HasChanges(self):
2443 """Has the remote received new commits not yet checked out?
2444 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002445 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002446 return False
2447
David Pursehouse8a68ff92012-09-24 12:15:13 +09002448 all_refs = self.bare_ref.all
2449 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002450 head = self.work_git.GetHead()
2451 if head.startswith(R_HEADS):
2452 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002453 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002454 except KeyError:
2455 head = None
2456
2457 if revid == head:
2458 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002459 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002460 return True
2461 return False