blob: 49fef2f79e95acdf983337aa6d6fb307f256b8ab [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
Doug Anderson37282b42011-03-04 11:54:18 -080015import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
18import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070019import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import re
21import shutil
22import stat
23import sys
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070024import time
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import urllib2
26
Shawn O. Pearcefab96c62011-10-11 12:00:38 -070027try:
28 import threading as _threading
29except ImportError:
30 import dummy_threading as _threading
31
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070032try:
33 from os import SEEK_END
34except ImportError:
35 SEEK_END = 2
36
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037from color import Coloring
38from git_command import GitCommand
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070039from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070040from error import DownloadError
Doug Anderson37282b42011-03-04 11:54:18 -080041from error import GitError, HookError, ImportError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080042from error import ManifestInvalidRevisionError
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070043from progress import Progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearced237b692009-04-17 18:49:50 -070045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
Shawn O. Pearcefab96c62011-10-11 12:00:38 -070047_urllib_lock = _threading.Lock()
48
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070049def _lwrite(path, content):
50 lock = '%s.lock' % path
51
52 fd = open(lock, 'wb')
53 try:
54 fd.write(content)
55 finally:
56 fd.close()
57
58 try:
59 os.rename(lock, path)
60 except OSError:
61 os.remove(lock)
62 raise
63
Shawn O. Pearce48244782009-04-16 08:25:57 -070064def _error(fmt, *args):
65 msg = fmt % args
66 print >>sys.stderr, 'error: %s' % msg
67
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068def not_rev(r):
69 return '^' + r
70
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080071def sq(r):
72 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080073
Doug Anderson8ced8642011-01-10 14:16:30 -080074_project_hook_list = None
75def _ProjectHooks():
76 """List the hooks present in the 'hooks' directory.
77
78 These hooks are project hooks and are copied to the '.git/hooks' directory
79 of all subprojects.
80
81 This function caches the list of hooks (based on the contents of the
82 'repo/hooks' directory) on the first call.
83
84 Returns:
85 A list of absolute paths to all of the files in the hooks directory.
86 """
87 global _project_hook_list
88 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080089 d = os.path.abspath(os.path.dirname(__file__))
90 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080091 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
92 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080093
94def relpath(dst, src):
95 src = os.path.dirname(src)
96 top = os.path.commonprefix([dst, src])
97 if top.endswith('/'):
98 top = top[:-1]
99 else:
100 top = os.path.dirname(top)
101
102 tmp = src
103 rel = ''
104 while top != tmp:
105 rel += '../'
106 tmp = os.path.dirname(tmp)
107 return rel + dst[len(top) + 1:]
108
109
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700110class DownloadedChange(object):
111 _commit_cache = None
112
113 def __init__(self, project, base, change_id, ps_id, commit):
114 self.project = project
115 self.base = base
116 self.change_id = change_id
117 self.ps_id = ps_id
118 self.commit = commit
119
120 @property
121 def commits(self):
122 if self._commit_cache is None:
123 self._commit_cache = self.project.bare_git.rev_list(
124 '--abbrev=8',
125 '--abbrev-commit',
126 '--pretty=oneline',
127 '--reverse',
128 '--date-order',
129 not_rev(self.base),
130 self.commit,
131 '--')
132 return self._commit_cache
133
134
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135class ReviewableBranch(object):
136 _commit_cache = None
137
138 def __init__(self, project, branch, base):
139 self.project = project
140 self.branch = branch
141 self.base = base
142
143 @property
144 def name(self):
145 return self.branch.name
146
147 @property
148 def commits(self):
149 if self._commit_cache is None:
150 self._commit_cache = self.project.bare_git.rev_list(
151 '--abbrev=8',
152 '--abbrev-commit',
153 '--pretty=oneline',
154 '--reverse',
155 '--date-order',
156 not_rev(self.base),
157 R_HEADS + self.name,
158 '--')
159 return self._commit_cache
160
161 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 def unabbrev_commits(self):
163 r = dict()
164 for commit in self.project.bare_git.rev_list(
165 not_rev(self.base),
166 R_HEADS + self.name,
167 '--'):
168 r[commit[0:8]] = commit
169 return r
170
171 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172 def date(self):
173 return self.project.bare_git.log(
174 '--pretty=format:%cd',
175 '-n', '1',
176 R_HEADS + self.name,
177 '--')
178
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700179 def UploadForReview(self, people, auto_topic=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800180 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700181 people,
182 auto_topic=auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700183
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700184 def GetPublishedRefs(self):
185 refs = {}
186 output = self.project.bare_git.ls_remote(
187 self.branch.remote.SshReviewUrl(self.project.UserEmail),
188 'refs/changes/*')
189 for line in output.split('\n'):
190 try:
191 (sha, ref) = line.split()
192 refs[sha] = ref
193 except ValueError:
194 pass
195
196 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197
198class StatusColoring(Coloring):
199 def __init__(self, config):
200 Coloring.__init__(self, config, 'status')
201 self.project = self.printer('header', attr = 'bold')
202 self.branch = self.printer('header', attr = 'bold')
203 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700204 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205
206 self.added = self.printer('added', fg = 'green')
207 self.changed = self.printer('changed', fg = 'red')
208 self.untracked = self.printer('untracked', fg = 'red')
209
210
211class DiffColoring(Coloring):
212 def __init__(self, config):
213 Coloring.__init__(self, config, 'diff')
214 self.project = self.printer('header', attr = 'bold')
215
216
217class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800218 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700219 self.src = src
220 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800221 self.abs_src = abssrc
222 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
224 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800225 src = self.abs_src
226 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227 # copy file if it does not exist or is out of date
228 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
229 try:
230 # remove existing file first, since it might be read-only
231 if os.path.exists(dest):
232 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400233 else:
234 dir = os.path.dirname(dest)
235 if not os.path.isdir(dir):
236 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237 shutil.copy(src, dest)
238 # make the file read-only
239 mode = os.stat(dest)[stat.ST_MODE]
240 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
241 os.chmod(dest, mode)
242 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700243 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700245class RemoteSpec(object):
246 def __init__(self,
247 name,
248 url = None,
249 review = None):
250 self.name = name
251 self.url = url
252 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253
Doug Anderson37282b42011-03-04 11:54:18 -0800254class RepoHook(object):
255 """A RepoHook contains information about a script to run as a hook.
256
257 Hooks are used to run a python script before running an upload (for instance,
258 to run presubmit checks). Eventually, we may have hooks for other actions.
259
260 This shouldn't be confused with files in the 'repo/hooks' directory. Those
261 files are copied into each '.git/hooks' folder for each project. Repo-level
262 hooks are associated instead with repo actions.
263
264 Hooks are always python. When a hook is run, we will load the hook into the
265 interpreter and execute its main() function.
266 """
267 def __init__(self,
268 hook_type,
269 hooks_project,
270 topdir,
271 abort_if_user_denies=False):
272 """RepoHook constructor.
273
274 Params:
275 hook_type: A string representing the type of hook. This is also used
276 to figure out the name of the file containing the hook. For
277 example: 'pre-upload'.
278 hooks_project: The project containing the repo hooks. If you have a
279 manifest, this is manifest.repo_hooks_project. OK if this is None,
280 which will make the hook a no-op.
281 topdir: Repo's top directory (the one containing the .repo directory).
282 Scripts will run with CWD as this directory. If you have a manifest,
283 this is manifest.topdir
284 abort_if_user_denies: If True, we'll throw a HookError() if the user
285 doesn't allow us to run the hook.
286 """
287 self._hook_type = hook_type
288 self._hooks_project = hooks_project
289 self._topdir = topdir
290 self._abort_if_user_denies = abort_if_user_denies
291
292 # Store the full path to the script for convenience.
293 if self._hooks_project:
294 self._script_fullpath = os.path.join(self._hooks_project.worktree,
295 self._hook_type + '.py')
296 else:
297 self._script_fullpath = None
298
299 def _GetHash(self):
300 """Return a hash of the contents of the hooks directory.
301
302 We'll just use git to do this. This hash has the property that if anything
303 changes in the directory we will return a different has.
304
305 SECURITY CONSIDERATION:
306 This hash only represents the contents of files in the hook directory, not
307 any other files imported or called by hooks. Changes to imported files
308 can change the script behavior without affecting the hash.
309
310 Returns:
311 A string representing the hash. This will always be ASCII so that it can
312 be printed to the user easily.
313 """
314 assert self._hooks_project, "Must have hooks to calculate their hash."
315
316 # We will use the work_git object rather than just calling GetRevisionId().
317 # That gives us a hash of the latest checked in version of the files that
318 # the user will actually be executing. Specifically, GetRevisionId()
319 # doesn't appear to change even if a user checks out a different version
320 # of the hooks repo (via git checkout) nor if a user commits their own revs.
321 #
322 # NOTE: Local (non-committed) changes will not be factored into this hash.
323 # I think this is OK, since we're really only worried about warning the user
324 # about upstream changes.
325 return self._hooks_project.work_git.rev_parse('HEAD')
326
327 def _GetMustVerb(self):
328 """Return 'must' if the hook is required; 'should' if not."""
329 if self._abort_if_user_denies:
330 return 'must'
331 else:
332 return 'should'
333
334 def _CheckForHookApproval(self):
335 """Check to see whether this hook has been approved.
336
337 We'll look at the hash of all of the hooks. If this matches the hash that
338 the user last approved, we're done. If it doesn't, we'll ask the user
339 about approval.
340
341 Note that we ask permission for each individual hook even though we use
342 the hash of all hooks when detecting changes. We'd like the user to be
343 able to approve / deny each hook individually. We only use the hash of all
344 hooks because there is no other easy way to detect changes to local imports.
345
346 Returns:
347 True if this hook is approved to run; False otherwise.
348
349 Raises:
350 HookError: Raised if the user doesn't approve and abort_if_user_denies
351 was passed to the consturctor.
352 """
353 hooks_dir = self._hooks_project.worktree
354 hooks_config = self._hooks_project.config
355 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
356
357 # Get the last hash that the user approved for this hook; may be None.
358 old_hash = hooks_config.GetString(git_approval_key)
359
360 # Get the current hash so we can tell if scripts changed since approval.
361 new_hash = self._GetHash()
362
363 if old_hash is not None:
364 # User previously approved hook and asked not to be prompted again.
365 if new_hash == old_hash:
366 # Approval matched. We're done.
367 return True
368 else:
369 # Give the user a reason why we're prompting, since they last told
370 # us to "never ask again".
371 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
372 self._hook_type)
373 else:
374 prompt = ''
375
376 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
377 if sys.stdout.isatty():
378 prompt += ('Repo %s run the script:\n'
379 ' %s\n'
380 '\n'
381 'Do you want to allow this script to run '
382 '(yes/yes-never-ask-again/NO)? ') % (
383 self._GetMustVerb(), self._script_fullpath)
384 response = raw_input(prompt).lower()
385 print
386
387 # User is doing a one-time approval.
388 if response in ('y', 'yes'):
389 return True
390 elif response == 'yes-never-ask-again':
391 hooks_config.SetString(git_approval_key, new_hash)
392 return True
393
394 # For anything else, we'll assume no approval.
395 if self._abort_if_user_denies:
396 raise HookError('You must allow the %s hook or use --no-verify.' %
397 self._hook_type)
398
399 return False
400
401 def _ExecuteHook(self, **kwargs):
402 """Actually execute the given hook.
403
404 This will run the hook's 'main' function in our python interpreter.
405
406 Args:
407 kwargs: Keyword arguments to pass to the hook. These are often specific
408 to the hook type. For instance, pre-upload hooks will contain
409 a project_list.
410 """
411 # Keep sys.path and CWD stashed away so that we can always restore them
412 # upon function exit.
413 orig_path = os.getcwd()
414 orig_syspath = sys.path
415
416 try:
417 # Always run hooks with CWD as topdir.
418 os.chdir(self._topdir)
419
420 # Put the hook dir as the first item of sys.path so hooks can do
421 # relative imports. We want to replace the repo dir as [0] so
422 # hooks can't import repo files.
423 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
424
425 # Exec, storing global context in the context dict. We catch exceptions
426 # and convert to a HookError w/ just the failing traceback.
427 context = {}
428 try:
429 execfile(self._script_fullpath, context)
430 except Exception:
431 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
432 traceback.format_exc(), self._hook_type))
433
434 # Running the script should have defined a main() function.
435 if 'main' not in context:
436 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
437
438
439 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
440 # We don't actually want hooks to define their main with this argument--
441 # it's there to remind them that their hook should always take **kwargs.
442 # For instance, a pre-upload hook should be defined like:
443 # def main(project_list, **kwargs):
444 #
445 # This allows us to later expand the API without breaking old hooks.
446 kwargs = kwargs.copy()
447 kwargs['hook_should_take_kwargs'] = True
448
449 # Call the main function in the hook. If the hook should cause the
450 # build to fail, it will raise an Exception. We'll catch that convert
451 # to a HookError w/ just the failing traceback.
452 try:
453 context['main'](**kwargs)
454 except Exception:
455 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
456 'above.' % (
457 traceback.format_exc(), self._hook_type))
458 finally:
459 # Restore sys.path and CWD.
460 sys.path = orig_syspath
461 os.chdir(orig_path)
462
463 def Run(self, user_allows_all_hooks, **kwargs):
464 """Run the hook.
465
466 If the hook doesn't exist (because there is no hooks project or because
467 this particular hook is not enabled), this is a no-op.
468
469 Args:
470 user_allows_all_hooks: If True, we will never prompt about running the
471 hook--we'll just assume it's OK to run it.
472 kwargs: Keyword arguments to pass to the hook. These are often specific
473 to the hook type. For instance, pre-upload hooks will contain
474 a project_list.
475
476 Raises:
477 HookError: If there was a problem finding the hook or the user declined
478 to run a required hook (from _CheckForHookApproval).
479 """
480 # No-op if there is no hooks project or if hook is disabled.
481 if ((not self._hooks_project) or
482 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
483 return
484
485 # Bail with a nice error if we can't find the hook.
486 if not os.path.isfile(self._script_fullpath):
487 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
488
489 # Make sure the user is OK with running the hook.
490 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
491 return
492
493 # Run the hook with the same version of python we're using.
494 self._ExecuteHook(**kwargs)
495
496
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700497class Project(object):
498 def __init__(self,
499 manifest,
500 name,
501 remote,
502 gitdir,
503 worktree,
504 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700505 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800506 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700507 rebase = True,
508 groups = None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700509 self.manifest = manifest
510 self.name = name
511 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800512 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800513 if worktree:
514 self.worktree = worktree.replace('\\', '/')
515 else:
516 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700517 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700518 self.revisionExpr = revisionExpr
519
520 if revisionId is None \
521 and revisionExpr \
522 and IsId(revisionExpr):
523 self.revisionId = revisionExpr
524 else:
525 self.revisionId = revisionId
526
Mike Pontillod3153822012-02-28 11:53:24 -0800527 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700528 self.groups = groups
Mike Pontillod3153822012-02-28 11:53:24 -0800529
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700530 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700531 self.copyfiles = []
532 self.config = GitConfig.ForRepository(
533 gitdir = self.gitdir,
534 defaults = self.manifest.globalConfig)
535
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800536 if self.worktree:
537 self.work_git = self._GitGetByExec(self, bare=False)
538 else:
539 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700541 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700542
Doug Anderson37282b42011-03-04 11:54:18 -0800543 # This will be filled in if a project is later identified to be the
544 # project containing repo hooks.
545 self.enabled_repo_hooks = []
546
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700547 @property
548 def Exists(self):
549 return os.path.isdir(self.gitdir)
550
551 @property
552 def CurrentBranch(self):
553 """Obtain the name of the currently checked out branch.
554 The branch name omits the 'refs/heads/' prefix.
555 None is returned if the project is on a detached HEAD.
556 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700557 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700558 if b.startswith(R_HEADS):
559 return b[len(R_HEADS):]
560 return None
561
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700562 def IsRebaseInProgress(self):
563 w = self.worktree
564 g = os.path.join(w, '.git')
565 return os.path.exists(os.path.join(g, 'rebase-apply')) \
566 or os.path.exists(os.path.join(g, 'rebase-merge')) \
567 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200568
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700569 def IsDirty(self, consider_untracked=True):
570 """Is the working directory modified in some way?
571 """
572 self.work_git.update_index('-q',
573 '--unmerged',
574 '--ignore-missing',
575 '--refresh')
576 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
577 return True
578 if self.work_git.DiffZ('diff-files'):
579 return True
580 if consider_untracked and self.work_git.LsOthers():
581 return True
582 return False
583
584 _userident_name = None
585 _userident_email = None
586
587 @property
588 def UserName(self):
589 """Obtain the user's personal name.
590 """
591 if self._userident_name is None:
592 self._LoadUserIdentity()
593 return self._userident_name
594
595 @property
596 def UserEmail(self):
597 """Obtain the user's email address. This is very likely
598 to be their Gerrit login.
599 """
600 if self._userident_email is None:
601 self._LoadUserIdentity()
602 return self._userident_email
603
604 def _LoadUserIdentity(self):
605 u = self.bare_git.var('GIT_COMMITTER_IDENT')
606 m = re.compile("^(.*) <([^>]*)> ").match(u)
607 if m:
608 self._userident_name = m.group(1)
609 self._userident_email = m.group(2)
610 else:
611 self._userident_name = ''
612 self._userident_email = ''
613
614 def GetRemote(self, name):
615 """Get the configuration for a single remote.
616 """
617 return self.config.GetRemote(name)
618
619 def GetBranch(self, name):
620 """Get the configuration for a single branch.
621 """
622 return self.config.GetBranch(name)
623
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700624 def GetBranches(self):
625 """Get all existing local branches.
626 """
627 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700628 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700629 heads = {}
630 pubd = {}
631
632 for name, id in all.iteritems():
633 if name.startswith(R_HEADS):
634 name = name[len(R_HEADS):]
635 b = self.GetBranch(name)
636 b.current = name == current
637 b.published = None
638 b.revision = id
639 heads[name] = b
640
641 for name, id in all.iteritems():
642 if name.startswith(R_PUB):
643 name = name[len(R_PUB):]
644 b = heads.get(name)
645 if b:
646 b.published = id
647
648 return heads
649
Colin Cross5acde752012-03-28 20:15:45 -0700650 def MatchesGroups(self, manifest_groups):
651 """Returns true if the manifest groups specified at init should cause
652 this project to be synced.
653 Prefixing a manifest group with "-" inverts the meaning of a group.
654 All projects are implicitly labelled with "default" unless they are
655 explicitly labelled "-default".
656 If any non-inverted manifest groups are specified, the default label
657 is ignored.
658 Specifying only inverted groups implies "default".
659 """
660 project_groups = self.groups
661 if not manifest_groups:
662 return not project_groups or not "-default" in project_groups
663
664 if not project_groups:
665 project_groups = ["default"]
666 elif not ("default" in project_groups or "-default" in project_groups):
667 project_groups.append("default")
668
669 plus_groups = [x for x in manifest_groups if not x.startswith("-")]
670 minus_groups = [x[1:] for x in manifest_groups if x.startswith("-")]
671
672 if not plus_groups:
673 plus_groups.append("default")
674
675 for group in minus_groups:
676 if group in project_groups:
677 # project was excluded by -group
678 return False
679
680 for group in plus_groups:
681 if group in project_groups:
682 # project was included by group
683 return True
684
685 # groups were specified that did not include this project
686 if plus_groups:
687 return False
688 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700689
690## Status Display ##
691
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500692 def HasChanges(self):
693 """Returns true if there are uncommitted changes.
694 """
695 self.work_git.update_index('-q',
696 '--unmerged',
697 '--ignore-missing',
698 '--refresh')
699 if self.IsRebaseInProgress():
700 return True
701
702 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
703 return True
704
705 if self.work_git.DiffZ('diff-files'):
706 return True
707
708 if self.work_git.LsOthers():
709 return True
710
711 return False
712
Terence Haddock4655e812011-03-31 12:33:34 +0200713 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700714 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200715
716 Args:
717 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700718 """
719 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200720 if output_redir == None:
721 output_redir = sys.stdout
722 print >>output_redir, ''
723 print >>output_redir, 'project %s/' % self.relpath
724 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700725 return
726
727 self.work_git.update_index('-q',
728 '--unmerged',
729 '--ignore-missing',
730 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700731 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
733 df = self.work_git.DiffZ('diff-files')
734 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100735 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700736 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737
738 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200739 if not output_redir == None:
740 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741 out.project('project %-40s', self.relpath + '/')
742
743 branch = self.CurrentBranch
744 if branch is None:
745 out.nobranch('(*** NO BRANCH ***)')
746 else:
747 out.branch('branch %s', branch)
748 out.nl()
749
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700750 if rb:
751 out.important('prior sync failed; rebase still in progress')
752 out.nl()
753
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700754 paths = list()
755 paths.extend(di.keys())
756 paths.extend(df.keys())
757 paths.extend(do)
758
759 paths = list(set(paths))
760 paths.sort()
761
762 for p in paths:
763 try: i = di[p]
764 except KeyError: i = None
765
766 try: f = df[p]
767 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200768
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700769 if i: i_status = i.status.upper()
770 else: i_status = '-'
771
772 if f: f_status = f.status.lower()
773 else: f_status = '-'
774
775 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800776 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700777 i.src_path, p, i.level)
778 else:
779 line = ' %s%s\t%s' % (i_status, f_status, p)
780
781 if i and not f:
782 out.added('%s', line)
783 elif (i and f) or (not i and f):
784 out.changed('%s', line)
785 elif not i and not f:
786 out.untracked('%s', line)
787 else:
788 out.write('%s', line)
789 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200790
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700791 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700792
pelyad67872d2012-03-28 14:49:58 +0300793 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794 """Prints the status of the repository to stdout.
795 """
796 out = DiffColoring(self.config)
797 cmd = ['diff']
798 if out.is_on:
799 cmd.append('--color')
800 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300801 if absolute_paths:
802 cmd.append('--src-prefix=a/%s/' % self.relpath)
803 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700804 cmd.append('--')
805 p = GitCommand(self,
806 cmd,
807 capture_stdout = True,
808 capture_stderr = True)
809 has_diff = False
810 for line in p.process.stdout:
811 if not has_diff:
812 out.nl()
813 out.project('project %s/' % self.relpath)
814 out.nl()
815 has_diff = True
816 print line[:-1]
817 p.Wait()
818
819
820## Publish / Upload ##
821
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700822 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700823 """Was the branch published (uploaded) for code review?
824 If so, returns the SHA-1 hash of the last published
825 state for the branch.
826 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700827 key = R_PUB + branch
828 if all is None:
829 try:
830 return self.bare_git.rev_parse(key)
831 except GitError:
832 return None
833 else:
834 try:
835 return all[key]
836 except KeyError:
837 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700839 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700840 """Prunes any stale published refs.
841 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700842 if all is None:
843 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844 heads = set()
845 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700846 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700847 if name.startswith(R_HEADS):
848 heads.add(name)
849 elif name.startswith(R_PUB):
850 canrm[name] = id
851
852 for name, id in canrm.iteritems():
853 n = name[len(R_PUB):]
854 if R_HEADS + n not in heads:
855 self.bare_git.DeleteRef(name, id)
856
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700857 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858 """List any branches which can be uploaded for review.
859 """
860 heads = {}
861 pubed = {}
862
863 for name, id in self._allrefs.iteritems():
864 if name.startswith(R_HEADS):
865 heads[name[len(R_HEADS):]] = id
866 elif name.startswith(R_PUB):
867 pubed[name[len(R_PUB):]] = id
868
869 ready = []
870 for branch, id in heads.iteritems():
871 if branch in pubed and pubed[branch] == id:
872 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700873 if selected_branch and branch != selected_branch:
874 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700875
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800876 rb = self.GetUploadableBranch(branch)
877 if rb:
878 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879 return ready
880
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800881 def GetUploadableBranch(self, branch_name):
882 """Get a single uploadable branch, or None.
883 """
884 branch = self.GetBranch(branch_name)
885 base = branch.LocalMerge
886 if branch.LocalMerge:
887 rb = ReviewableBranch(self, branch, base)
888 if rb.commits:
889 return rb
890 return None
891
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700892 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700893 people=([],[]),
894 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700895 """Uploads the named branch for code review.
896 """
897 if branch is None:
898 branch = self.CurrentBranch
899 if branch is None:
900 raise GitError('not currently on a branch')
901
902 branch = self.GetBranch(branch)
903 if not branch.LocalMerge:
904 raise GitError('branch %s does not track a remote' % branch.name)
905 if not branch.remote.review:
906 raise GitError('remote %s has no review url' % branch.remote.name)
907
908 dest_branch = branch.merge
909 if not dest_branch.startswith(R_HEADS):
910 dest_branch = R_HEADS + dest_branch
911
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800912 if not branch.remote.projectname:
913 branch.remote.projectname = self.name
914 branch.remote.Save()
915
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800916 url = branch.remote.ReviewUrl(self.UserEmail)
917 if url is None:
918 raise UploadError('review not configured')
919 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800920
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800921 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800922 rp = ['gerrit receive-pack']
923 for e in people[0]:
924 rp.append('--reviewer=%s' % sq(e))
925 for e in people[1]:
926 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800927 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700928
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800929 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800930
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800931 if dest_branch.startswith(R_HEADS):
932 dest_branch = dest_branch[len(R_HEADS):]
933 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
934 if auto_topic:
935 ref_spec = ref_spec + '/' + branch.name
936 cmd.append(ref_spec)
937
938 if GitCommand(self, cmd, bare = True).Wait() != 0:
939 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700940
941 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
942 self.bare_git.UpdateRef(R_PUB + branch.name,
943 R_HEADS + branch.name,
944 message = msg)
945
946
947## Sync ##
948
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700949 def Sync_NetworkHalf(self,
950 quiet=False,
951 is_new=None,
952 current_branch_only=False,
953 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954 """Perform only the network IO portion of the sync process.
955 Local working directory/branch state is not affected.
956 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700957 if is_new is None:
958 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200959 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700960 self._InitGitDir()
961 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700962
963 if is_new:
964 alt = os.path.join(self.gitdir, 'objects/info/alternates')
965 try:
966 fd = open(alt, 'rb')
967 try:
968 alt_dir = fd.readline().rstrip()
969 finally:
970 fd.close()
971 except IOError:
972 alt_dir = None
973 else:
974 alt_dir = None
975
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700976 if clone_bundle \
977 and alt_dir is None \
978 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700979 is_new = False
980
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700981 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
982 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800984
985 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800986 self._InitMRef()
987 else:
988 self._InitMirrorHead()
989 try:
990 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
991 except OSError:
992 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700993 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800994
995 def PostRepoUpgrade(self):
996 self._InitHooks()
997
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998 def _CopyFiles(self):
999 for file in self.copyfiles:
1000 file._Copy()
1001
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001002 def GetRevisionId(self, all=None):
1003 if self.revisionId:
1004 return self.revisionId
1005
1006 rem = self.GetRemote(self.remote.name)
1007 rev = rem.ToLocal(self.revisionExpr)
1008
1009 if all is not None and rev in all:
1010 return all[rev]
1011
1012 try:
1013 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1014 except GitError:
1015 raise ManifestInvalidRevisionError(
1016 'revision %s in %s not found' % (self.revisionExpr,
1017 self.name))
1018
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001019 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001020 """Perform only the local IO portion of the sync process.
1021 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001023 all = self.bare_ref.all
1024 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001025 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001026
1027 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001028 head = self.work_git.GetHead()
1029 if head.startswith(R_HEADS):
1030 branch = head[len(R_HEADS):]
1031 try:
1032 head = all[head]
1033 except KeyError:
1034 head = None
1035 else:
1036 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001038 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039 # Currently on a detached HEAD. The user is assumed to
1040 # not have any local modifications worth worrying about.
1041 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001042 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001043 syncbuf.fail(self, _PriorSyncFailedError())
1044 return
1045
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001046 if head == revid:
1047 # No changes; don't do anything further.
1048 #
1049 return
1050
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001051 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001052 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001053 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001055 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001056 except GitError, e:
1057 syncbuf.fail(self, e)
1058 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001060 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001062 if head == revid:
1063 # No changes; don't do anything further.
1064 #
1065 return
1066
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001068
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001069 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001071 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001073 syncbuf.info(self,
1074 "leaving %s; does not track upstream",
1075 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001077 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001078 except GitError, e:
1079 syncbuf.fail(self, e)
1080 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001082 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001084 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001085 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001087 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088 if not_merged:
1089 if upstream_gain:
1090 # The user has published this branch and some of those
1091 # commits are not yet merged upstream. We do not want
1092 # to rewrite the published commits so we punt.
1093 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001094 syncbuf.fail(self,
1095 "branch %s is published (but not merged) and is now %d commits behind"
1096 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001097 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001098 elif pub == head:
1099 # All published commits are merged, and thus we are a
1100 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001101 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001102 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001103 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001104 self._CopyFiles()
1105 syncbuf.later1(self, _doff)
1106 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001107
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001108 # Examine the local commits not in the remote. Find the
1109 # last one attributed to this user, if any.
1110 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001111 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001112 last_mine = None
1113 cnt_mine = 0
1114 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001115 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001116 if committer_email == self.UserEmail:
1117 last_mine = commit_id
1118 cnt_mine += 1
1119
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001120 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001121 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001122
1123 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001124 syncbuf.fail(self, _DirtyError())
1125 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001127 # If the upstream switched on us, warn the user.
1128 #
1129 if branch.merge != self.revisionExpr:
1130 if branch.merge and self.revisionExpr:
1131 syncbuf.info(self,
1132 'manifest switched %s...%s',
1133 branch.merge,
1134 self.revisionExpr)
1135 elif branch.merge:
1136 syncbuf.info(self,
1137 'manifest no longer tracks %s',
1138 branch.merge)
1139
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001140 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001141 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001142 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001143 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001144 syncbuf.info(self,
1145 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001146 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001147
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001148 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001149 if not ID_RE.match(self.revisionExpr):
1150 # in case of manifest sync the revisionExpr might be a SHA1
1151 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001152 branch.Save()
1153
Mike Pontillod3153822012-02-28 11:53:24 -08001154 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001155 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001156 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001157 self._CopyFiles()
1158 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001159 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001161 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001162 self._CopyFiles()
1163 except GitError, e:
1164 syncbuf.fail(self, e)
1165 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001166 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001167 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001168 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001169 self._CopyFiles()
1170 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001171
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001172 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001173 # dest should already be an absolute path, but src is project relative
1174 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001175 abssrc = os.path.join(self.worktree, src)
1176 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001177
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001178 def DownloadPatchSet(self, change_id, patch_id):
1179 """Download a single patch set of a single change to FETCH_HEAD.
1180 """
1181 remote = self.GetRemote(self.remote.name)
1182
1183 cmd = ['fetch', remote.name]
1184 cmd.append('refs/changes/%2.2d/%d/%d' \
1185 % (change_id % 100, change_id, patch_id))
1186 cmd.extend(map(lambda x: str(x), remote.fetch))
1187 if GitCommand(self, cmd, bare=True).Wait() != 0:
1188 return None
1189 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001190 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001191 change_id,
1192 patch_id,
1193 self.bare_git.rev_parse('FETCH_HEAD'))
1194
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195
1196## Branch Management ##
1197
1198 def StartBranch(self, name):
1199 """Create a new branch off the manifest's revision.
1200 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001201 head = self.work_git.GetHead()
1202 if head == (R_HEADS + name):
1203 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001205 all = self.bare_ref.all
1206 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001207 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001208 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001209 capture_stdout = True,
1210 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001211
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001212 branch = self.GetBranch(name)
1213 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001214 branch.merge = self.revisionExpr
1215 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001216
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001217 if head.startswith(R_HEADS):
1218 try:
1219 head = all[head]
1220 except KeyError:
1221 head = None
1222
1223 if revid and head and revid == head:
1224 ref = os.path.join(self.gitdir, R_HEADS + name)
1225 try:
1226 os.makedirs(os.path.dirname(ref))
1227 except OSError:
1228 pass
1229 _lwrite(ref, '%s\n' % revid)
1230 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1231 'ref: %s%s\n' % (R_HEADS, name))
1232 branch.Save()
1233 return True
1234
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001235 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001236 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001237 capture_stdout = True,
1238 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001239 branch.Save()
1240 return True
1241 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242
Wink Saville02d79452009-04-10 13:01:24 -07001243 def CheckoutBranch(self, name):
1244 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001245
1246 Args:
1247 name: The name of the branch to checkout.
1248
1249 Returns:
1250 True if the checkout succeeded; False if it didn't; None if the branch
1251 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001252 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001253 rev = R_HEADS + name
1254 head = self.work_git.GetHead()
1255 if head == rev:
1256 # Already on the branch
1257 #
1258 return True
Wink Saville02d79452009-04-10 13:01:24 -07001259
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001260 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001261 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001262 revid = all[rev]
1263 except KeyError:
1264 # Branch does not exist in this project
1265 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001266 return None
Wink Saville02d79452009-04-10 13:01:24 -07001267
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001268 if head.startswith(R_HEADS):
1269 try:
1270 head = all[head]
1271 except KeyError:
1272 head = None
1273
1274 if head == revid:
1275 # Same revision; just update HEAD to point to the new
1276 # target branch, but otherwise take no other action.
1277 #
1278 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1279 'ref: %s%s\n' % (R_HEADS, name))
1280 return True
1281
1282 return GitCommand(self,
1283 ['checkout', name, '--'],
1284 capture_stdout = True,
1285 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001286
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001287 def AbandonBranch(self, name):
1288 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001289
1290 Args:
1291 name: The name of the branch to abandon.
1292
1293 Returns:
1294 True if the abandon succeeded; False if it didn't; None if the branch
1295 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001296 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001297 rev = R_HEADS + name
1298 all = self.bare_ref.all
1299 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001300 # Doesn't exist
1301 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001302
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001303 head = self.work_git.GetHead()
1304 if head == rev:
1305 # We can't destroy the branch while we are sitting
1306 # on it. Switch to a detached HEAD.
1307 #
1308 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001309
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001310 revid = self.GetRevisionId(all)
1311 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001312 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1313 '%s\n' % revid)
1314 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001315 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001316
1317 return GitCommand(self,
1318 ['branch', '-D', name],
1319 capture_stdout = True,
1320 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001321
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 def PruneHeads(self):
1323 """Prune any topic branches already merged into upstream.
1324 """
1325 cb = self.CurrentBranch
1326 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001327 left = self._allrefs
1328 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329 if name.startswith(R_HEADS):
1330 name = name[len(R_HEADS):]
1331 if cb is None or name != cb:
1332 kill.append(name)
1333
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001334 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001335 if cb is not None \
1336 and not self._revlist(HEAD + '...' + rev) \
1337 and not self.IsDirty(consider_untracked = False):
1338 self.work_git.DetachHead(HEAD)
1339 kill.append(cb)
1340
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001341 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001342 old = self.bare_git.GetHead()
1343 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1345
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001346 try:
1347 self.bare_git.DetachHead(rev)
1348
1349 b = ['branch', '-d']
1350 b.extend(kill)
1351 b = GitCommand(self, b, bare=True,
1352 capture_stdout=True,
1353 capture_stderr=True)
1354 b.Wait()
1355 finally:
1356 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001357 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001359 for branch in kill:
1360 if (R_HEADS + branch) not in left:
1361 self.CleanPublishedCache()
1362 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363
1364 if cb and cb not in kill:
1365 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001366 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367
1368 kept = []
1369 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001370 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001371 branch = self.GetBranch(branch)
1372 base = branch.LocalMerge
1373 if not base:
1374 base = rev
1375 kept.append(ReviewableBranch(self, branch, base))
1376 return kept
1377
1378
1379## Direct Git Commands ##
1380
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001381 def _RemoteFetch(self, name=None,
1382 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001383 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001384 quiet=False,
1385 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001386
1387 is_sha1 = False
1388 tag_name = None
1389
1390 if current_branch_only:
1391 if ID_RE.match(self.revisionExpr) is not None:
1392 is_sha1 = True
1393 elif self.revisionExpr.startswith(R_TAGS):
1394 # this is a tag and its sha1 value should never change
1395 tag_name = self.revisionExpr[len(R_TAGS):]
1396
1397 if is_sha1 or tag_name is not None:
1398 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001399 # if revision (sha or tag) is not present then following function
1400 # throws an error.
1401 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001402 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001403 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001404 # There is no such persistent revision. We have to fetch it.
1405 pass
1406
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407 if not name:
1408 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001409
1410 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001411 remote = self.GetRemote(name)
1412 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001413 ssh_proxy = True
1414
Shawn O. Pearce88443382010-10-08 10:02:09 +02001415 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001416 if alt_dir and 'objects' == os.path.basename(alt_dir):
1417 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001418 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1419 remote = self.GetRemote(name)
1420
1421 all = self.bare_ref.all
1422 ids = set(all.values())
1423 tmp = set()
1424
1425 for r, id in GitRefs(ref_dir).all.iteritems():
1426 if r not in all:
1427 if r.startswith(R_TAGS) or remote.WritesTo(r):
1428 all[r] = id
1429 ids.add(id)
1430 continue
1431
1432 if id in ids:
1433 continue
1434
1435 r = 'refs/_alt/%s' % id
1436 all[r] = id
1437 ids.add(id)
1438 tmp.add(r)
1439
1440 ref_names = list(all.keys())
1441 ref_names.sort()
1442
1443 tmp_packed = ''
1444 old_packed = ''
1445
1446 for r in ref_names:
1447 line = '%s %s\n' % (all[r], r)
1448 tmp_packed += line
1449 if r not in tmp:
1450 old_packed += line
1451
1452 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001453 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001454 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001455
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001456 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001457
1458 # The --depth option only affects the initial fetch; after that we'll do
1459 # full fetches of changes.
1460 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1461 if depth and initial:
1462 cmd.append('--depth=%s' % depth)
1463
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001464 if quiet:
1465 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001466 if not self.worktree:
1467 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001468 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001469
1470 if not current_branch_only or is_sha1:
1471 # Fetch whole repo
1472 cmd.append('--tags')
1473 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1474 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001475 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001476 cmd.append(tag_name)
1477 else:
1478 branch = self.revisionExpr
1479 if branch.startswith(R_HEADS):
1480 branch = branch[len(R_HEADS):]
1481 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001482
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001483 ok = False
1484 for i in range(2):
1485 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1486 ok = True
1487 break
1488 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001489
1490 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001491 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001492 if old_packed != '':
1493 _lwrite(packed_refs, old_packed)
1494 else:
1495 os.remove(packed_refs)
1496 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001497 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001498
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001499 def _ApplyCloneBundle(self, initial=False, quiet=False):
1500 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1501 return False
1502
1503 remote = self.GetRemote(self.remote.name)
1504 bundle_url = remote.url + '/clone.bundle'
1505 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001506 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1507 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001508 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1509 return False
1510
1511 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1512 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1513
1514 exist_dst = os.path.exists(bundle_dst)
1515 exist_tmp = os.path.exists(bundle_tmp)
1516
1517 if not initial and not exist_dst and not exist_tmp:
1518 return False
1519
1520 if not exist_dst:
1521 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1522 if not exist_dst:
1523 return False
1524
1525 cmd = ['fetch']
1526 if quiet:
1527 cmd.append('--quiet')
1528 if not self.worktree:
1529 cmd.append('--update-head-ok')
1530 cmd.append(bundle_dst)
1531 for f in remote.fetch:
1532 cmd.append(str(f))
1533 cmd.append('refs/tags/*:refs/tags/*')
1534
1535 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001536 if os.path.exists(bundle_dst):
1537 os.remove(bundle_dst)
1538 if os.path.exists(bundle_tmp):
1539 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001540 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001541
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001542 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001543 keep = True
1544 done = False
1545 dest = open(tmpPath, 'a+b')
1546 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001547 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001548 pos = dest.tell()
1549
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001550 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001551 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001552 req = urllib2.Request(srcUrl)
1553 if pos > 0:
1554 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001555
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001556 try:
1557 r = urllib2.urlopen(req)
1558 except urllib2.HTTPError, e:
1559 def _content_type():
1560 try:
1561 return e.info()['content-type']
1562 except:
1563 return None
1564
Shawn O. Pearcec3d2f2b2012-03-22 14:09:22 -07001565 if e.code in (401, 403, 404):
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001566 keep = False
1567 return False
1568 elif _content_type() == 'text/plain':
1569 try:
1570 msg = e.read()
1571 if len(msg) > 0 and msg[-1] == '\n':
1572 msg = msg[0:-1]
1573 msg = ' (%s)' % msg
1574 except:
1575 msg = ''
1576 else:
1577 try:
1578 from BaseHTTPServer import BaseHTTPRequestHandler
1579 res = BaseHTTPRequestHandler.responses[e.code]
1580 msg = ' (%s: %s)' % (res[0], res[1])
1581 except:
1582 msg = ''
1583 raise DownloadError('HTTP %s%s' % (e.code, msg))
1584 except urllib2.URLError, e:
1585 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1586 finally:
1587 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001588
1589 p = None
1590 try:
Conley Owens43bda842012-03-12 11:25:04 -07001591 size = r.headers.get('content-length', 0)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001592 unit = 1 << 10
1593
1594 if size and not quiet:
1595 if size > 1024 * 1.3:
1596 unit = 1 << 20
1597 desc = 'MB'
1598 else:
1599 desc = 'KB'
1600 p = Progress(
1601 'Downloading %s' % self.relpath,
1602 int(size) / unit,
1603 units=desc)
1604 if pos > 0:
1605 p.update(pos / unit)
1606
1607 s = 0
1608 while True:
1609 d = r.read(8192)
1610 if d == '':
1611 done = True
1612 return True
1613 dest.write(d)
1614 if p:
1615 s += len(d)
1616 if s >= unit:
1617 p.update(s / unit)
1618 s = s % unit
1619 if p:
1620 if s >= unit:
1621 p.update(s / unit)
1622 else:
1623 p.update(1)
1624 finally:
1625 r.close()
1626 if p:
1627 p.end()
1628 finally:
1629 dest.close()
1630
1631 if os.path.exists(dstPath):
1632 os.remove(dstPath)
1633 if done:
1634 os.rename(tmpPath, dstPath)
1635 elif not keep:
1636 os.remove(tmpPath)
1637
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001638 def _Checkout(self, rev, quiet=False):
1639 cmd = ['checkout']
1640 if quiet:
1641 cmd.append('-q')
1642 cmd.append(rev)
1643 cmd.append('--')
1644 if GitCommand(self, cmd).Wait() != 0:
1645 if self._allrefs:
1646 raise GitError('%s checkout %s ' % (self.name, rev))
1647
1648 def _ResetHard(self, rev, quiet=True):
1649 cmd = ['reset', '--hard']
1650 if quiet:
1651 cmd.append('-q')
1652 cmd.append(rev)
1653 if GitCommand(self, cmd).Wait() != 0:
1654 raise GitError('%s reset --hard %s ' % (self.name, rev))
1655
1656 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001657 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001658 if onto is not None:
1659 cmd.extend(['--onto', onto])
1660 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001661 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001662 raise GitError('%s rebase %s ' % (self.name, upstream))
1663
1664 def _FastForward(self, head):
1665 cmd = ['merge', head]
1666 if GitCommand(self, cmd).Wait() != 0:
1667 raise GitError('%s merge %s ' % (self.name, head))
1668
1669 def _InitGitDir(self):
1670 if not os.path.exists(self.gitdir):
1671 os.makedirs(self.gitdir)
1672 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001673
Shawn O. Pearce88443382010-10-08 10:02:09 +02001674 mp = self.manifest.manifestProject
1675 ref_dir = mp.config.GetString('repo.reference')
1676
1677 if ref_dir:
1678 mirror_git = os.path.join(ref_dir, self.name + '.git')
1679 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1680 self.relpath + '.git')
1681
1682 if os.path.exists(mirror_git):
1683 ref_dir = mirror_git
1684
1685 elif os.path.exists(repo_git):
1686 ref_dir = repo_git
1687
1688 else:
1689 ref_dir = None
1690
1691 if ref_dir:
1692 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1693 os.path.join(ref_dir, 'objects') + '\n')
1694
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001695 if self.manifest.IsMirror:
1696 self.config.SetString('core.bare', 'true')
1697 else:
1698 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001699
1700 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001701 try:
1702 to_rm = os.listdir(hooks)
1703 except OSError:
1704 to_rm = []
1705 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001706 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001707 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001708
1709 m = self.manifest.manifestProject.config
1710 for key in ['user.name', 'user.email']:
1711 if m.Has(key, include_defaults = False):
1712 self.config.SetString(key, m.GetString(key))
1713
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001714 def _InitHooks(self):
1715 hooks = self._gitdir_path('hooks')
1716 if not os.path.exists(hooks):
1717 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001718 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001719 name = os.path.basename(stock_hook)
1720
Victor Boivie65e0f352011-04-18 11:23:29 +02001721 if name in ('commit-msg',) and not self.remote.review \
1722 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001723 # Don't install a Gerrit Code Review hook if this
1724 # project does not appear to use it for reviews.
1725 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001726 # Since the manifest project is one of those, but also
1727 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001728 continue
1729
1730 dst = os.path.join(hooks, name)
1731 if os.path.islink(dst):
1732 continue
1733 if os.path.exists(dst):
1734 if filecmp.cmp(stock_hook, dst, shallow=False):
1735 os.remove(dst)
1736 else:
1737 _error("%s: Not replacing %s hook", self.relpath, name)
1738 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001739 try:
1740 os.symlink(relpath(stock_hook, dst), dst)
1741 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001742 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001743 raise GitError('filesystem must support symlinks')
1744 else:
1745 raise
1746
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001747 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001748 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001749 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001750 remote.url = self.remote.url
1751 remote.review = self.remote.review
1752 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001753
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001754 if self.worktree:
1755 remote.ResetFetch(mirror=False)
1756 else:
1757 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758 remote.Save()
1759
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001760 def _InitMRef(self):
1761 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001762 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001763
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001764 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001765 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001766
1767 def _InitAnyMRef(self, ref):
1768 cur = self.bare_ref.symref(ref)
1769
1770 if self.revisionId:
1771 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1772 msg = 'manifest set to %s' % self.revisionId
1773 dst = self.revisionId + '^0'
1774 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1775 else:
1776 remote = self.GetRemote(self.remote.name)
1777 dst = remote.ToLocal(self.revisionExpr)
1778 if cur != dst:
1779 msg = 'manifest set to %s' % self.revisionExpr
1780 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001781
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001782 def _InitWorkTree(self):
1783 dotgit = os.path.join(self.worktree, '.git')
1784 if not os.path.exists(dotgit):
1785 os.makedirs(dotgit)
1786
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001787 for name in ['config',
1788 'description',
1789 'hooks',
1790 'info',
1791 'logs',
1792 'objects',
1793 'packed-refs',
1794 'refs',
1795 'rr-cache',
1796 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001797 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001798 src = os.path.join(self.gitdir, name)
1799 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001800 if os.path.islink(dst) or not os.path.exists(dst):
1801 os.symlink(relpath(src, dst), dst)
1802 else:
1803 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001804 except OSError, e:
1805 if e.errno == errno.EPERM:
1806 raise GitError('filesystem must support symlinks')
1807 else:
1808 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001809
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001810 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001811
1812 cmd = ['read-tree', '--reset', '-u']
1813 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001814 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001815 if GitCommand(self, cmd).Wait() != 0:
1816 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001817
1818 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1819 if not os.path.exists(rr_cache):
1820 os.makedirs(rr_cache)
1821
Shawn O. Pearce93609662009-04-21 10:50:33 -07001822 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001823
1824 def _gitdir_path(self, path):
1825 return os.path.join(self.gitdir, path)
1826
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001827 def _revlist(self, *args, **kw):
1828 a = []
1829 a.extend(args)
1830 a.append('--')
1831 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001832
1833 @property
1834 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001835 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001836
1837 class _GitGetByExec(object):
1838 def __init__(self, project, bare):
1839 self._project = project
1840 self._bare = bare
1841
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001842 def LsOthers(self):
1843 p = GitCommand(self._project,
1844 ['ls-files',
1845 '-z',
1846 '--others',
1847 '--exclude-standard'],
1848 bare = False,
1849 capture_stdout = True,
1850 capture_stderr = True)
1851 if p.Wait() == 0:
1852 out = p.stdout
1853 if out:
1854 return out[:-1].split("\0")
1855 return []
1856
1857 def DiffZ(self, name, *args):
1858 cmd = [name]
1859 cmd.append('-z')
1860 cmd.extend(args)
1861 p = GitCommand(self._project,
1862 cmd,
1863 bare = False,
1864 capture_stdout = True,
1865 capture_stderr = True)
1866 try:
1867 out = p.process.stdout.read()
1868 r = {}
1869 if out:
1870 out = iter(out[:-1].split('\0'))
1871 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001872 try:
1873 info = out.next()
1874 path = out.next()
1875 except StopIteration:
1876 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001877
1878 class _Info(object):
1879 def __init__(self, path, omode, nmode, oid, nid, state):
1880 self.path = path
1881 self.src_path = None
1882 self.old_mode = omode
1883 self.new_mode = nmode
1884 self.old_id = oid
1885 self.new_id = nid
1886
1887 if len(state) == 1:
1888 self.status = state
1889 self.level = None
1890 else:
1891 self.status = state[:1]
1892 self.level = state[1:]
1893 while self.level.startswith('0'):
1894 self.level = self.level[1:]
1895
1896 info = info[1:].split(' ')
1897 info =_Info(path, *info)
1898 if info.status in ('R', 'C'):
1899 info.src_path = info.path
1900 info.path = out.next()
1901 r[info.path] = info
1902 return r
1903 finally:
1904 p.Wait()
1905
1906 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001907 if self._bare:
1908 path = os.path.join(self._project.gitdir, HEAD)
1909 else:
1910 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001911 fd = open(path, 'rb')
1912 try:
1913 line = fd.read()
1914 finally:
1915 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001916 if line.startswith('ref: '):
1917 return line[5:-1]
1918 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001919
1920 def SetHead(self, ref, message=None):
1921 cmdv = []
1922 if message is not None:
1923 cmdv.extend(['-m', message])
1924 cmdv.append(HEAD)
1925 cmdv.append(ref)
1926 self.symbolic_ref(*cmdv)
1927
1928 def DetachHead(self, new, message=None):
1929 cmdv = ['--no-deref']
1930 if message is not None:
1931 cmdv.extend(['-m', message])
1932 cmdv.append(HEAD)
1933 cmdv.append(new)
1934 self.update_ref(*cmdv)
1935
1936 def UpdateRef(self, name, new, old=None,
1937 message=None,
1938 detach=False):
1939 cmdv = []
1940 if message is not None:
1941 cmdv.extend(['-m', message])
1942 if detach:
1943 cmdv.append('--no-deref')
1944 cmdv.append(name)
1945 cmdv.append(new)
1946 if old is not None:
1947 cmdv.append(old)
1948 self.update_ref(*cmdv)
1949
1950 def DeleteRef(self, name, old=None):
1951 if not old:
1952 old = self.rev_parse(name)
1953 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001954 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001955
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001956 def rev_list(self, *args, **kw):
1957 if 'format' in kw:
1958 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1959 else:
1960 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001961 cmdv.extend(args)
1962 p = GitCommand(self._project,
1963 cmdv,
1964 bare = self._bare,
1965 capture_stdout = True,
1966 capture_stderr = True)
1967 r = []
1968 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001969 if line[-1] == '\n':
1970 line = line[:-1]
1971 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001972 if p.Wait() != 0:
1973 raise GitError('%s rev-list %s: %s' % (
1974 self._project.name,
1975 str(args),
1976 p.stderr))
1977 return r
1978
1979 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001980 """Allow arbitrary git commands using pythonic syntax.
1981
1982 This allows you to do things like:
1983 git_obj.rev_parse('HEAD')
1984
1985 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1986 run. We'll replace the '_' with a '-' and try to run a git command.
1987 Any other arguments will be passed to the git command.
1988
1989 Args:
1990 name: The name of the git command to call. Any '_' characters will
1991 be replaced with '-'.
1992
1993 Returns:
1994 A callable object that will try to call git with the named command.
1995 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001996 name = name.replace('_', '-')
1997 def runner(*args):
1998 cmdv = [name]
1999 cmdv.extend(args)
2000 p = GitCommand(self._project,
2001 cmdv,
2002 bare = self._bare,
2003 capture_stdout = True,
2004 capture_stderr = True)
2005 if p.Wait() != 0:
2006 raise GitError('%s %s: %s' % (
2007 self._project.name,
2008 name,
2009 p.stderr))
2010 r = p.stdout
2011 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2012 return r[:-1]
2013 return r
2014 return runner
2015
2016
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002017class _PriorSyncFailedError(Exception):
2018 def __str__(self):
2019 return 'prior sync failed; rebase still in progress'
2020
2021class _DirtyError(Exception):
2022 def __str__(self):
2023 return 'contains uncommitted changes'
2024
2025class _InfoMessage(object):
2026 def __init__(self, project, text):
2027 self.project = project
2028 self.text = text
2029
2030 def Print(self, syncbuf):
2031 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2032 syncbuf.out.nl()
2033
2034class _Failure(object):
2035 def __init__(self, project, why):
2036 self.project = project
2037 self.why = why
2038
2039 def Print(self, syncbuf):
2040 syncbuf.out.fail('error: %s/: %s',
2041 self.project.relpath,
2042 str(self.why))
2043 syncbuf.out.nl()
2044
2045class _Later(object):
2046 def __init__(self, project, action):
2047 self.project = project
2048 self.action = action
2049
2050 def Run(self, syncbuf):
2051 out = syncbuf.out
2052 out.project('project %s/', self.project.relpath)
2053 out.nl()
2054 try:
2055 self.action()
2056 out.nl()
2057 return True
2058 except GitError, e:
2059 out.nl()
2060 return False
2061
2062class _SyncColoring(Coloring):
2063 def __init__(self, config):
2064 Coloring.__init__(self, config, 'reposync')
2065 self.project = self.printer('header', attr = 'bold')
2066 self.info = self.printer('info')
2067 self.fail = self.printer('fail', fg='red')
2068
2069class SyncBuffer(object):
2070 def __init__(self, config, detach_head=False):
2071 self._messages = []
2072 self._failures = []
2073 self._later_queue1 = []
2074 self._later_queue2 = []
2075
2076 self.out = _SyncColoring(config)
2077 self.out.redirect(sys.stderr)
2078
2079 self.detach_head = detach_head
2080 self.clean = True
2081
2082 def info(self, project, fmt, *args):
2083 self._messages.append(_InfoMessage(project, fmt % args))
2084
2085 def fail(self, project, err=None):
2086 self._failures.append(_Failure(project, err))
2087 self.clean = False
2088
2089 def later1(self, project, what):
2090 self._later_queue1.append(_Later(project, what))
2091
2092 def later2(self, project, what):
2093 self._later_queue2.append(_Later(project, what))
2094
2095 def Finish(self):
2096 self._PrintMessages()
2097 self._RunLater()
2098 self._PrintMessages()
2099 return self.clean
2100
2101 def _RunLater(self):
2102 for q in ['_later_queue1', '_later_queue2']:
2103 if not self._RunQueue(q):
2104 return
2105
2106 def _RunQueue(self, queue):
2107 for m in getattr(self, queue):
2108 if not m.Run(self):
2109 self.clean = False
2110 return False
2111 setattr(self, queue, [])
2112 return True
2113
2114 def _PrintMessages(self):
2115 for m in self._messages:
2116 m.Print(self)
2117 for m in self._failures:
2118 m.Print(self)
2119
2120 self._messages = []
2121 self._failures = []
2122
2123
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002124class MetaProject(Project):
2125 """A special project housed under .repo.
2126 """
2127 def __init__(self, manifest, name, gitdir, worktree):
2128 repodir = manifest.repodir
2129 Project.__init__(self,
2130 manifest = manifest,
2131 name = name,
2132 gitdir = gitdir,
2133 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002134 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002135 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002136 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002137 revisionId = None,
2138 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002139
2140 def PreSync(self):
2141 if self.Exists:
2142 cb = self.CurrentBranch
2143 if cb:
2144 base = self.GetBranch(cb).merge
2145 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002146 self.revisionExpr = base
2147 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002148
2149 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002150 def LastFetch(self):
2151 try:
2152 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2153 return os.path.getmtime(fh)
2154 except OSError:
2155 return 0
2156
2157 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002158 def HasChanges(self):
2159 """Has the remote received new commits not yet checked out?
2160 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002161 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002162 return False
2163
2164 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002165 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002166 head = self.work_git.GetHead()
2167 if head.startswith(R_HEADS):
2168 try:
2169 head = all[head]
2170 except KeyError:
2171 head = None
2172
2173 if revid == head:
2174 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002175 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002176 return True
2177 return False