blob: 2b7400079d12487f072d118b23536a356f252fda [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
James W. Mills24c13082012-04-12 15:04:13 -0500216class _Annotation:
217 def __init__(self, name, value, keep):
218 self.name = name
219 self.value = value
220 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221
222class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800223 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224 self.src = src
225 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800226 self.abs_src = abssrc
227 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228
229 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800230 src = self.abs_src
231 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232 # copy file if it does not exist or is out of date
233 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
234 try:
235 # remove existing file first, since it might be read-only
236 if os.path.exists(dest):
237 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400238 else:
239 dir = os.path.dirname(dest)
240 if not os.path.isdir(dir):
241 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242 shutil.copy(src, dest)
243 # make the file read-only
244 mode = os.stat(dest)[stat.ST_MODE]
245 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
246 os.chmod(dest, mode)
247 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700248 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700249
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700250class RemoteSpec(object):
251 def __init__(self,
252 name,
253 url = None,
254 review = None):
255 self.name = name
256 self.url = url
257 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700258
Doug Anderson37282b42011-03-04 11:54:18 -0800259class RepoHook(object):
260 """A RepoHook contains information about a script to run as a hook.
261
262 Hooks are used to run a python script before running an upload (for instance,
263 to run presubmit checks). Eventually, we may have hooks for other actions.
264
265 This shouldn't be confused with files in the 'repo/hooks' directory. Those
266 files are copied into each '.git/hooks' folder for each project. Repo-level
267 hooks are associated instead with repo actions.
268
269 Hooks are always python. When a hook is run, we will load the hook into the
270 interpreter and execute its main() function.
271 """
272 def __init__(self,
273 hook_type,
274 hooks_project,
275 topdir,
276 abort_if_user_denies=False):
277 """RepoHook constructor.
278
279 Params:
280 hook_type: A string representing the type of hook. This is also used
281 to figure out the name of the file containing the hook. For
282 example: 'pre-upload'.
283 hooks_project: The project containing the repo hooks. If you have a
284 manifest, this is manifest.repo_hooks_project. OK if this is None,
285 which will make the hook a no-op.
286 topdir: Repo's top directory (the one containing the .repo directory).
287 Scripts will run with CWD as this directory. If you have a manifest,
288 this is manifest.topdir
289 abort_if_user_denies: If True, we'll throw a HookError() if the user
290 doesn't allow us to run the hook.
291 """
292 self._hook_type = hook_type
293 self._hooks_project = hooks_project
294 self._topdir = topdir
295 self._abort_if_user_denies = abort_if_user_denies
296
297 # Store the full path to the script for convenience.
298 if self._hooks_project:
299 self._script_fullpath = os.path.join(self._hooks_project.worktree,
300 self._hook_type + '.py')
301 else:
302 self._script_fullpath = None
303
304 def _GetHash(self):
305 """Return a hash of the contents of the hooks directory.
306
307 We'll just use git to do this. This hash has the property that if anything
308 changes in the directory we will return a different has.
309
310 SECURITY CONSIDERATION:
311 This hash only represents the contents of files in the hook directory, not
312 any other files imported or called by hooks. Changes to imported files
313 can change the script behavior without affecting the hash.
314
315 Returns:
316 A string representing the hash. This will always be ASCII so that it can
317 be printed to the user easily.
318 """
319 assert self._hooks_project, "Must have hooks to calculate their hash."
320
321 # We will use the work_git object rather than just calling GetRevisionId().
322 # That gives us a hash of the latest checked in version of the files that
323 # the user will actually be executing. Specifically, GetRevisionId()
324 # doesn't appear to change even if a user checks out a different version
325 # of the hooks repo (via git checkout) nor if a user commits their own revs.
326 #
327 # NOTE: Local (non-committed) changes will not be factored into this hash.
328 # I think this is OK, since we're really only worried about warning the user
329 # about upstream changes.
330 return self._hooks_project.work_git.rev_parse('HEAD')
331
332 def _GetMustVerb(self):
333 """Return 'must' if the hook is required; 'should' if not."""
334 if self._abort_if_user_denies:
335 return 'must'
336 else:
337 return 'should'
338
339 def _CheckForHookApproval(self):
340 """Check to see whether this hook has been approved.
341
342 We'll look at the hash of all of the hooks. If this matches the hash that
343 the user last approved, we're done. If it doesn't, we'll ask the user
344 about approval.
345
346 Note that we ask permission for each individual hook even though we use
347 the hash of all hooks when detecting changes. We'd like the user to be
348 able to approve / deny each hook individually. We only use the hash of all
349 hooks because there is no other easy way to detect changes to local imports.
350
351 Returns:
352 True if this hook is approved to run; False otherwise.
353
354 Raises:
355 HookError: Raised if the user doesn't approve and abort_if_user_denies
356 was passed to the consturctor.
357 """
358 hooks_dir = self._hooks_project.worktree
359 hooks_config = self._hooks_project.config
360 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
361
362 # Get the last hash that the user approved for this hook; may be None.
363 old_hash = hooks_config.GetString(git_approval_key)
364
365 # Get the current hash so we can tell if scripts changed since approval.
366 new_hash = self._GetHash()
367
368 if old_hash is not None:
369 # User previously approved hook and asked not to be prompted again.
370 if new_hash == old_hash:
371 # Approval matched. We're done.
372 return True
373 else:
374 # Give the user a reason why we're prompting, since they last told
375 # us to "never ask again".
376 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
377 self._hook_type)
378 else:
379 prompt = ''
380
381 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
382 if sys.stdout.isatty():
383 prompt += ('Repo %s run the script:\n'
384 ' %s\n'
385 '\n'
386 'Do you want to allow this script to run '
387 '(yes/yes-never-ask-again/NO)? ') % (
388 self._GetMustVerb(), self._script_fullpath)
389 response = raw_input(prompt).lower()
390 print
391
392 # User is doing a one-time approval.
393 if response in ('y', 'yes'):
394 return True
395 elif response == 'yes-never-ask-again':
396 hooks_config.SetString(git_approval_key, new_hash)
397 return True
398
399 # For anything else, we'll assume no approval.
400 if self._abort_if_user_denies:
401 raise HookError('You must allow the %s hook or use --no-verify.' %
402 self._hook_type)
403
404 return False
405
406 def _ExecuteHook(self, **kwargs):
407 """Actually execute the given hook.
408
409 This will run the hook's 'main' function in our python interpreter.
410
411 Args:
412 kwargs: Keyword arguments to pass to the hook. These are often specific
413 to the hook type. For instance, pre-upload hooks will contain
414 a project_list.
415 """
416 # Keep sys.path and CWD stashed away so that we can always restore them
417 # upon function exit.
418 orig_path = os.getcwd()
419 orig_syspath = sys.path
420
421 try:
422 # Always run hooks with CWD as topdir.
423 os.chdir(self._topdir)
424
425 # Put the hook dir as the first item of sys.path so hooks can do
426 # relative imports. We want to replace the repo dir as [0] so
427 # hooks can't import repo files.
428 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
429
430 # Exec, storing global context in the context dict. We catch exceptions
431 # and convert to a HookError w/ just the failing traceback.
432 context = {}
433 try:
434 execfile(self._script_fullpath, context)
435 except Exception:
436 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
437 traceback.format_exc(), self._hook_type))
438
439 # Running the script should have defined a main() function.
440 if 'main' not in context:
441 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
442
443
444 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
445 # We don't actually want hooks to define their main with this argument--
446 # it's there to remind them that their hook should always take **kwargs.
447 # For instance, a pre-upload hook should be defined like:
448 # def main(project_list, **kwargs):
449 #
450 # This allows us to later expand the API without breaking old hooks.
451 kwargs = kwargs.copy()
452 kwargs['hook_should_take_kwargs'] = True
453
454 # Call the main function in the hook. If the hook should cause the
455 # build to fail, it will raise an Exception. We'll catch that convert
456 # to a HookError w/ just the failing traceback.
457 try:
458 context['main'](**kwargs)
459 except Exception:
460 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
461 'above.' % (
462 traceback.format_exc(), self._hook_type))
463 finally:
464 # Restore sys.path and CWD.
465 sys.path = orig_syspath
466 os.chdir(orig_path)
467
468 def Run(self, user_allows_all_hooks, **kwargs):
469 """Run the hook.
470
471 If the hook doesn't exist (because there is no hooks project or because
472 this particular hook is not enabled), this is a no-op.
473
474 Args:
475 user_allows_all_hooks: If True, we will never prompt about running the
476 hook--we'll just assume it's OK to run it.
477 kwargs: Keyword arguments to pass to the hook. These are often specific
478 to the hook type. For instance, pre-upload hooks will contain
479 a project_list.
480
481 Raises:
482 HookError: If there was a problem finding the hook or the user declined
483 to run a required hook (from _CheckForHookApproval).
484 """
485 # No-op if there is no hooks project or if hook is disabled.
486 if ((not self._hooks_project) or
487 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
488 return
489
490 # Bail with a nice error if we can't find the hook.
491 if not os.path.isfile(self._script_fullpath):
492 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
493
494 # Make sure the user is OK with running the hook.
495 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
496 return
497
498 # Run the hook with the same version of python we're using.
499 self._ExecuteHook(**kwargs)
500
501
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700502class Project(object):
503 def __init__(self,
504 manifest,
505 name,
506 remote,
507 gitdir,
508 worktree,
509 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700510 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800511 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700512 rebase = True,
513 groups = None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700514 self.manifest = manifest
515 self.name = name
516 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800517 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800518 if worktree:
519 self.worktree = worktree.replace('\\', '/')
520 else:
521 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700522 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700523 self.revisionExpr = revisionExpr
524
525 if revisionId is None \
526 and revisionExpr \
527 and IsId(revisionExpr):
528 self.revisionId = revisionExpr
529 else:
530 self.revisionId = revisionId
531
Mike Pontillod3153822012-02-28 11:53:24 -0800532 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700533 self.groups = groups
Mike Pontillod3153822012-02-28 11:53:24 -0800534
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700535 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700536 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500537 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700538 self.config = GitConfig.ForRepository(
539 gitdir = self.gitdir,
540 defaults = self.manifest.globalConfig)
541
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800542 if self.worktree:
543 self.work_git = self._GitGetByExec(self, bare=False)
544 else:
545 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700546 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700547 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700548
Doug Anderson37282b42011-03-04 11:54:18 -0800549 # This will be filled in if a project is later identified to be the
550 # project containing repo hooks.
551 self.enabled_repo_hooks = []
552
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700553 @property
554 def Exists(self):
555 return os.path.isdir(self.gitdir)
556
557 @property
558 def CurrentBranch(self):
559 """Obtain the name of the currently checked out branch.
560 The branch name omits the 'refs/heads/' prefix.
561 None is returned if the project is on a detached HEAD.
562 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700563 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700564 if b.startswith(R_HEADS):
565 return b[len(R_HEADS):]
566 return None
567
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700568 def IsRebaseInProgress(self):
569 w = self.worktree
570 g = os.path.join(w, '.git')
571 return os.path.exists(os.path.join(g, 'rebase-apply')) \
572 or os.path.exists(os.path.join(g, 'rebase-merge')) \
573 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200574
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700575 def IsDirty(self, consider_untracked=True):
576 """Is the working directory modified in some way?
577 """
578 self.work_git.update_index('-q',
579 '--unmerged',
580 '--ignore-missing',
581 '--refresh')
582 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
583 return True
584 if self.work_git.DiffZ('diff-files'):
585 return True
586 if consider_untracked and self.work_git.LsOthers():
587 return True
588 return False
589
590 _userident_name = None
591 _userident_email = None
592
593 @property
594 def UserName(self):
595 """Obtain the user's personal name.
596 """
597 if self._userident_name is None:
598 self._LoadUserIdentity()
599 return self._userident_name
600
601 @property
602 def UserEmail(self):
603 """Obtain the user's email address. This is very likely
604 to be their Gerrit login.
605 """
606 if self._userident_email is None:
607 self._LoadUserIdentity()
608 return self._userident_email
609
610 def _LoadUserIdentity(self):
611 u = self.bare_git.var('GIT_COMMITTER_IDENT')
612 m = re.compile("^(.*) <([^>]*)> ").match(u)
613 if m:
614 self._userident_name = m.group(1)
615 self._userident_email = m.group(2)
616 else:
617 self._userident_name = ''
618 self._userident_email = ''
619
620 def GetRemote(self, name):
621 """Get the configuration for a single remote.
622 """
623 return self.config.GetRemote(name)
624
625 def GetBranch(self, name):
626 """Get the configuration for a single branch.
627 """
628 return self.config.GetBranch(name)
629
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700630 def GetBranches(self):
631 """Get all existing local branches.
632 """
633 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700634 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700635 heads = {}
636 pubd = {}
637
638 for name, id in all.iteritems():
639 if name.startswith(R_HEADS):
640 name = name[len(R_HEADS):]
641 b = self.GetBranch(name)
642 b.current = name == current
643 b.published = None
644 b.revision = id
645 heads[name] = b
646
647 for name, id in all.iteritems():
648 if name.startswith(R_PUB):
649 name = name[len(R_PUB):]
650 b = heads.get(name)
651 if b:
652 b.published = id
653
654 return heads
655
Colin Cross5acde752012-03-28 20:15:45 -0700656 def MatchesGroups(self, manifest_groups):
657 """Returns true if the manifest groups specified at init should cause
658 this project to be synced.
659 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owens971de8e2012-04-16 10:36:08 -0700660 All projects are implicitly labelled with "default".
Colin Cross5acde752012-03-28 20:15:45 -0700661
Conley Owens971de8e2012-04-16 10:36:08 -0700662 labels are resolved in order. In the example case of
663 project_groups: "default,group1,group2"
664 manifest_groups: "-group1,group2"
665 the project will be matched.
666 """
667 matched = False
668 for group in manifest_groups:
669 if group.startswith('-') and group[1:] in self.groups:
670 matched = False
671 elif group in self.groups:
672 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700673
Conley Owens971de8e2012-04-16 10:36:08 -0700674 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700675
676## Status Display ##
677
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500678 def HasChanges(self):
679 """Returns true if there are uncommitted changes.
680 """
681 self.work_git.update_index('-q',
682 '--unmerged',
683 '--ignore-missing',
684 '--refresh')
685 if self.IsRebaseInProgress():
686 return True
687
688 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
689 return True
690
691 if self.work_git.DiffZ('diff-files'):
692 return True
693
694 if self.work_git.LsOthers():
695 return True
696
697 return False
698
Terence Haddock4655e812011-03-31 12:33:34 +0200699 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700700 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200701
702 Args:
703 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700704 """
705 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200706 if output_redir == None:
707 output_redir = sys.stdout
708 print >>output_redir, ''
709 print >>output_redir, 'project %s/' % self.relpath
710 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700711 return
712
713 self.work_git.update_index('-q',
714 '--unmerged',
715 '--ignore-missing',
716 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700717 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700718 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
719 df = self.work_git.DiffZ('diff-files')
720 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100721 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700722 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700723
724 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200725 if not output_redir == None:
726 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700727 out.project('project %-40s', self.relpath + '/')
728
729 branch = self.CurrentBranch
730 if branch is None:
731 out.nobranch('(*** NO BRANCH ***)')
732 else:
733 out.branch('branch %s', branch)
734 out.nl()
735
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700736 if rb:
737 out.important('prior sync failed; rebase still in progress')
738 out.nl()
739
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700740 paths = list()
741 paths.extend(di.keys())
742 paths.extend(df.keys())
743 paths.extend(do)
744
745 paths = list(set(paths))
746 paths.sort()
747
748 for p in paths:
749 try: i = di[p]
750 except KeyError: i = None
751
752 try: f = df[p]
753 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200754
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700755 if i: i_status = i.status.upper()
756 else: i_status = '-'
757
758 if f: f_status = f.status.lower()
759 else: f_status = '-'
760
761 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800762 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700763 i.src_path, p, i.level)
764 else:
765 line = ' %s%s\t%s' % (i_status, f_status, p)
766
767 if i and not f:
768 out.added('%s', line)
769 elif (i and f) or (not i and f):
770 out.changed('%s', line)
771 elif not i and not f:
772 out.untracked('%s', line)
773 else:
774 out.write('%s', line)
775 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200776
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700777 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700778
pelyad67872d2012-03-28 14:49:58 +0300779 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780 """Prints the status of the repository to stdout.
781 """
782 out = DiffColoring(self.config)
783 cmd = ['diff']
784 if out.is_on:
785 cmd.append('--color')
786 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300787 if absolute_paths:
788 cmd.append('--src-prefix=a/%s/' % self.relpath)
789 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700790 cmd.append('--')
791 p = GitCommand(self,
792 cmd,
793 capture_stdout = True,
794 capture_stderr = True)
795 has_diff = False
796 for line in p.process.stdout:
797 if not has_diff:
798 out.nl()
799 out.project('project %s/' % self.relpath)
800 out.nl()
801 has_diff = True
802 print line[:-1]
803 p.Wait()
804
805
806## Publish / Upload ##
807
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700808 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809 """Was the branch published (uploaded) for code review?
810 If so, returns the SHA-1 hash of the last published
811 state for the branch.
812 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700813 key = R_PUB + branch
814 if all is None:
815 try:
816 return self.bare_git.rev_parse(key)
817 except GitError:
818 return None
819 else:
820 try:
821 return all[key]
822 except KeyError:
823 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700825 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700826 """Prunes any stale published refs.
827 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700828 if all is None:
829 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830 heads = set()
831 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700832 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700833 if name.startswith(R_HEADS):
834 heads.add(name)
835 elif name.startswith(R_PUB):
836 canrm[name] = id
837
838 for name, id in canrm.iteritems():
839 n = name[len(R_PUB):]
840 if R_HEADS + n not in heads:
841 self.bare_git.DeleteRef(name, id)
842
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700843 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844 """List any branches which can be uploaded for review.
845 """
846 heads = {}
847 pubed = {}
848
849 for name, id in self._allrefs.iteritems():
850 if name.startswith(R_HEADS):
851 heads[name[len(R_HEADS):]] = id
852 elif name.startswith(R_PUB):
853 pubed[name[len(R_PUB):]] = id
854
855 ready = []
856 for branch, id in heads.iteritems():
857 if branch in pubed and pubed[branch] == id:
858 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700859 if selected_branch and branch != selected_branch:
860 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700861
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800862 rb = self.GetUploadableBranch(branch)
863 if rb:
864 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865 return ready
866
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800867 def GetUploadableBranch(self, branch_name):
868 """Get a single uploadable branch, or None.
869 """
870 branch = self.GetBranch(branch_name)
871 base = branch.LocalMerge
872 if branch.LocalMerge:
873 rb = ReviewableBranch(self, branch, base)
874 if rb.commits:
875 return rb
876 return None
877
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700878 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700879 people=([],[]),
880 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881 """Uploads the named branch for code review.
882 """
883 if branch is None:
884 branch = self.CurrentBranch
885 if branch is None:
886 raise GitError('not currently on a branch')
887
888 branch = self.GetBranch(branch)
889 if not branch.LocalMerge:
890 raise GitError('branch %s does not track a remote' % branch.name)
891 if not branch.remote.review:
892 raise GitError('remote %s has no review url' % branch.remote.name)
893
894 dest_branch = branch.merge
895 if not dest_branch.startswith(R_HEADS):
896 dest_branch = R_HEADS + dest_branch
897
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800898 if not branch.remote.projectname:
899 branch.remote.projectname = self.name
900 branch.remote.Save()
901
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800902 url = branch.remote.ReviewUrl(self.UserEmail)
903 if url is None:
904 raise UploadError('review not configured')
905 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800906
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800907 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800908 rp = ['gerrit receive-pack']
909 for e in people[0]:
910 rp.append('--reviewer=%s' % sq(e))
911 for e in people[1]:
912 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800913 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700914
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800915 cmd.append(url)
916
917 if dest_branch.startswith(R_HEADS):
918 dest_branch = dest_branch[len(R_HEADS):]
919 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
920 if auto_topic:
921 ref_spec = ref_spec + '/' + branch.name
922 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800923
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800924 if GitCommand(self, cmd, bare = True).Wait() != 0:
925 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926
927 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
928 self.bare_git.UpdateRef(R_PUB + branch.name,
929 R_HEADS + branch.name,
930 message = msg)
931
932
933## Sync ##
934
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700935 def Sync_NetworkHalf(self,
936 quiet=False,
937 is_new=None,
938 current_branch_only=False,
939 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700940 """Perform only the network IO portion of the sync process.
941 Local working directory/branch state is not affected.
942 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700943 if is_new is None:
944 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200945 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946 self._InitGitDir()
947 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700948
949 if is_new:
950 alt = os.path.join(self.gitdir, 'objects/info/alternates')
951 try:
952 fd = open(alt, 'rb')
953 try:
954 alt_dir = fd.readline().rstrip()
955 finally:
956 fd.close()
957 except IOError:
958 alt_dir = None
959 else:
960 alt_dir = None
961
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700962 if clone_bundle \
963 and alt_dir is None \
964 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700965 is_new = False
966
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700967 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
968 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800970
971 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800972 self._InitMRef()
973 else:
974 self._InitMirrorHead()
975 try:
976 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
977 except OSError:
978 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800980
981 def PostRepoUpgrade(self):
982 self._InitHooks()
983
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984 def _CopyFiles(self):
985 for file in self.copyfiles:
986 file._Copy()
987
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700988 def GetRevisionId(self, all=None):
989 if self.revisionId:
990 return self.revisionId
991
992 rem = self.GetRemote(self.remote.name)
993 rev = rem.ToLocal(self.revisionExpr)
994
995 if all is not None and rev in all:
996 return all[rev]
997
998 try:
999 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1000 except GitError:
1001 raise ManifestInvalidRevisionError(
1002 'revision %s in %s not found' % (self.revisionExpr,
1003 self.name))
1004
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001005 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001006 """Perform only the local IO portion of the sync process.
1007 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001009 all = self.bare_ref.all
1010 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001011 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001012
1013 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001014 head = self.work_git.GetHead()
1015 if head.startswith(R_HEADS):
1016 branch = head[len(R_HEADS):]
1017 try:
1018 head = all[head]
1019 except KeyError:
1020 head = None
1021 else:
1022 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001024 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001025 # Currently on a detached HEAD. The user is assumed to
1026 # not have any local modifications worth worrying about.
1027 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001028 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001029 syncbuf.fail(self, _PriorSyncFailedError())
1030 return
1031
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001032 if head == revid:
1033 # No changes; don't do anything further.
1034 #
1035 return
1036
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001037 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001039 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001040 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001041 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001042 except GitError, e:
1043 syncbuf.fail(self, e)
1044 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001045 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001046 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001047
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001048 if head == revid:
1049 # No changes; don't do anything further.
1050 #
1051 return
1052
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001055 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001057 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001059 syncbuf.info(self,
1060 "leaving %s; does not track upstream",
1061 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001063 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001064 except GitError, e:
1065 syncbuf.fail(self, e)
1066 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001068 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001070 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001071 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001073 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074 if not_merged:
1075 if upstream_gain:
1076 # The user has published this branch and some of those
1077 # commits are not yet merged upstream. We do not want
1078 # to rewrite the published commits so we punt.
1079 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001080 syncbuf.fail(self,
1081 "branch %s is published (but not merged) and is now %d commits behind"
1082 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001083 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001084 elif pub == head:
1085 # All published commits are merged, and thus we are a
1086 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001087 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001088 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001089 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001090 self._CopyFiles()
1091 syncbuf.later1(self, _doff)
1092 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001093
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001094 # Examine the local commits not in the remote. Find the
1095 # last one attributed to this user, if any.
1096 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001097 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001098 last_mine = None
1099 cnt_mine = 0
1100 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001101 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001102 if committer_email == self.UserEmail:
1103 last_mine = commit_id
1104 cnt_mine += 1
1105
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001106 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001107 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108
1109 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001110 syncbuf.fail(self, _DirtyError())
1111 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001112
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001113 # If the upstream switched on us, warn the user.
1114 #
1115 if branch.merge != self.revisionExpr:
1116 if branch.merge and self.revisionExpr:
1117 syncbuf.info(self,
1118 'manifest switched %s...%s',
1119 branch.merge,
1120 self.revisionExpr)
1121 elif branch.merge:
1122 syncbuf.info(self,
1123 'manifest no longer tracks %s',
1124 branch.merge)
1125
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001126 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001127 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001128 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001129 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001130 syncbuf.info(self,
1131 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001132 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001133
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001134 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001135 if not ID_RE.match(self.revisionExpr):
1136 # in case of manifest sync the revisionExpr might be a SHA1
1137 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001138 branch.Save()
1139
Mike Pontillod3153822012-02-28 11:53:24 -08001140 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001141 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001142 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001143 self._CopyFiles()
1144 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001145 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001147 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001148 self._CopyFiles()
1149 except GitError, e:
1150 syncbuf.fail(self, e)
1151 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001152 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001153 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001154 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001155 self._CopyFiles()
1156 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001157
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001158 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001159 # dest should already be an absolute path, but src is project relative
1160 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001161 abssrc = os.path.join(self.worktree, src)
1162 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163
James W. Mills24c13082012-04-12 15:04:13 -05001164 def AddAnnotation(self, name, value, keep):
1165 self.annotations.append(_Annotation(name, value, keep))
1166
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001167 def DownloadPatchSet(self, change_id, patch_id):
1168 """Download a single patch set of a single change to FETCH_HEAD.
1169 """
1170 remote = self.GetRemote(self.remote.name)
1171
1172 cmd = ['fetch', remote.name]
1173 cmd.append('refs/changes/%2.2d/%d/%d' \
1174 % (change_id % 100, change_id, patch_id))
1175 cmd.extend(map(lambda x: str(x), remote.fetch))
1176 if GitCommand(self, cmd, bare=True).Wait() != 0:
1177 return None
1178 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001179 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001180 change_id,
1181 patch_id,
1182 self.bare_git.rev_parse('FETCH_HEAD'))
1183
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001184
1185## Branch Management ##
1186
1187 def StartBranch(self, name):
1188 """Create a new branch off the manifest's revision.
1189 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001190 head = self.work_git.GetHead()
1191 if head == (R_HEADS + name):
1192 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001194 all = self.bare_ref.all
1195 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001196 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001197 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001198 capture_stdout = True,
1199 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001200
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001201 branch = self.GetBranch(name)
1202 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001203 branch.merge = self.revisionExpr
1204 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001205
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001206 if head.startswith(R_HEADS):
1207 try:
1208 head = all[head]
1209 except KeyError:
1210 head = None
1211
1212 if revid and head and revid == head:
1213 ref = os.path.join(self.gitdir, R_HEADS + name)
1214 try:
1215 os.makedirs(os.path.dirname(ref))
1216 except OSError:
1217 pass
1218 _lwrite(ref, '%s\n' % revid)
1219 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1220 'ref: %s%s\n' % (R_HEADS, name))
1221 branch.Save()
1222 return True
1223
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001224 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001225 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001226 capture_stdout = True,
1227 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001228 branch.Save()
1229 return True
1230 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001231
Wink Saville02d79452009-04-10 13:01:24 -07001232 def CheckoutBranch(self, name):
1233 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001234
1235 Args:
1236 name: The name of the branch to checkout.
1237
1238 Returns:
1239 True if the checkout succeeded; False if it didn't; None if the branch
1240 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001241 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001242 rev = R_HEADS + name
1243 head = self.work_git.GetHead()
1244 if head == rev:
1245 # Already on the branch
1246 #
1247 return True
Wink Saville02d79452009-04-10 13:01:24 -07001248
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001249 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001250 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001251 revid = all[rev]
1252 except KeyError:
1253 # Branch does not exist in this project
1254 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001255 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001256
1257 if head.startswith(R_HEADS):
1258 try:
1259 head = all[head]
1260 except KeyError:
1261 head = None
1262
1263 if head == revid:
1264 # Same revision; just update HEAD to point to the new
1265 # target branch, but otherwise take no other action.
1266 #
1267 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1268 'ref: %s%s\n' % (R_HEADS, name))
1269 return True
Wink Saville02d79452009-04-10 13:01:24 -07001270
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001271 return GitCommand(self,
1272 ['checkout', name, '--'],
1273 capture_stdout = True,
1274 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001275
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001276 def AbandonBranch(self, name):
1277 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001278
1279 Args:
1280 name: The name of the branch to abandon.
1281
1282 Returns:
1283 True if the abandon succeeded; False if it didn't; None if the branch
1284 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001285 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001286 rev = R_HEADS + name
1287 all = self.bare_ref.all
1288 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001289 # Doesn't exist
1290 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001291
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001292 head = self.work_git.GetHead()
1293 if head == rev:
1294 # We can't destroy the branch while we are sitting
1295 # on it. Switch to a detached HEAD.
1296 #
1297 head = all[head]
1298
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001299 revid = self.GetRevisionId(all)
1300 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001301 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1302 '%s\n' % revid)
1303 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001304 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001305
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001306 return GitCommand(self,
1307 ['branch', '-D', name],
1308 capture_stdout = True,
1309 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001310
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 def PruneHeads(self):
1312 """Prune any topic branches already merged into upstream.
1313 """
1314 cb = self.CurrentBranch
1315 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001316 left = self._allrefs
1317 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318 if name.startswith(R_HEADS):
1319 name = name[len(R_HEADS):]
1320 if cb is None or name != cb:
1321 kill.append(name)
1322
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001323 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 if cb is not None \
1325 and not self._revlist(HEAD + '...' + rev) \
1326 and not self.IsDirty(consider_untracked = False):
1327 self.work_git.DetachHead(HEAD)
1328 kill.append(cb)
1329
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001331 old = self.bare_git.GetHead()
1332 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001333 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1334
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001335 try:
1336 self.bare_git.DetachHead(rev)
1337
1338 b = ['branch', '-d']
1339 b.extend(kill)
1340 b = GitCommand(self, b, bare=True,
1341 capture_stdout=True,
1342 capture_stderr=True)
1343 b.Wait()
1344 finally:
1345 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001346 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001347
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001348 for branch in kill:
1349 if (R_HEADS + branch) not in left:
1350 self.CleanPublishedCache()
1351 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001352
1353 if cb and cb not in kill:
1354 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001355 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001356
1357 kept = []
1358 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001359 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001360 branch = self.GetBranch(branch)
1361 base = branch.LocalMerge
1362 if not base:
1363 base = rev
1364 kept.append(ReviewableBranch(self, branch, base))
1365 return kept
1366
1367
1368## Direct Git Commands ##
1369
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001370 def _RemoteFetch(self, name=None,
1371 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001372 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001373 quiet=False,
1374 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001375
1376 is_sha1 = False
1377 tag_name = None
1378
1379 if current_branch_only:
1380 if ID_RE.match(self.revisionExpr) is not None:
1381 is_sha1 = True
1382 elif self.revisionExpr.startswith(R_TAGS):
1383 # this is a tag and its sha1 value should never change
1384 tag_name = self.revisionExpr[len(R_TAGS):]
1385
1386 if is_sha1 or tag_name is not None:
1387 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001388 # if revision (sha or tag) is not present then following function
1389 # throws an error.
1390 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001391 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001392 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001393 # There is no such persistent revision. We have to fetch it.
1394 pass
1395
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001396 if not name:
1397 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001398
1399 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001400 remote = self.GetRemote(name)
1401 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001402 ssh_proxy = True
1403
Shawn O. Pearce88443382010-10-08 10:02:09 +02001404 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001405 if alt_dir and 'objects' == os.path.basename(alt_dir):
1406 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001407 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1408 remote = self.GetRemote(name)
1409
1410 all = self.bare_ref.all
1411 ids = set(all.values())
1412 tmp = set()
1413
1414 for r, id in GitRefs(ref_dir).all.iteritems():
1415 if r not in all:
1416 if r.startswith(R_TAGS) or remote.WritesTo(r):
1417 all[r] = id
1418 ids.add(id)
1419 continue
1420
1421 if id in ids:
1422 continue
1423
1424 r = 'refs/_alt/%s' % id
1425 all[r] = id
1426 ids.add(id)
1427 tmp.add(r)
1428
1429 ref_names = list(all.keys())
1430 ref_names.sort()
1431
1432 tmp_packed = ''
1433 old_packed = ''
1434
1435 for r in ref_names:
1436 line = '%s %s\n' % (all[r], r)
1437 tmp_packed += line
1438 if r not in tmp:
1439 old_packed += line
1440
1441 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001442 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001443 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001444
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001445 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001446
1447 # The --depth option only affects the initial fetch; after that we'll do
1448 # full fetches of changes.
1449 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1450 if depth and initial:
1451 cmd.append('--depth=%s' % depth)
1452
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001453 if quiet:
1454 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001455 if not self.worktree:
1456 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001457 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001458
1459 if not current_branch_only or is_sha1:
1460 # Fetch whole repo
1461 cmd.append('--tags')
1462 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1463 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001464 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001465 cmd.append(tag_name)
1466 else:
1467 branch = self.revisionExpr
1468 if branch.startswith(R_HEADS):
1469 branch = branch[len(R_HEADS):]
1470 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001471
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001472 ok = False
1473 for i in range(2):
1474 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1475 ok = True
1476 break
1477 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001478
1479 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001480 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001481 if old_packed != '':
1482 _lwrite(packed_refs, old_packed)
1483 else:
1484 os.remove(packed_refs)
1485 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001486 return ok
1487
1488 def _ApplyCloneBundle(self, initial=False, quiet=False):
1489 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1490 return False
1491
1492 remote = self.GetRemote(self.remote.name)
1493 bundle_url = remote.url + '/clone.bundle'
1494 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001495 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1496 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001497 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1498 return False
1499
1500 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1501 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001502
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001503 exist_dst = os.path.exists(bundle_dst)
1504 exist_tmp = os.path.exists(bundle_tmp)
1505
1506 if not initial and not exist_dst and not exist_tmp:
1507 return False
1508
1509 if not exist_dst:
1510 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1511 if not exist_dst:
1512 return False
1513
1514 cmd = ['fetch']
1515 if quiet:
1516 cmd.append('--quiet')
1517 if not self.worktree:
1518 cmd.append('--update-head-ok')
1519 cmd.append(bundle_dst)
1520 for f in remote.fetch:
1521 cmd.append(str(f))
1522 cmd.append('refs/tags/*:refs/tags/*')
1523
1524 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001525 if os.path.exists(bundle_dst):
1526 os.remove(bundle_dst)
1527 if os.path.exists(bundle_tmp):
1528 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001529 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001530
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001531 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001532 keep = True
1533 done = False
1534 dest = open(tmpPath, 'a+b')
1535 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001536 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001537 pos = dest.tell()
1538
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001539 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001540 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001541 req = urllib2.Request(srcUrl)
1542 if pos > 0:
1543 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001544
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001545 try:
1546 r = urllib2.urlopen(req)
1547 except urllib2.HTTPError, e:
1548 def _content_type():
1549 try:
1550 return e.info()['content-type']
1551 except:
1552 return None
1553
Shawn O. Pearcec3d2f2b2012-03-22 14:09:22 -07001554 if e.code in (401, 403, 404):
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001555 keep = False
1556 return False
1557 elif _content_type() == 'text/plain':
1558 try:
1559 msg = e.read()
1560 if len(msg) > 0 and msg[-1] == '\n':
1561 msg = msg[0:-1]
1562 msg = ' (%s)' % msg
1563 except:
1564 msg = ''
1565 else:
1566 try:
1567 from BaseHTTPServer import BaseHTTPRequestHandler
1568 res = BaseHTTPRequestHandler.responses[e.code]
1569 msg = ' (%s: %s)' % (res[0], res[1])
1570 except:
1571 msg = ''
1572 raise DownloadError('HTTP %s%s' % (e.code, msg))
1573 except urllib2.URLError, e:
1574 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1575 finally:
1576 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001577
1578 p = None
1579 try:
Conley Owens43bda842012-03-12 11:25:04 -07001580 size = r.headers.get('content-length', 0)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001581 unit = 1 << 10
1582
1583 if size and not quiet:
1584 if size > 1024 * 1.3:
1585 unit = 1 << 20
1586 desc = 'MB'
1587 else:
1588 desc = 'KB'
1589 p = Progress(
1590 'Downloading %s' % self.relpath,
1591 int(size) / unit,
1592 units=desc)
1593 if pos > 0:
1594 p.update(pos / unit)
1595
1596 s = 0
1597 while True:
1598 d = r.read(8192)
1599 if d == '':
1600 done = True
1601 return True
1602 dest.write(d)
1603 if p:
1604 s += len(d)
1605 if s >= unit:
1606 p.update(s / unit)
1607 s = s % unit
1608 if p:
1609 if s >= unit:
1610 p.update(s / unit)
1611 else:
1612 p.update(1)
1613 finally:
1614 r.close()
1615 if p:
1616 p.end()
1617 finally:
1618 dest.close()
1619
1620 if os.path.exists(dstPath):
1621 os.remove(dstPath)
1622 if done:
1623 os.rename(tmpPath, dstPath)
1624 elif not keep:
1625 os.remove(tmpPath)
1626
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001627 def _Checkout(self, rev, quiet=False):
1628 cmd = ['checkout']
1629 if quiet:
1630 cmd.append('-q')
1631 cmd.append(rev)
1632 cmd.append('--')
1633 if GitCommand(self, cmd).Wait() != 0:
1634 if self._allrefs:
1635 raise GitError('%s checkout %s ' % (self.name, rev))
1636
1637 def _ResetHard(self, rev, quiet=True):
1638 cmd = ['reset', '--hard']
1639 if quiet:
1640 cmd.append('-q')
1641 cmd.append(rev)
1642 if GitCommand(self, cmd).Wait() != 0:
1643 raise GitError('%s reset --hard %s ' % (self.name, rev))
1644
1645 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001646 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001647 if onto is not None:
1648 cmd.extend(['--onto', onto])
1649 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001650 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001651 raise GitError('%s rebase %s ' % (self.name, upstream))
1652
1653 def _FastForward(self, head):
1654 cmd = ['merge', head]
1655 if GitCommand(self, cmd).Wait() != 0:
1656 raise GitError('%s merge %s ' % (self.name, head))
1657
1658 def _InitGitDir(self):
1659 if not os.path.exists(self.gitdir):
1660 os.makedirs(self.gitdir)
1661 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001662
Shawn O. Pearce88443382010-10-08 10:02:09 +02001663 mp = self.manifest.manifestProject
1664 ref_dir = mp.config.GetString('repo.reference')
1665
1666 if ref_dir:
1667 mirror_git = os.path.join(ref_dir, self.name + '.git')
1668 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1669 self.relpath + '.git')
1670
1671 if os.path.exists(mirror_git):
1672 ref_dir = mirror_git
1673
1674 elif os.path.exists(repo_git):
1675 ref_dir = repo_git
1676
1677 else:
1678 ref_dir = None
1679
1680 if ref_dir:
1681 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1682 os.path.join(ref_dir, 'objects') + '\n')
1683
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001684 if self.manifest.IsMirror:
1685 self.config.SetString('core.bare', 'true')
1686 else:
1687 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001688
1689 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001690 try:
1691 to_rm = os.listdir(hooks)
1692 except OSError:
1693 to_rm = []
1694 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001695 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001696 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001697
1698 m = self.manifest.manifestProject.config
1699 for key in ['user.name', 'user.email']:
1700 if m.Has(key, include_defaults = False):
1701 self.config.SetString(key, m.GetString(key))
1702
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001703 def _InitHooks(self):
1704 hooks = self._gitdir_path('hooks')
1705 if not os.path.exists(hooks):
1706 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001707 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001708 name = os.path.basename(stock_hook)
1709
Victor Boivie65e0f352011-04-18 11:23:29 +02001710 if name in ('commit-msg',) and not self.remote.review \
1711 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001712 # Don't install a Gerrit Code Review hook if this
1713 # project does not appear to use it for reviews.
1714 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001715 # Since the manifest project is one of those, but also
1716 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001717 continue
1718
1719 dst = os.path.join(hooks, name)
1720 if os.path.islink(dst):
1721 continue
1722 if os.path.exists(dst):
1723 if filecmp.cmp(stock_hook, dst, shallow=False):
1724 os.remove(dst)
1725 else:
1726 _error("%s: Not replacing %s hook", self.relpath, name)
1727 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001728 try:
1729 os.symlink(relpath(stock_hook, dst), dst)
1730 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001731 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001732 raise GitError('filesystem must support symlinks')
1733 else:
1734 raise
1735
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001736 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001737 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001738 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001739 remote.url = self.remote.url
1740 remote.review = self.remote.review
1741 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001742
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001743 if self.worktree:
1744 remote.ResetFetch(mirror=False)
1745 else:
1746 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001747 remote.Save()
1748
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001749 def _InitMRef(self):
1750 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001751 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001752
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001753 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001754 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001755
1756 def _InitAnyMRef(self, ref):
1757 cur = self.bare_ref.symref(ref)
1758
1759 if self.revisionId:
1760 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1761 msg = 'manifest set to %s' % self.revisionId
1762 dst = self.revisionId + '^0'
1763 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1764 else:
1765 remote = self.GetRemote(self.remote.name)
1766 dst = remote.ToLocal(self.revisionExpr)
1767 if cur != dst:
1768 msg = 'manifest set to %s' % self.revisionExpr
1769 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001770
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001771 def _InitWorkTree(self):
1772 dotgit = os.path.join(self.worktree, '.git')
1773 if not os.path.exists(dotgit):
1774 os.makedirs(dotgit)
1775
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001776 for name in ['config',
1777 'description',
1778 'hooks',
1779 'info',
1780 'logs',
1781 'objects',
1782 'packed-refs',
1783 'refs',
1784 'rr-cache',
1785 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001786 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001787 src = os.path.join(self.gitdir, name)
1788 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001789 if os.path.islink(dst) or not os.path.exists(dst):
1790 os.symlink(relpath(src, dst), dst)
1791 else:
1792 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001793 except OSError, e:
1794 if e.errno == errno.EPERM:
1795 raise GitError('filesystem must support symlinks')
1796 else:
1797 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001798
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001799 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001800
1801 cmd = ['read-tree', '--reset', '-u']
1802 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001803 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001804 if GitCommand(self, cmd).Wait() != 0:
1805 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001806
1807 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1808 if not os.path.exists(rr_cache):
1809 os.makedirs(rr_cache)
1810
Shawn O. Pearce93609662009-04-21 10:50:33 -07001811 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001812
1813 def _gitdir_path(self, path):
1814 return os.path.join(self.gitdir, path)
1815
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001816 def _revlist(self, *args, **kw):
1817 a = []
1818 a.extend(args)
1819 a.append('--')
1820 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001821
1822 @property
1823 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001824 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001825
1826 class _GitGetByExec(object):
1827 def __init__(self, project, bare):
1828 self._project = project
1829 self._bare = bare
1830
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001831 def LsOthers(self):
1832 p = GitCommand(self._project,
1833 ['ls-files',
1834 '-z',
1835 '--others',
1836 '--exclude-standard'],
1837 bare = False,
1838 capture_stdout = True,
1839 capture_stderr = True)
1840 if p.Wait() == 0:
1841 out = p.stdout
1842 if out:
1843 return out[:-1].split("\0")
1844 return []
1845
1846 def DiffZ(self, name, *args):
1847 cmd = [name]
1848 cmd.append('-z')
1849 cmd.extend(args)
1850 p = GitCommand(self._project,
1851 cmd,
1852 bare = False,
1853 capture_stdout = True,
1854 capture_stderr = True)
1855 try:
1856 out = p.process.stdout.read()
1857 r = {}
1858 if out:
1859 out = iter(out[:-1].split('\0'))
1860 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001861 try:
1862 info = out.next()
1863 path = out.next()
1864 except StopIteration:
1865 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001866
1867 class _Info(object):
1868 def __init__(self, path, omode, nmode, oid, nid, state):
1869 self.path = path
1870 self.src_path = None
1871 self.old_mode = omode
1872 self.new_mode = nmode
1873 self.old_id = oid
1874 self.new_id = nid
1875
1876 if len(state) == 1:
1877 self.status = state
1878 self.level = None
1879 else:
1880 self.status = state[:1]
1881 self.level = state[1:]
1882 while self.level.startswith('0'):
1883 self.level = self.level[1:]
1884
1885 info = info[1:].split(' ')
1886 info =_Info(path, *info)
1887 if info.status in ('R', 'C'):
1888 info.src_path = info.path
1889 info.path = out.next()
1890 r[info.path] = info
1891 return r
1892 finally:
1893 p.Wait()
1894
1895 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001896 if self._bare:
1897 path = os.path.join(self._project.gitdir, HEAD)
1898 else:
1899 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001900 fd = open(path, 'rb')
1901 try:
1902 line = fd.read()
1903 finally:
1904 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001905 if line.startswith('ref: '):
1906 return line[5:-1]
1907 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001908
1909 def SetHead(self, ref, message=None):
1910 cmdv = []
1911 if message is not None:
1912 cmdv.extend(['-m', message])
1913 cmdv.append(HEAD)
1914 cmdv.append(ref)
1915 self.symbolic_ref(*cmdv)
1916
1917 def DetachHead(self, new, message=None):
1918 cmdv = ['--no-deref']
1919 if message is not None:
1920 cmdv.extend(['-m', message])
1921 cmdv.append(HEAD)
1922 cmdv.append(new)
1923 self.update_ref(*cmdv)
1924
1925 def UpdateRef(self, name, new, old=None,
1926 message=None,
1927 detach=False):
1928 cmdv = []
1929 if message is not None:
1930 cmdv.extend(['-m', message])
1931 if detach:
1932 cmdv.append('--no-deref')
1933 cmdv.append(name)
1934 cmdv.append(new)
1935 if old is not None:
1936 cmdv.append(old)
1937 self.update_ref(*cmdv)
1938
1939 def DeleteRef(self, name, old=None):
1940 if not old:
1941 old = self.rev_parse(name)
1942 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001943 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001944
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001945 def rev_list(self, *args, **kw):
1946 if 'format' in kw:
1947 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1948 else:
1949 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001950 cmdv.extend(args)
1951 p = GitCommand(self._project,
1952 cmdv,
1953 bare = self._bare,
1954 capture_stdout = True,
1955 capture_stderr = True)
1956 r = []
1957 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001958 if line[-1] == '\n':
1959 line = line[:-1]
1960 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001961 if p.Wait() != 0:
1962 raise GitError('%s rev-list %s: %s' % (
1963 self._project.name,
1964 str(args),
1965 p.stderr))
1966 return r
1967
1968 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001969 """Allow arbitrary git commands using pythonic syntax.
1970
1971 This allows you to do things like:
1972 git_obj.rev_parse('HEAD')
1973
1974 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1975 run. We'll replace the '_' with a '-' and try to run a git command.
1976 Any other arguments will be passed to the git command.
1977
1978 Args:
1979 name: The name of the git command to call. Any '_' characters will
1980 be replaced with '-'.
1981
1982 Returns:
1983 A callable object that will try to call git with the named command.
1984 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001985 name = name.replace('_', '-')
1986 def runner(*args):
1987 cmdv = [name]
1988 cmdv.extend(args)
1989 p = GitCommand(self._project,
1990 cmdv,
1991 bare = self._bare,
1992 capture_stdout = True,
1993 capture_stderr = True)
1994 if p.Wait() != 0:
1995 raise GitError('%s %s: %s' % (
1996 self._project.name,
1997 name,
1998 p.stderr))
1999 r = p.stdout
2000 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2001 return r[:-1]
2002 return r
2003 return runner
2004
2005
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002006class _PriorSyncFailedError(Exception):
2007 def __str__(self):
2008 return 'prior sync failed; rebase still in progress'
2009
2010class _DirtyError(Exception):
2011 def __str__(self):
2012 return 'contains uncommitted changes'
2013
2014class _InfoMessage(object):
2015 def __init__(self, project, text):
2016 self.project = project
2017 self.text = text
2018
2019 def Print(self, syncbuf):
2020 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2021 syncbuf.out.nl()
2022
2023class _Failure(object):
2024 def __init__(self, project, why):
2025 self.project = project
2026 self.why = why
2027
2028 def Print(self, syncbuf):
2029 syncbuf.out.fail('error: %s/: %s',
2030 self.project.relpath,
2031 str(self.why))
2032 syncbuf.out.nl()
2033
2034class _Later(object):
2035 def __init__(self, project, action):
2036 self.project = project
2037 self.action = action
2038
2039 def Run(self, syncbuf):
2040 out = syncbuf.out
2041 out.project('project %s/', self.project.relpath)
2042 out.nl()
2043 try:
2044 self.action()
2045 out.nl()
2046 return True
2047 except GitError, e:
2048 out.nl()
2049 return False
2050
2051class _SyncColoring(Coloring):
2052 def __init__(self, config):
2053 Coloring.__init__(self, config, 'reposync')
2054 self.project = self.printer('header', attr = 'bold')
2055 self.info = self.printer('info')
2056 self.fail = self.printer('fail', fg='red')
2057
2058class SyncBuffer(object):
2059 def __init__(self, config, detach_head=False):
2060 self._messages = []
2061 self._failures = []
2062 self._later_queue1 = []
2063 self._later_queue2 = []
2064
2065 self.out = _SyncColoring(config)
2066 self.out.redirect(sys.stderr)
2067
2068 self.detach_head = detach_head
2069 self.clean = True
2070
2071 def info(self, project, fmt, *args):
2072 self._messages.append(_InfoMessage(project, fmt % args))
2073
2074 def fail(self, project, err=None):
2075 self._failures.append(_Failure(project, err))
2076 self.clean = False
2077
2078 def later1(self, project, what):
2079 self._later_queue1.append(_Later(project, what))
2080
2081 def later2(self, project, what):
2082 self._later_queue2.append(_Later(project, what))
2083
2084 def Finish(self):
2085 self._PrintMessages()
2086 self._RunLater()
2087 self._PrintMessages()
2088 return self.clean
2089
2090 def _RunLater(self):
2091 for q in ['_later_queue1', '_later_queue2']:
2092 if not self._RunQueue(q):
2093 return
2094
2095 def _RunQueue(self, queue):
2096 for m in getattr(self, queue):
2097 if not m.Run(self):
2098 self.clean = False
2099 return False
2100 setattr(self, queue, [])
2101 return True
2102
2103 def _PrintMessages(self):
2104 for m in self._messages:
2105 m.Print(self)
2106 for m in self._failures:
2107 m.Print(self)
2108
2109 self._messages = []
2110 self._failures = []
2111
2112
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002113class MetaProject(Project):
2114 """A special project housed under .repo.
2115 """
2116 def __init__(self, manifest, name, gitdir, worktree):
2117 repodir = manifest.repodir
2118 Project.__init__(self,
2119 manifest = manifest,
2120 name = name,
2121 gitdir = gitdir,
2122 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002123 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002124 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002125 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002126 revisionId = None,
2127 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002128
2129 def PreSync(self):
2130 if self.Exists:
2131 cb = self.CurrentBranch
2132 if cb:
2133 base = self.GetBranch(cb).merge
2134 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002135 self.revisionExpr = base
2136 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002137
2138 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002139 def LastFetch(self):
2140 try:
2141 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2142 return os.path.getmtime(fh)
2143 except OSError:
2144 return 0
2145
2146 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002147 def HasChanges(self):
2148 """Has the remote received new commits not yet checked out?
2149 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002150 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002151 return False
2152
2153 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002154 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002155 head = self.work_git.GetHead()
2156 if head.startswith(R_HEADS):
2157 try:
2158 head = all[head]
2159 except KeyError:
2160 head = None
2161
2162 if revid == head:
2163 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002164 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002165 return True
2166 return False