blob: 40f6f317ecb559a26f20c9eb69ffbb46014b2acd [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,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700513 groups = None,
514 sync_c = False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700515 self.manifest = manifest
516 self.name = name
517 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800518 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800519 if worktree:
520 self.worktree = worktree.replace('\\', '/')
521 else:
522 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700523 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700524 self.revisionExpr = revisionExpr
525
526 if revisionId is None \
527 and revisionExpr \
528 and IsId(revisionExpr):
529 self.revisionId = revisionExpr
530 else:
531 self.revisionId = revisionId
532
Mike Pontillod3153822012-02-28 11:53:24 -0800533 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700534 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700535 self.sync_c = sync_c
Mike Pontillod3153822012-02-28 11:53:24 -0800536
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700537 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700538 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500539 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540 self.config = GitConfig.ForRepository(
541 gitdir = self.gitdir,
542 defaults = self.manifest.globalConfig)
543
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800544 if self.worktree:
545 self.work_git = self._GitGetByExec(self, bare=False)
546 else:
547 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700548 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700549 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700550
Doug Anderson37282b42011-03-04 11:54:18 -0800551 # This will be filled in if a project is later identified to be the
552 # project containing repo hooks.
553 self.enabled_repo_hooks = []
554
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700555 @property
556 def Exists(self):
557 return os.path.isdir(self.gitdir)
558
559 @property
560 def CurrentBranch(self):
561 """Obtain the name of the currently checked out branch.
562 The branch name omits the 'refs/heads/' prefix.
563 None is returned if the project is on a detached HEAD.
564 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700565 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700566 if b.startswith(R_HEADS):
567 return b[len(R_HEADS):]
568 return None
569
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700570 def IsRebaseInProgress(self):
571 w = self.worktree
572 g = os.path.join(w, '.git')
573 return os.path.exists(os.path.join(g, 'rebase-apply')) \
574 or os.path.exists(os.path.join(g, 'rebase-merge')) \
575 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200576
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700577 def IsDirty(self, consider_untracked=True):
578 """Is the working directory modified in some way?
579 """
580 self.work_git.update_index('-q',
581 '--unmerged',
582 '--ignore-missing',
583 '--refresh')
584 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
585 return True
586 if self.work_git.DiffZ('diff-files'):
587 return True
588 if consider_untracked and self.work_git.LsOthers():
589 return True
590 return False
591
592 _userident_name = None
593 _userident_email = None
594
595 @property
596 def UserName(self):
597 """Obtain the user's personal name.
598 """
599 if self._userident_name is None:
600 self._LoadUserIdentity()
601 return self._userident_name
602
603 @property
604 def UserEmail(self):
605 """Obtain the user's email address. This is very likely
606 to be their Gerrit login.
607 """
608 if self._userident_email is None:
609 self._LoadUserIdentity()
610 return self._userident_email
611
612 def _LoadUserIdentity(self):
613 u = self.bare_git.var('GIT_COMMITTER_IDENT')
614 m = re.compile("^(.*) <([^>]*)> ").match(u)
615 if m:
616 self._userident_name = m.group(1)
617 self._userident_email = m.group(2)
618 else:
619 self._userident_name = ''
620 self._userident_email = ''
621
622 def GetRemote(self, name):
623 """Get the configuration for a single remote.
624 """
625 return self.config.GetRemote(name)
626
627 def GetBranch(self, name):
628 """Get the configuration for a single branch.
629 """
630 return self.config.GetBranch(name)
631
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700632 def GetBranches(self):
633 """Get all existing local branches.
634 """
635 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700636 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700637 heads = {}
638 pubd = {}
639
640 for name, id in all.iteritems():
641 if name.startswith(R_HEADS):
642 name = name[len(R_HEADS):]
643 b = self.GetBranch(name)
644 b.current = name == current
645 b.published = None
646 b.revision = id
647 heads[name] = b
648
649 for name, id in all.iteritems():
650 if name.startswith(R_PUB):
651 name = name[len(R_PUB):]
652 b = heads.get(name)
653 if b:
654 b.published = id
655
656 return heads
657
Colin Cross5acde752012-03-28 20:15:45 -0700658 def MatchesGroups(self, manifest_groups):
659 """Returns true if the manifest groups specified at init should cause
660 this project to be synced.
661 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owens971de8e2012-04-16 10:36:08 -0700662 All projects are implicitly labelled with "default".
663
664 labels are resolved in order. In the example case of
665 project_groups: "default,group1,group2"
666 manifest_groups: "-group1,group2"
667 the project will be matched.
Colin Cross5acde752012-03-28 20:15:45 -0700668 """
Conley Owens971de8e2012-04-16 10:36:08 -0700669 matched = False
670 for group in manifest_groups:
671 if group.startswith('-') and group[1:] in self.groups:
672 matched = False
673 elif group in self.groups:
674 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700675
Conley Owens971de8e2012-04-16 10:36:08 -0700676 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700677
678## Status Display ##
679
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500680 def HasChanges(self):
681 """Returns true if there are uncommitted changes.
682 """
683 self.work_git.update_index('-q',
684 '--unmerged',
685 '--ignore-missing',
686 '--refresh')
687 if self.IsRebaseInProgress():
688 return True
689
690 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
691 return True
692
693 if self.work_git.DiffZ('diff-files'):
694 return True
695
696 if self.work_git.LsOthers():
697 return True
698
699 return False
700
Terence Haddock4655e812011-03-31 12:33:34 +0200701 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200703
704 Args:
705 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700706 """
707 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200708 if output_redir == None:
709 output_redir = sys.stdout
710 print >>output_redir, ''
711 print >>output_redir, 'project %s/' % self.relpath
712 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700713 return
714
715 self.work_git.update_index('-q',
716 '--unmerged',
717 '--ignore-missing',
718 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700719 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
721 df = self.work_git.DiffZ('diff-files')
722 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100723 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700724 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700725
726 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200727 if not output_redir == None:
728 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700729 out.project('project %-40s', self.relpath + '/')
730
731 branch = self.CurrentBranch
732 if branch is None:
733 out.nobranch('(*** NO BRANCH ***)')
734 else:
735 out.branch('branch %s', branch)
736 out.nl()
737
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700738 if rb:
739 out.important('prior sync failed; rebase still in progress')
740 out.nl()
741
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742 paths = list()
743 paths.extend(di.keys())
744 paths.extend(df.keys())
745 paths.extend(do)
746
747 paths = list(set(paths))
748 paths.sort()
749
750 for p in paths:
751 try: i = di[p]
752 except KeyError: i = None
753
754 try: f = df[p]
755 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200756
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 if i: i_status = i.status.upper()
758 else: i_status = '-'
759
760 if f: f_status = f.status.lower()
761 else: f_status = '-'
762
763 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800764 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700765 i.src_path, p, i.level)
766 else:
767 line = ' %s%s\t%s' % (i_status, f_status, p)
768
769 if i and not f:
770 out.added('%s', line)
771 elif (i and f) or (not i and f):
772 out.changed('%s', line)
773 elif not i and not f:
774 out.untracked('%s', line)
775 else:
776 out.write('%s', line)
777 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200778
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700779 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780
pelyad67872d2012-03-28 14:49:58 +0300781 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782 """Prints the status of the repository to stdout.
783 """
784 out = DiffColoring(self.config)
785 cmd = ['diff']
786 if out.is_on:
787 cmd.append('--color')
788 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300789 if absolute_paths:
790 cmd.append('--src-prefix=a/%s/' % self.relpath)
791 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700792 cmd.append('--')
793 p = GitCommand(self,
794 cmd,
795 capture_stdout = True,
796 capture_stderr = True)
797 has_diff = False
798 for line in p.process.stdout:
799 if not has_diff:
800 out.nl()
801 out.project('project %s/' % self.relpath)
802 out.nl()
803 has_diff = True
804 print line[:-1]
805 p.Wait()
806
807
808## Publish / Upload ##
809
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700810 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811 """Was the branch published (uploaded) for code review?
812 If so, returns the SHA-1 hash of the last published
813 state for the branch.
814 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700815 key = R_PUB + branch
816 if all is None:
817 try:
818 return self.bare_git.rev_parse(key)
819 except GitError:
820 return None
821 else:
822 try:
823 return all[key]
824 except KeyError:
825 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700826
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700827 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828 """Prunes any stale published refs.
829 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700830 if all is None:
831 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 heads = set()
833 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700834 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835 if name.startswith(R_HEADS):
836 heads.add(name)
837 elif name.startswith(R_PUB):
838 canrm[name] = id
839
840 for name, id in canrm.iteritems():
841 n = name[len(R_PUB):]
842 if R_HEADS + n not in heads:
843 self.bare_git.DeleteRef(name, id)
844
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700845 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 """List any branches which can be uploaded for review.
847 """
848 heads = {}
849 pubed = {}
850
851 for name, id in self._allrefs.iteritems():
852 if name.startswith(R_HEADS):
853 heads[name[len(R_HEADS):]] = id
854 elif name.startswith(R_PUB):
855 pubed[name[len(R_PUB):]] = id
856
857 ready = []
858 for branch, id in heads.iteritems():
859 if branch in pubed and pubed[branch] == id:
860 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700861 if selected_branch and branch != selected_branch:
862 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700863
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800864 rb = self.GetUploadableBranch(branch)
865 if rb:
866 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867 return ready
868
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800869 def GetUploadableBranch(self, branch_name):
870 """Get a single uploadable branch, or None.
871 """
872 branch = self.GetBranch(branch_name)
873 base = branch.LocalMerge
874 if branch.LocalMerge:
875 rb = ReviewableBranch(self, branch, base)
876 if rb.commits:
877 return rb
878 return None
879
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700880 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700881 people=([],[]),
882 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700883 """Uploads the named branch for code review.
884 """
885 if branch is None:
886 branch = self.CurrentBranch
887 if branch is None:
888 raise GitError('not currently on a branch')
889
890 branch = self.GetBranch(branch)
891 if not branch.LocalMerge:
892 raise GitError('branch %s does not track a remote' % branch.name)
893 if not branch.remote.review:
894 raise GitError('remote %s has no review url' % branch.remote.name)
895
896 dest_branch = branch.merge
897 if not dest_branch.startswith(R_HEADS):
898 dest_branch = R_HEADS + dest_branch
899
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800900 if not branch.remote.projectname:
901 branch.remote.projectname = self.name
902 branch.remote.Save()
903
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800904 url = branch.remote.ReviewUrl(self.UserEmail)
905 if url is None:
906 raise UploadError('review not configured')
907 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800908
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800909 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800910 rp = ['gerrit receive-pack']
911 for e in people[0]:
912 rp.append('--reviewer=%s' % sq(e))
913 for e in people[1]:
914 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800915 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700916
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800917 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800918
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800919 if dest_branch.startswith(R_HEADS):
920 dest_branch = dest_branch[len(R_HEADS):]
921 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
922 if auto_topic:
923 ref_spec = ref_spec + '/' + branch.name
924 cmd.append(ref_spec)
925
926 if GitCommand(self, cmd, bare = True).Wait() != 0:
927 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928
929 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
930 self.bare_git.UpdateRef(R_PUB + branch.name,
931 R_HEADS + branch.name,
932 message = msg)
933
934
935## Sync ##
936
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700937 def Sync_NetworkHalf(self,
938 quiet=False,
939 is_new=None,
940 current_branch_only=False,
941 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 """Perform only the network IO portion of the sync process.
943 Local working directory/branch state is not affected.
944 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700945 if is_new is None:
946 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200947 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 self._InitGitDir()
949 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700950
951 if is_new:
952 alt = os.path.join(self.gitdir, 'objects/info/alternates')
953 try:
954 fd = open(alt, 'rb')
955 try:
956 alt_dir = fd.readline().rstrip()
957 finally:
958 fd.close()
959 except IOError:
960 alt_dir = None
961 else:
962 alt_dir = None
963
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700964 if clone_bundle \
965 and alt_dir is None \
966 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700967 is_new = False
968
Anatol Pomazau79770d22012-04-20 14:41:59 -0700969 current_branch_only = current_branch_only or self.sync_c or self.manifest.default.sync_c
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700970 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
971 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800973
974 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800975 self._InitMRef()
976 else:
977 self._InitMirrorHead()
978 try:
979 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
980 except OSError:
981 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800983
984 def PostRepoUpgrade(self):
985 self._InitHooks()
986
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987 def _CopyFiles(self):
988 for file in self.copyfiles:
989 file._Copy()
990
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700991 def GetRevisionId(self, all=None):
992 if self.revisionId:
993 return self.revisionId
994
995 rem = self.GetRemote(self.remote.name)
996 rev = rem.ToLocal(self.revisionExpr)
997
998 if all is not None and rev in all:
999 return all[rev]
1000
1001 try:
1002 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1003 except GitError:
1004 raise ManifestInvalidRevisionError(
1005 'revision %s in %s not found' % (self.revisionExpr,
1006 self.name))
1007
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001008 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001009 """Perform only the local IO portion of the sync process.
1010 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001012 all = self.bare_ref.all
1013 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001014 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001015
1016 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001017 head = self.work_git.GetHead()
1018 if head.startswith(R_HEADS):
1019 branch = head[len(R_HEADS):]
1020 try:
1021 head = all[head]
1022 except KeyError:
1023 head = None
1024 else:
1025 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001026
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001027 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028 # Currently on a detached HEAD. The user is assumed to
1029 # not have any local modifications worth worrying about.
1030 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001031 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001032 syncbuf.fail(self, _PriorSyncFailedError())
1033 return
1034
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001035 if head == revid:
1036 # No changes; don't do anything further.
1037 #
1038 return
1039
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001040 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001042 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001043 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001044 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001045 except GitError, e:
1046 syncbuf.fail(self, e)
1047 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001048 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001049 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001050
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001051 if head == revid:
1052 # No changes; don't do anything further.
1053 #
1054 return
1055
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001057
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001058 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001060 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001062 syncbuf.info(self,
1063 "leaving %s; does not track upstream",
1064 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001066 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001067 except GitError, e:
1068 syncbuf.fail(self, e)
1069 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001071 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001073 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001074 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001076 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 if not_merged:
1078 if upstream_gain:
1079 # The user has published this branch and some of those
1080 # commits are not yet merged upstream. We do not want
1081 # to rewrite the published commits so we punt.
1082 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001083 syncbuf.fail(self,
1084 "branch %s is published (but not merged) and is now %d commits behind"
1085 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001086 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001087 elif pub == head:
1088 # All published commits are merged, and thus we are a
1089 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001090 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001091 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001092 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001093 self._CopyFiles()
1094 syncbuf.later1(self, _doff)
1095 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001096
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001097 # Examine the local commits not in the remote. Find the
1098 # last one attributed to this user, if any.
1099 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001100 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001101 last_mine = None
1102 cnt_mine = 0
1103 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001104 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001105 if committer_email == self.UserEmail:
1106 last_mine = commit_id
1107 cnt_mine += 1
1108
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001109 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001110 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111
1112 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001113 syncbuf.fail(self, _DirtyError())
1114 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001115
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001116 # If the upstream switched on us, warn the user.
1117 #
1118 if branch.merge != self.revisionExpr:
1119 if branch.merge and self.revisionExpr:
1120 syncbuf.info(self,
1121 'manifest switched %s...%s',
1122 branch.merge,
1123 self.revisionExpr)
1124 elif branch.merge:
1125 syncbuf.info(self,
1126 'manifest no longer tracks %s',
1127 branch.merge)
1128
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001129 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001130 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001131 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001132 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001133 syncbuf.info(self,
1134 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001135 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001137 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001138 if not ID_RE.match(self.revisionExpr):
1139 # in case of manifest sync the revisionExpr might be a SHA1
1140 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001141 branch.Save()
1142
Mike Pontillod3153822012-02-28 11:53:24 -08001143 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001144 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001145 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001146 self._CopyFiles()
1147 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001148 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001150 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001151 self._CopyFiles()
1152 except GitError, e:
1153 syncbuf.fail(self, e)
1154 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001155 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001156 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001157 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001158 self._CopyFiles()
1159 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001161 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162 # dest should already be an absolute path, but src is project relative
1163 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001164 abssrc = os.path.join(self.worktree, src)
1165 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001166
James W. Mills24c13082012-04-12 15:04:13 -05001167 def AddAnnotation(self, name, value, keep):
1168 self.annotations.append(_Annotation(name, value, keep))
1169
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001170 def DownloadPatchSet(self, change_id, patch_id):
1171 """Download a single patch set of a single change to FETCH_HEAD.
1172 """
1173 remote = self.GetRemote(self.remote.name)
1174
1175 cmd = ['fetch', remote.name]
1176 cmd.append('refs/changes/%2.2d/%d/%d' \
1177 % (change_id % 100, change_id, patch_id))
1178 cmd.extend(map(lambda x: str(x), remote.fetch))
1179 if GitCommand(self, cmd, bare=True).Wait() != 0:
1180 return None
1181 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001182 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001183 change_id,
1184 patch_id,
1185 self.bare_git.rev_parse('FETCH_HEAD'))
1186
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187
1188## Branch Management ##
1189
1190 def StartBranch(self, name):
1191 """Create a new branch off the manifest's revision.
1192 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001193 head = self.work_git.GetHead()
1194 if head == (R_HEADS + name):
1195 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001197 all = self.bare_ref.all
1198 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001199 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001200 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001201 capture_stdout = True,
1202 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001203
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001204 branch = self.GetBranch(name)
1205 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001206 branch.merge = self.revisionExpr
1207 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001208
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001209 if head.startswith(R_HEADS):
1210 try:
1211 head = all[head]
1212 except KeyError:
1213 head = None
1214
1215 if revid and head and revid == head:
1216 ref = os.path.join(self.gitdir, R_HEADS + name)
1217 try:
1218 os.makedirs(os.path.dirname(ref))
1219 except OSError:
1220 pass
1221 _lwrite(ref, '%s\n' % revid)
1222 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1223 'ref: %s%s\n' % (R_HEADS, name))
1224 branch.Save()
1225 return True
1226
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001227 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001228 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001229 capture_stdout = True,
1230 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001231 branch.Save()
1232 return True
1233 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001234
Wink Saville02d79452009-04-10 13:01:24 -07001235 def CheckoutBranch(self, name):
1236 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001237
1238 Args:
1239 name: The name of the branch to checkout.
1240
1241 Returns:
1242 True if the checkout succeeded; False if it didn't; None if the branch
1243 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001244 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001245 rev = R_HEADS + name
1246 head = self.work_git.GetHead()
1247 if head == rev:
1248 # Already on the branch
1249 #
1250 return True
Wink Saville02d79452009-04-10 13:01:24 -07001251
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001252 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001253 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001254 revid = all[rev]
1255 except KeyError:
1256 # Branch does not exist in this project
1257 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001258 return None
Wink Saville02d79452009-04-10 13:01:24 -07001259
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001260 if head.startswith(R_HEADS):
1261 try:
1262 head = all[head]
1263 except KeyError:
1264 head = None
1265
1266 if head == revid:
1267 # Same revision; just update HEAD to point to the new
1268 # target branch, but otherwise take no other action.
1269 #
1270 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1271 'ref: %s%s\n' % (R_HEADS, name))
1272 return True
1273
1274 return GitCommand(self,
1275 ['checkout', name, '--'],
1276 capture_stdout = True,
1277 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001278
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001279 def AbandonBranch(self, name):
1280 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001281
1282 Args:
1283 name: The name of the branch to abandon.
1284
1285 Returns:
1286 True if the abandon succeeded; False if it didn't; None if the branch
1287 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001288 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001289 rev = R_HEADS + name
1290 all = self.bare_ref.all
1291 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001292 # Doesn't exist
1293 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001294
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001295 head = self.work_git.GetHead()
1296 if head == rev:
1297 # We can't destroy the branch while we are sitting
1298 # on it. Switch to a detached HEAD.
1299 #
1300 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001301
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001302 revid = self.GetRevisionId(all)
1303 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001304 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1305 '%s\n' % revid)
1306 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001307 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001308
1309 return GitCommand(self,
1310 ['branch', '-D', name],
1311 capture_stdout = True,
1312 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001313
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001314 def PruneHeads(self):
1315 """Prune any topic branches already merged into upstream.
1316 """
1317 cb = self.CurrentBranch
1318 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001319 left = self._allrefs
1320 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 if name.startswith(R_HEADS):
1322 name = name[len(R_HEADS):]
1323 if cb is None or name != cb:
1324 kill.append(name)
1325
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001326 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327 if cb is not None \
1328 and not self._revlist(HEAD + '...' + rev) \
1329 and not self.IsDirty(consider_untracked = False):
1330 self.work_git.DetachHead(HEAD)
1331 kill.append(cb)
1332
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001333 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001334 old = self.bare_git.GetHead()
1335 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001336 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1337
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001338 try:
1339 self.bare_git.DetachHead(rev)
1340
1341 b = ['branch', '-d']
1342 b.extend(kill)
1343 b = GitCommand(self, b, bare=True,
1344 capture_stdout=True,
1345 capture_stderr=True)
1346 b.Wait()
1347 finally:
1348 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001349 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001351 for branch in kill:
1352 if (R_HEADS + branch) not in left:
1353 self.CleanPublishedCache()
1354 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001355
1356 if cb and cb not in kill:
1357 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001358 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359
1360 kept = []
1361 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001362 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363 branch = self.GetBranch(branch)
1364 base = branch.LocalMerge
1365 if not base:
1366 base = rev
1367 kept.append(ReviewableBranch(self, branch, base))
1368 return kept
1369
1370
1371## Direct Git Commands ##
1372
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001373 def _RemoteFetch(self, name=None,
1374 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001375 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001376 quiet=False,
1377 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001378
1379 is_sha1 = False
1380 tag_name = None
1381
1382 if current_branch_only:
1383 if ID_RE.match(self.revisionExpr) is not None:
1384 is_sha1 = True
1385 elif self.revisionExpr.startswith(R_TAGS):
1386 # this is a tag and its sha1 value should never change
1387 tag_name = self.revisionExpr[len(R_TAGS):]
1388
1389 if is_sha1 or tag_name is not None:
1390 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001391 # if revision (sha or tag) is not present then following function
1392 # throws an error.
1393 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001394 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001395 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001396 # There is no such persistent revision. We have to fetch it.
1397 pass
1398
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399 if not name:
1400 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001401
1402 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001403 remote = self.GetRemote(name)
1404 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001405 ssh_proxy = True
1406
Shawn O. Pearce88443382010-10-08 10:02:09 +02001407 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001408 if alt_dir and 'objects' == os.path.basename(alt_dir):
1409 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001410 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1411 remote = self.GetRemote(name)
1412
1413 all = self.bare_ref.all
1414 ids = set(all.values())
1415 tmp = set()
1416
1417 for r, id in GitRefs(ref_dir).all.iteritems():
1418 if r not in all:
1419 if r.startswith(R_TAGS) or remote.WritesTo(r):
1420 all[r] = id
1421 ids.add(id)
1422 continue
1423
1424 if id in ids:
1425 continue
1426
1427 r = 'refs/_alt/%s' % id
1428 all[r] = id
1429 ids.add(id)
1430 tmp.add(r)
1431
1432 ref_names = list(all.keys())
1433 ref_names.sort()
1434
1435 tmp_packed = ''
1436 old_packed = ''
1437
1438 for r in ref_names:
1439 line = '%s %s\n' % (all[r], r)
1440 tmp_packed += line
1441 if r not in tmp:
1442 old_packed += line
1443
1444 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001445 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001446 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001447
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001448 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001449
1450 # The --depth option only affects the initial fetch; after that we'll do
1451 # full fetches of changes.
1452 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1453 if depth and initial:
1454 cmd.append('--depth=%s' % depth)
1455
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001456 if quiet:
1457 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001458 if not self.worktree:
1459 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001460 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001461
1462 if not current_branch_only or is_sha1:
1463 # Fetch whole repo
1464 cmd.append('--tags')
1465 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1466 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001467 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001468 cmd.append(tag_name)
1469 else:
1470 branch = self.revisionExpr
1471 if branch.startswith(R_HEADS):
1472 branch = branch[len(R_HEADS):]
1473 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001474
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001475 ok = False
1476 for i in range(2):
1477 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1478 ok = True
1479 break
1480 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001481
1482 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001483 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001484 if old_packed != '':
1485 _lwrite(packed_refs, old_packed)
1486 else:
1487 os.remove(packed_refs)
1488 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001489 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001490
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001491 def _ApplyCloneBundle(self, initial=False, quiet=False):
1492 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1493 return False
1494
1495 remote = self.GetRemote(self.remote.name)
1496 bundle_url = remote.url + '/clone.bundle'
1497 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001498 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1499 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001500 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1501 return False
1502
1503 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1504 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1505
1506 exist_dst = os.path.exists(bundle_dst)
1507 exist_tmp = os.path.exists(bundle_tmp)
1508
1509 if not initial and not exist_dst and not exist_tmp:
1510 return False
1511
1512 if not exist_dst:
1513 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1514 if not exist_dst:
1515 return False
1516
1517 cmd = ['fetch']
1518 if quiet:
1519 cmd.append('--quiet')
1520 if not self.worktree:
1521 cmd.append('--update-head-ok')
1522 cmd.append(bundle_dst)
1523 for f in remote.fetch:
1524 cmd.append(str(f))
1525 cmd.append('refs/tags/*:refs/tags/*')
1526
1527 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001528 if os.path.exists(bundle_dst):
1529 os.remove(bundle_dst)
1530 if os.path.exists(bundle_tmp):
1531 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001532 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001533
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001534 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001535 keep = True
1536 done = False
1537 dest = open(tmpPath, 'a+b')
1538 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001539 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001540 pos = dest.tell()
1541
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001542 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001543 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001544 req = urllib2.Request(srcUrl)
1545 if pos > 0:
1546 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001547
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001548 try:
1549 r = urllib2.urlopen(req)
1550 except urllib2.HTTPError, e:
1551 def _content_type():
1552 try:
1553 return e.info()['content-type']
1554 except:
1555 return None
1556
Shawn O. Pearcec3d2f2b2012-03-22 14:09:22 -07001557 if e.code in (401, 403, 404):
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001558 keep = False
1559 return False
1560 elif _content_type() == 'text/plain':
1561 try:
1562 msg = e.read()
1563 if len(msg) > 0 and msg[-1] == '\n':
1564 msg = msg[0:-1]
1565 msg = ' (%s)' % msg
1566 except:
1567 msg = ''
1568 else:
1569 try:
1570 from BaseHTTPServer import BaseHTTPRequestHandler
1571 res = BaseHTTPRequestHandler.responses[e.code]
1572 msg = ' (%s: %s)' % (res[0], res[1])
1573 except:
1574 msg = ''
1575 raise DownloadError('HTTP %s%s' % (e.code, msg))
1576 except urllib2.URLError, e:
1577 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1578 finally:
1579 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001580
1581 p = None
1582 try:
Conley Owens43bda842012-03-12 11:25:04 -07001583 size = r.headers.get('content-length', 0)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001584 unit = 1 << 10
1585
1586 if size and not quiet:
1587 if size > 1024 * 1.3:
1588 unit = 1 << 20
1589 desc = 'MB'
1590 else:
1591 desc = 'KB'
1592 p = Progress(
1593 'Downloading %s' % self.relpath,
1594 int(size) / unit,
1595 units=desc)
1596 if pos > 0:
1597 p.update(pos / unit)
1598
1599 s = 0
1600 while True:
1601 d = r.read(8192)
1602 if d == '':
1603 done = True
1604 return True
1605 dest.write(d)
1606 if p:
1607 s += len(d)
1608 if s >= unit:
1609 p.update(s / unit)
1610 s = s % unit
1611 if p:
1612 if s >= unit:
1613 p.update(s / unit)
1614 else:
1615 p.update(1)
1616 finally:
1617 r.close()
1618 if p:
1619 p.end()
1620 finally:
1621 dest.close()
1622
1623 if os.path.exists(dstPath):
1624 os.remove(dstPath)
1625 if done:
1626 os.rename(tmpPath, dstPath)
1627 elif not keep:
1628 os.remove(tmpPath)
1629
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001630 def _Checkout(self, rev, quiet=False):
1631 cmd = ['checkout']
1632 if quiet:
1633 cmd.append('-q')
1634 cmd.append(rev)
1635 cmd.append('--')
1636 if GitCommand(self, cmd).Wait() != 0:
1637 if self._allrefs:
1638 raise GitError('%s checkout %s ' % (self.name, rev))
1639
1640 def _ResetHard(self, rev, quiet=True):
1641 cmd = ['reset', '--hard']
1642 if quiet:
1643 cmd.append('-q')
1644 cmd.append(rev)
1645 if GitCommand(self, cmd).Wait() != 0:
1646 raise GitError('%s reset --hard %s ' % (self.name, rev))
1647
1648 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001649 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001650 if onto is not None:
1651 cmd.extend(['--onto', onto])
1652 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001653 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001654 raise GitError('%s rebase %s ' % (self.name, upstream))
1655
1656 def _FastForward(self, head):
1657 cmd = ['merge', head]
1658 if GitCommand(self, cmd).Wait() != 0:
1659 raise GitError('%s merge %s ' % (self.name, head))
1660
1661 def _InitGitDir(self):
1662 if not os.path.exists(self.gitdir):
1663 os.makedirs(self.gitdir)
1664 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001665
Shawn O. Pearce88443382010-10-08 10:02:09 +02001666 mp = self.manifest.manifestProject
1667 ref_dir = mp.config.GetString('repo.reference')
1668
1669 if ref_dir:
1670 mirror_git = os.path.join(ref_dir, self.name + '.git')
1671 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1672 self.relpath + '.git')
1673
1674 if os.path.exists(mirror_git):
1675 ref_dir = mirror_git
1676
1677 elif os.path.exists(repo_git):
1678 ref_dir = repo_git
1679
1680 else:
1681 ref_dir = None
1682
1683 if ref_dir:
1684 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1685 os.path.join(ref_dir, 'objects') + '\n')
1686
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001687 if self.manifest.IsMirror:
1688 self.config.SetString('core.bare', 'true')
1689 else:
1690 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691
1692 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001693 try:
1694 to_rm = os.listdir(hooks)
1695 except OSError:
1696 to_rm = []
1697 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001698 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001699 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001700
1701 m = self.manifest.manifestProject.config
1702 for key in ['user.name', 'user.email']:
1703 if m.Has(key, include_defaults = False):
1704 self.config.SetString(key, m.GetString(key))
1705
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001706 def _InitHooks(self):
1707 hooks = self._gitdir_path('hooks')
1708 if not os.path.exists(hooks):
1709 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001710 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001711 name = os.path.basename(stock_hook)
1712
Victor Boivie65e0f352011-04-18 11:23:29 +02001713 if name in ('commit-msg',) and not self.remote.review \
1714 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001715 # Don't install a Gerrit Code Review hook if this
1716 # project does not appear to use it for reviews.
1717 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001718 # Since the manifest project is one of those, but also
1719 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001720 continue
1721
1722 dst = os.path.join(hooks, name)
1723 if os.path.islink(dst):
1724 continue
1725 if os.path.exists(dst):
1726 if filecmp.cmp(stock_hook, dst, shallow=False):
1727 os.remove(dst)
1728 else:
1729 _error("%s: Not replacing %s hook", self.relpath, name)
1730 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001731 try:
1732 os.symlink(relpath(stock_hook, dst), dst)
1733 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001734 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001735 raise GitError('filesystem must support symlinks')
1736 else:
1737 raise
1738
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001739 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001740 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001741 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001742 remote.url = self.remote.url
1743 remote.review = self.remote.review
1744 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001745
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001746 if self.worktree:
1747 remote.ResetFetch(mirror=False)
1748 else:
1749 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001750 remote.Save()
1751
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001752 def _InitMRef(self):
1753 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001754 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001755
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001756 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001757 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001758
1759 def _InitAnyMRef(self, ref):
1760 cur = self.bare_ref.symref(ref)
1761
1762 if self.revisionId:
1763 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1764 msg = 'manifest set to %s' % self.revisionId
1765 dst = self.revisionId + '^0'
1766 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1767 else:
1768 remote = self.GetRemote(self.remote.name)
1769 dst = remote.ToLocal(self.revisionExpr)
1770 if cur != dst:
1771 msg = 'manifest set to %s' % self.revisionExpr
1772 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001773
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001774 def _InitWorkTree(self):
1775 dotgit = os.path.join(self.worktree, '.git')
1776 if not os.path.exists(dotgit):
1777 os.makedirs(dotgit)
1778
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001779 for name in ['config',
1780 'description',
1781 'hooks',
1782 'info',
1783 'logs',
1784 'objects',
1785 'packed-refs',
1786 'refs',
1787 'rr-cache',
1788 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001789 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001790 src = os.path.join(self.gitdir, name)
1791 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001792 if os.path.islink(dst) or not os.path.exists(dst):
1793 os.symlink(relpath(src, dst), dst)
1794 else:
1795 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001796 except OSError, e:
1797 if e.errno == errno.EPERM:
1798 raise GitError('filesystem must support symlinks')
1799 else:
1800 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001801
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001802 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001803
1804 cmd = ['read-tree', '--reset', '-u']
1805 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001806 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001807 if GitCommand(self, cmd).Wait() != 0:
1808 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001809
1810 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1811 if not os.path.exists(rr_cache):
1812 os.makedirs(rr_cache)
1813
Shawn O. Pearce93609662009-04-21 10:50:33 -07001814 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001815
1816 def _gitdir_path(self, path):
1817 return os.path.join(self.gitdir, path)
1818
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001819 def _revlist(self, *args, **kw):
1820 a = []
1821 a.extend(args)
1822 a.append('--')
1823 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001824
1825 @property
1826 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001827 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001828
1829 class _GitGetByExec(object):
1830 def __init__(self, project, bare):
1831 self._project = project
1832 self._bare = bare
1833
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001834 def LsOthers(self):
1835 p = GitCommand(self._project,
1836 ['ls-files',
1837 '-z',
1838 '--others',
1839 '--exclude-standard'],
1840 bare = False,
1841 capture_stdout = True,
1842 capture_stderr = True)
1843 if p.Wait() == 0:
1844 out = p.stdout
1845 if out:
1846 return out[:-1].split("\0")
1847 return []
1848
1849 def DiffZ(self, name, *args):
1850 cmd = [name]
1851 cmd.append('-z')
1852 cmd.extend(args)
1853 p = GitCommand(self._project,
1854 cmd,
1855 bare = False,
1856 capture_stdout = True,
1857 capture_stderr = True)
1858 try:
1859 out = p.process.stdout.read()
1860 r = {}
1861 if out:
1862 out = iter(out[:-1].split('\0'))
1863 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001864 try:
1865 info = out.next()
1866 path = out.next()
1867 except StopIteration:
1868 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001869
1870 class _Info(object):
1871 def __init__(self, path, omode, nmode, oid, nid, state):
1872 self.path = path
1873 self.src_path = None
1874 self.old_mode = omode
1875 self.new_mode = nmode
1876 self.old_id = oid
1877 self.new_id = nid
1878
1879 if len(state) == 1:
1880 self.status = state
1881 self.level = None
1882 else:
1883 self.status = state[:1]
1884 self.level = state[1:]
1885 while self.level.startswith('0'):
1886 self.level = self.level[1:]
1887
1888 info = info[1:].split(' ')
1889 info =_Info(path, *info)
1890 if info.status in ('R', 'C'):
1891 info.src_path = info.path
1892 info.path = out.next()
1893 r[info.path] = info
1894 return r
1895 finally:
1896 p.Wait()
1897
1898 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001899 if self._bare:
1900 path = os.path.join(self._project.gitdir, HEAD)
1901 else:
1902 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001903 fd = open(path, 'rb')
1904 try:
1905 line = fd.read()
1906 finally:
1907 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001908 if line.startswith('ref: '):
1909 return line[5:-1]
1910 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001911
1912 def SetHead(self, ref, message=None):
1913 cmdv = []
1914 if message is not None:
1915 cmdv.extend(['-m', message])
1916 cmdv.append(HEAD)
1917 cmdv.append(ref)
1918 self.symbolic_ref(*cmdv)
1919
1920 def DetachHead(self, new, message=None):
1921 cmdv = ['--no-deref']
1922 if message is not None:
1923 cmdv.extend(['-m', message])
1924 cmdv.append(HEAD)
1925 cmdv.append(new)
1926 self.update_ref(*cmdv)
1927
1928 def UpdateRef(self, name, new, old=None,
1929 message=None,
1930 detach=False):
1931 cmdv = []
1932 if message is not None:
1933 cmdv.extend(['-m', message])
1934 if detach:
1935 cmdv.append('--no-deref')
1936 cmdv.append(name)
1937 cmdv.append(new)
1938 if old is not None:
1939 cmdv.append(old)
1940 self.update_ref(*cmdv)
1941
1942 def DeleteRef(self, name, old=None):
1943 if not old:
1944 old = self.rev_parse(name)
1945 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001946 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001947
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001948 def rev_list(self, *args, **kw):
1949 if 'format' in kw:
1950 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1951 else:
1952 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001953 cmdv.extend(args)
1954 p = GitCommand(self._project,
1955 cmdv,
1956 bare = self._bare,
1957 capture_stdout = True,
1958 capture_stderr = True)
1959 r = []
1960 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001961 if line[-1] == '\n':
1962 line = line[:-1]
1963 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001964 if p.Wait() != 0:
1965 raise GitError('%s rev-list %s: %s' % (
1966 self._project.name,
1967 str(args),
1968 p.stderr))
1969 return r
1970
1971 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001972 """Allow arbitrary git commands using pythonic syntax.
1973
1974 This allows you to do things like:
1975 git_obj.rev_parse('HEAD')
1976
1977 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1978 run. We'll replace the '_' with a '-' and try to run a git command.
1979 Any other arguments will be passed to the git command.
1980
1981 Args:
1982 name: The name of the git command to call. Any '_' characters will
1983 be replaced with '-'.
1984
1985 Returns:
1986 A callable object that will try to call git with the named command.
1987 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001988 name = name.replace('_', '-')
1989 def runner(*args):
1990 cmdv = [name]
1991 cmdv.extend(args)
1992 p = GitCommand(self._project,
1993 cmdv,
1994 bare = self._bare,
1995 capture_stdout = True,
1996 capture_stderr = True)
1997 if p.Wait() != 0:
1998 raise GitError('%s %s: %s' % (
1999 self._project.name,
2000 name,
2001 p.stderr))
2002 r = p.stdout
2003 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2004 return r[:-1]
2005 return r
2006 return runner
2007
2008
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002009class _PriorSyncFailedError(Exception):
2010 def __str__(self):
2011 return 'prior sync failed; rebase still in progress'
2012
2013class _DirtyError(Exception):
2014 def __str__(self):
2015 return 'contains uncommitted changes'
2016
2017class _InfoMessage(object):
2018 def __init__(self, project, text):
2019 self.project = project
2020 self.text = text
2021
2022 def Print(self, syncbuf):
2023 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2024 syncbuf.out.nl()
2025
2026class _Failure(object):
2027 def __init__(self, project, why):
2028 self.project = project
2029 self.why = why
2030
2031 def Print(self, syncbuf):
2032 syncbuf.out.fail('error: %s/: %s',
2033 self.project.relpath,
2034 str(self.why))
2035 syncbuf.out.nl()
2036
2037class _Later(object):
2038 def __init__(self, project, action):
2039 self.project = project
2040 self.action = action
2041
2042 def Run(self, syncbuf):
2043 out = syncbuf.out
2044 out.project('project %s/', self.project.relpath)
2045 out.nl()
2046 try:
2047 self.action()
2048 out.nl()
2049 return True
2050 except GitError, e:
2051 out.nl()
2052 return False
2053
2054class _SyncColoring(Coloring):
2055 def __init__(self, config):
2056 Coloring.__init__(self, config, 'reposync')
2057 self.project = self.printer('header', attr = 'bold')
2058 self.info = self.printer('info')
2059 self.fail = self.printer('fail', fg='red')
2060
2061class SyncBuffer(object):
2062 def __init__(self, config, detach_head=False):
2063 self._messages = []
2064 self._failures = []
2065 self._later_queue1 = []
2066 self._later_queue2 = []
2067
2068 self.out = _SyncColoring(config)
2069 self.out.redirect(sys.stderr)
2070
2071 self.detach_head = detach_head
2072 self.clean = True
2073
2074 def info(self, project, fmt, *args):
2075 self._messages.append(_InfoMessage(project, fmt % args))
2076
2077 def fail(self, project, err=None):
2078 self._failures.append(_Failure(project, err))
2079 self.clean = False
2080
2081 def later1(self, project, what):
2082 self._later_queue1.append(_Later(project, what))
2083
2084 def later2(self, project, what):
2085 self._later_queue2.append(_Later(project, what))
2086
2087 def Finish(self):
2088 self._PrintMessages()
2089 self._RunLater()
2090 self._PrintMessages()
2091 return self.clean
2092
2093 def _RunLater(self):
2094 for q in ['_later_queue1', '_later_queue2']:
2095 if not self._RunQueue(q):
2096 return
2097
2098 def _RunQueue(self, queue):
2099 for m in getattr(self, queue):
2100 if not m.Run(self):
2101 self.clean = False
2102 return False
2103 setattr(self, queue, [])
2104 return True
2105
2106 def _PrintMessages(self):
2107 for m in self._messages:
2108 m.Print(self)
2109 for m in self._failures:
2110 m.Print(self)
2111
2112 self._messages = []
2113 self._failures = []
2114
2115
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002116class MetaProject(Project):
2117 """A special project housed under .repo.
2118 """
2119 def __init__(self, manifest, name, gitdir, worktree):
2120 repodir = manifest.repodir
2121 Project.__init__(self,
2122 manifest = manifest,
2123 name = name,
2124 gitdir = gitdir,
2125 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002126 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002127 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002128 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002129 revisionId = None,
2130 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002131
2132 def PreSync(self):
2133 if self.Exists:
2134 cb = self.CurrentBranch
2135 if cb:
2136 base = self.GetBranch(cb).merge
2137 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002138 self.revisionExpr = base
2139 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002140
2141 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002142 def LastFetch(self):
2143 try:
2144 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2145 return os.path.getmtime(fh)
2146 except OSError:
2147 return 0
2148
2149 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002150 def HasChanges(self):
2151 """Has the remote received new commits not yet checked out?
2152 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002153 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002154 return False
2155
2156 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002157 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002158 head = self.work_git.GetHead()
2159 if head.startswith(R_HEADS):
2160 try:
2161 head = all[head]
2162 except KeyError:
2163 head = None
2164
2165 if revid == head:
2166 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002167 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002168 return True
2169 return False