blob: e297926d1345b457e698402ea38e9a1727e19b4a [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.
660 All projects are implicitly labelled with "default" unless they are
661 explicitly labelled "-default".
662 If any non-inverted manifest groups are specified, the default label
663 is ignored.
664 Specifying only inverted groups implies "default".
665 """
666 project_groups = self.groups
667 if not manifest_groups:
668 return not project_groups or not "-default" in project_groups
669
670 if not project_groups:
671 project_groups = ["default"]
672 elif not ("default" in project_groups or "-default" in project_groups):
673 project_groups.append("default")
674
675 plus_groups = [x for x in manifest_groups if not x.startswith("-")]
676 minus_groups = [x[1:] for x in manifest_groups if x.startswith("-")]
677
678 if not plus_groups:
679 plus_groups.append("default")
680
681 for group in minus_groups:
682 if group in project_groups:
683 # project was excluded by -group
684 return False
685
686 for group in plus_groups:
687 if group in project_groups:
688 # project was included by group
689 return True
690
691 # groups were specified that did not include this project
692 if plus_groups:
693 return False
694 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700695
696## Status Display ##
697
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500698 def HasChanges(self):
699 """Returns true if there are uncommitted changes.
700 """
701 self.work_git.update_index('-q',
702 '--unmerged',
703 '--ignore-missing',
704 '--refresh')
705 if self.IsRebaseInProgress():
706 return True
707
708 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
709 return True
710
711 if self.work_git.DiffZ('diff-files'):
712 return True
713
714 if self.work_git.LsOthers():
715 return True
716
717 return False
718
Terence Haddock4655e812011-03-31 12:33:34 +0200719 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200721
722 Args:
723 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700724 """
725 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200726 if output_redir == None:
727 output_redir = sys.stdout
728 print >>output_redir, ''
729 print >>output_redir, 'project %s/' % self.relpath
730 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700731 return
732
733 self.work_git.update_index('-q',
734 '--unmerged',
735 '--ignore-missing',
736 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700737 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700738 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
739 df = self.work_git.DiffZ('diff-files')
740 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100741 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700742 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743
744 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200745 if not output_redir == None:
746 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700747 out.project('project %-40s', self.relpath + '/')
748
749 branch = self.CurrentBranch
750 if branch is None:
751 out.nobranch('(*** NO BRANCH ***)')
752 else:
753 out.branch('branch %s', branch)
754 out.nl()
755
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700756 if rb:
757 out.important('prior sync failed; rebase still in progress')
758 out.nl()
759
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700760 paths = list()
761 paths.extend(di.keys())
762 paths.extend(df.keys())
763 paths.extend(do)
764
765 paths = list(set(paths))
766 paths.sort()
767
768 for p in paths:
769 try: i = di[p]
770 except KeyError: i = None
771
772 try: f = df[p]
773 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200774
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775 if i: i_status = i.status.upper()
776 else: i_status = '-'
777
778 if f: f_status = f.status.lower()
779 else: f_status = '-'
780
781 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800782 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700783 i.src_path, p, i.level)
784 else:
785 line = ' %s%s\t%s' % (i_status, f_status, p)
786
787 if i and not f:
788 out.added('%s', line)
789 elif (i and f) or (not i and f):
790 out.changed('%s', line)
791 elif not i and not f:
792 out.untracked('%s', line)
793 else:
794 out.write('%s', line)
795 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200796
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700797 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700798
pelyad67872d2012-03-28 14:49:58 +0300799 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700800 """Prints the status of the repository to stdout.
801 """
802 out = DiffColoring(self.config)
803 cmd = ['diff']
804 if out.is_on:
805 cmd.append('--color')
806 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300807 if absolute_paths:
808 cmd.append('--src-prefix=a/%s/' % self.relpath)
809 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810 cmd.append('--')
811 p = GitCommand(self,
812 cmd,
813 capture_stdout = True,
814 capture_stderr = True)
815 has_diff = False
816 for line in p.process.stdout:
817 if not has_diff:
818 out.nl()
819 out.project('project %s/' % self.relpath)
820 out.nl()
821 has_diff = True
822 print line[:-1]
823 p.Wait()
824
825
826## Publish / Upload ##
827
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700828 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829 """Was the branch published (uploaded) for code review?
830 If so, returns the SHA-1 hash of the last published
831 state for the branch.
832 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700833 key = R_PUB + branch
834 if all is None:
835 try:
836 return self.bare_git.rev_parse(key)
837 except GitError:
838 return None
839 else:
840 try:
841 return all[key]
842 except KeyError:
843 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700845 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 """Prunes any stale published refs.
847 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700848 if all is None:
849 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700850 heads = set()
851 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700852 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853 if name.startswith(R_HEADS):
854 heads.add(name)
855 elif name.startswith(R_PUB):
856 canrm[name] = id
857
858 for name, id in canrm.iteritems():
859 n = name[len(R_PUB):]
860 if R_HEADS + n not in heads:
861 self.bare_git.DeleteRef(name, id)
862
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700863 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700864 """List any branches which can be uploaded for review.
865 """
866 heads = {}
867 pubed = {}
868
869 for name, id in self._allrefs.iteritems():
870 if name.startswith(R_HEADS):
871 heads[name[len(R_HEADS):]] = id
872 elif name.startswith(R_PUB):
873 pubed[name[len(R_PUB):]] = id
874
875 ready = []
876 for branch, id in heads.iteritems():
877 if branch in pubed and pubed[branch] == id:
878 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700879 if selected_branch and branch != selected_branch:
880 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800882 rb = self.GetUploadableBranch(branch)
883 if rb:
884 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700885 return ready
886
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800887 def GetUploadableBranch(self, branch_name):
888 """Get a single uploadable branch, or None.
889 """
890 branch = self.GetBranch(branch_name)
891 base = branch.LocalMerge
892 if branch.LocalMerge:
893 rb = ReviewableBranch(self, branch, base)
894 if rb.commits:
895 return rb
896 return None
897
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700898 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700899 people=([],[]),
900 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901 """Uploads the named branch for code review.
902 """
903 if branch is None:
904 branch = self.CurrentBranch
905 if branch is None:
906 raise GitError('not currently on a branch')
907
908 branch = self.GetBranch(branch)
909 if not branch.LocalMerge:
910 raise GitError('branch %s does not track a remote' % branch.name)
911 if not branch.remote.review:
912 raise GitError('remote %s has no review url' % branch.remote.name)
913
914 dest_branch = branch.merge
915 if not dest_branch.startswith(R_HEADS):
916 dest_branch = R_HEADS + dest_branch
917
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800918 if not branch.remote.projectname:
919 branch.remote.projectname = self.name
920 branch.remote.Save()
921
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800922 url = branch.remote.ReviewUrl(self.UserEmail)
923 if url is None:
924 raise UploadError('review not configured')
925 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800926
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800927 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800928 rp = ['gerrit receive-pack']
929 for e in people[0]:
930 rp.append('--reviewer=%s' % sq(e))
931 for e in people[1]:
932 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800933 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700934
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800935 cmd.append(url)
936
937 if dest_branch.startswith(R_HEADS):
938 dest_branch = dest_branch[len(R_HEADS):]
939 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
940 if auto_topic:
941 ref_spec = ref_spec + '/' + branch.name
942 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800943
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800944 if GitCommand(self, cmd, bare = True).Wait() != 0:
945 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946
947 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
948 self.bare_git.UpdateRef(R_PUB + branch.name,
949 R_HEADS + branch.name,
950 message = msg)
951
952
953## Sync ##
954
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700955 def Sync_NetworkHalf(self,
956 quiet=False,
957 is_new=None,
958 current_branch_only=False,
959 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700960 """Perform only the network IO portion of the sync process.
961 Local working directory/branch state is not affected.
962 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700963 if is_new is None:
964 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200965 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966 self._InitGitDir()
967 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700968
969 if is_new:
970 alt = os.path.join(self.gitdir, 'objects/info/alternates')
971 try:
972 fd = open(alt, 'rb')
973 try:
974 alt_dir = fd.readline().rstrip()
975 finally:
976 fd.close()
977 except IOError:
978 alt_dir = None
979 else:
980 alt_dir = None
981
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700982 if clone_bundle \
983 and alt_dir is None \
984 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700985 is_new = False
986
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700987 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
988 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800990
991 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800992 self._InitMRef()
993 else:
994 self._InitMirrorHead()
995 try:
996 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
997 except OSError:
998 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700999 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001000
1001 def PostRepoUpgrade(self):
1002 self._InitHooks()
1003
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001004 def _CopyFiles(self):
1005 for file in self.copyfiles:
1006 file._Copy()
1007
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001008 def GetRevisionId(self, all=None):
1009 if self.revisionId:
1010 return self.revisionId
1011
1012 rem = self.GetRemote(self.remote.name)
1013 rev = rem.ToLocal(self.revisionExpr)
1014
1015 if all is not None and rev in all:
1016 return all[rev]
1017
1018 try:
1019 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1020 except GitError:
1021 raise ManifestInvalidRevisionError(
1022 'revision %s in %s not found' % (self.revisionExpr,
1023 self.name))
1024
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001025 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001026 """Perform only the local IO portion of the sync process.
1027 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001029 all = self.bare_ref.all
1030 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001031 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001032
1033 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001034 head = self.work_git.GetHead()
1035 if head.startswith(R_HEADS):
1036 branch = head[len(R_HEADS):]
1037 try:
1038 head = all[head]
1039 except KeyError:
1040 head = None
1041 else:
1042 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001043
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001044 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001045 # Currently on a detached HEAD. The user is assumed to
1046 # not have any local modifications worth worrying about.
1047 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001048 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001049 syncbuf.fail(self, _PriorSyncFailedError())
1050 return
1051
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001052 if head == revid:
1053 # No changes; don't do anything further.
1054 #
1055 return
1056
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001057 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001059 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001061 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001062 except GitError, e:
1063 syncbuf.fail(self, e)
1064 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001066 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001068 if head == revid:
1069 # No changes; don't do anything further.
1070 #
1071 return
1072
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001073 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001075 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001077 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001079 syncbuf.info(self,
1080 "leaving %s; does not track upstream",
1081 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001083 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001084 except GitError, e:
1085 syncbuf.fail(self, e)
1086 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001088 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001090 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001091 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001092 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001093 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001094 if not_merged:
1095 if upstream_gain:
1096 # The user has published this branch and some of those
1097 # commits are not yet merged upstream. We do not want
1098 # to rewrite the published commits so we punt.
1099 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001100 syncbuf.fail(self,
1101 "branch %s is published (but not merged) and is now %d commits behind"
1102 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001103 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001104 elif pub == head:
1105 # All published commits are merged, and thus we are a
1106 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001107 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001108 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001109 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001110 self._CopyFiles()
1111 syncbuf.later1(self, _doff)
1112 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001113
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001114 # Examine the local commits not in the remote. Find the
1115 # last one attributed to this user, if any.
1116 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001117 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001118 last_mine = None
1119 cnt_mine = 0
1120 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001121 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001122 if committer_email == self.UserEmail:
1123 last_mine = commit_id
1124 cnt_mine += 1
1125
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001126 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001127 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001128
1129 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001130 syncbuf.fail(self, _DirtyError())
1131 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001132
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001133 # If the upstream switched on us, warn the user.
1134 #
1135 if branch.merge != self.revisionExpr:
1136 if branch.merge and self.revisionExpr:
1137 syncbuf.info(self,
1138 'manifest switched %s...%s',
1139 branch.merge,
1140 self.revisionExpr)
1141 elif branch.merge:
1142 syncbuf.info(self,
1143 'manifest no longer tracks %s',
1144 branch.merge)
1145
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001146 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001147 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001148 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001150 syncbuf.info(self,
1151 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001152 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001153
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001154 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001155 if not ID_RE.match(self.revisionExpr):
1156 # in case of manifest sync the revisionExpr might be a SHA1
1157 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001158 branch.Save()
1159
Mike Pontillod3153822012-02-28 11:53:24 -08001160 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001161 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001162 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001163 self._CopyFiles()
1164 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001165 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001166 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001167 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001168 self._CopyFiles()
1169 except GitError, e:
1170 syncbuf.fail(self, e)
1171 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001173 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001174 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001175 self._CopyFiles()
1176 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001177
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001178 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179 # dest should already be an absolute path, but src is project relative
1180 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001181 abssrc = os.path.join(self.worktree, src)
1182 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001183
James W. Mills24c13082012-04-12 15:04:13 -05001184 def AddAnnotation(self, name, value, keep):
1185 self.annotations.append(_Annotation(name, value, keep))
1186
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001187 def DownloadPatchSet(self, change_id, patch_id):
1188 """Download a single patch set of a single change to FETCH_HEAD.
1189 """
1190 remote = self.GetRemote(self.remote.name)
1191
1192 cmd = ['fetch', remote.name]
1193 cmd.append('refs/changes/%2.2d/%d/%d' \
1194 % (change_id % 100, change_id, patch_id))
1195 cmd.extend(map(lambda x: str(x), remote.fetch))
1196 if GitCommand(self, cmd, bare=True).Wait() != 0:
1197 return None
1198 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001199 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001200 change_id,
1201 patch_id,
1202 self.bare_git.rev_parse('FETCH_HEAD'))
1203
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204
1205## Branch Management ##
1206
1207 def StartBranch(self, name):
1208 """Create a new branch off the manifest's revision.
1209 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001210 head = self.work_git.GetHead()
1211 if head == (R_HEADS + name):
1212 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001214 all = self.bare_ref.all
1215 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001216 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001217 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001218 capture_stdout = True,
1219 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001220
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001221 branch = self.GetBranch(name)
1222 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001223 branch.merge = self.revisionExpr
1224 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001225
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001226 if head.startswith(R_HEADS):
1227 try:
1228 head = all[head]
1229 except KeyError:
1230 head = None
1231
1232 if revid and head and revid == head:
1233 ref = os.path.join(self.gitdir, R_HEADS + name)
1234 try:
1235 os.makedirs(os.path.dirname(ref))
1236 except OSError:
1237 pass
1238 _lwrite(ref, '%s\n' % revid)
1239 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1240 'ref: %s%s\n' % (R_HEADS, name))
1241 branch.Save()
1242 return True
1243
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001244 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001245 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001246 capture_stdout = True,
1247 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001248 branch.Save()
1249 return True
1250 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001251
Wink Saville02d79452009-04-10 13:01:24 -07001252 def CheckoutBranch(self, name):
1253 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001254
1255 Args:
1256 name: The name of the branch to checkout.
1257
1258 Returns:
1259 True if the checkout succeeded; False if it didn't; None if the branch
1260 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001261 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001262 rev = R_HEADS + name
1263 head = self.work_git.GetHead()
1264 if head == rev:
1265 # Already on the branch
1266 #
1267 return True
Wink Saville02d79452009-04-10 13:01:24 -07001268
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001269 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001270 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001271 revid = all[rev]
1272 except KeyError:
1273 # Branch does not exist in this project
1274 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001275 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001276
1277 if head.startswith(R_HEADS):
1278 try:
1279 head = all[head]
1280 except KeyError:
1281 head = None
1282
1283 if head == revid:
1284 # Same revision; just update HEAD to point to the new
1285 # target branch, but otherwise take no other action.
1286 #
1287 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1288 'ref: %s%s\n' % (R_HEADS, name))
1289 return True
Wink Saville02d79452009-04-10 13:01:24 -07001290
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001291 return GitCommand(self,
1292 ['checkout', name, '--'],
1293 capture_stdout = True,
1294 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001295
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001296 def AbandonBranch(self, name):
1297 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001298
1299 Args:
1300 name: The name of the branch to abandon.
1301
1302 Returns:
1303 True if the abandon succeeded; False if it didn't; None if the branch
1304 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001305 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001306 rev = R_HEADS + name
1307 all = self.bare_ref.all
1308 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001309 # Doesn't exist
1310 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001311
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001312 head = self.work_git.GetHead()
1313 if head == rev:
1314 # We can't destroy the branch while we are sitting
1315 # on it. Switch to a detached HEAD.
1316 #
1317 head = all[head]
1318
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001319 revid = self.GetRevisionId(all)
1320 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001321 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1322 '%s\n' % revid)
1323 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001324 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001325
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001326 return GitCommand(self,
1327 ['branch', '-D', name],
1328 capture_stdout = True,
1329 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001331 def PruneHeads(self):
1332 """Prune any topic branches already merged into upstream.
1333 """
1334 cb = self.CurrentBranch
1335 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001336 left = self._allrefs
1337 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001338 if name.startswith(R_HEADS):
1339 name = name[len(R_HEADS):]
1340 if cb is None or name != cb:
1341 kill.append(name)
1342
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001343 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344 if cb is not None \
1345 and not self._revlist(HEAD + '...' + rev) \
1346 and not self.IsDirty(consider_untracked = False):
1347 self.work_git.DetachHead(HEAD)
1348 kill.append(cb)
1349
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001351 old = self.bare_git.GetHead()
1352 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1354
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001355 try:
1356 self.bare_git.DetachHead(rev)
1357
1358 b = ['branch', '-d']
1359 b.extend(kill)
1360 b = GitCommand(self, b, bare=True,
1361 capture_stdout=True,
1362 capture_stderr=True)
1363 b.Wait()
1364 finally:
1365 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001366 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001368 for branch in kill:
1369 if (R_HEADS + branch) not in left:
1370 self.CleanPublishedCache()
1371 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001372
1373 if cb and cb not in kill:
1374 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001375 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376
1377 kept = []
1378 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001379 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001380 branch = self.GetBranch(branch)
1381 base = branch.LocalMerge
1382 if not base:
1383 base = rev
1384 kept.append(ReviewableBranch(self, branch, base))
1385 return kept
1386
1387
1388## Direct Git Commands ##
1389
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001390 def _RemoteFetch(self, name=None,
1391 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001392 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001393 quiet=False,
1394 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001395
1396 is_sha1 = False
1397 tag_name = None
1398
1399 if current_branch_only:
1400 if ID_RE.match(self.revisionExpr) is not None:
1401 is_sha1 = True
1402 elif self.revisionExpr.startswith(R_TAGS):
1403 # this is a tag and its sha1 value should never change
1404 tag_name = self.revisionExpr[len(R_TAGS):]
1405
1406 if is_sha1 or tag_name is not None:
1407 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001408 # if revision (sha or tag) is not present then following function
1409 # throws an error.
1410 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001411 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001412 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001413 # There is no such persistent revision. We have to fetch it.
1414 pass
1415
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001416 if not name:
1417 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001418
1419 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001420 remote = self.GetRemote(name)
1421 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001422 ssh_proxy = True
1423
Shawn O. Pearce88443382010-10-08 10:02:09 +02001424 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001425 if alt_dir and 'objects' == os.path.basename(alt_dir):
1426 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001427 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1428 remote = self.GetRemote(name)
1429
1430 all = self.bare_ref.all
1431 ids = set(all.values())
1432 tmp = set()
1433
1434 for r, id in GitRefs(ref_dir).all.iteritems():
1435 if r not in all:
1436 if r.startswith(R_TAGS) or remote.WritesTo(r):
1437 all[r] = id
1438 ids.add(id)
1439 continue
1440
1441 if id in ids:
1442 continue
1443
1444 r = 'refs/_alt/%s' % id
1445 all[r] = id
1446 ids.add(id)
1447 tmp.add(r)
1448
1449 ref_names = list(all.keys())
1450 ref_names.sort()
1451
1452 tmp_packed = ''
1453 old_packed = ''
1454
1455 for r in ref_names:
1456 line = '%s %s\n' % (all[r], r)
1457 tmp_packed += line
1458 if r not in tmp:
1459 old_packed += line
1460
1461 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001462 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001463 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001464
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001465 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001466
1467 # The --depth option only affects the initial fetch; after that we'll do
1468 # full fetches of changes.
1469 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1470 if depth and initial:
1471 cmd.append('--depth=%s' % depth)
1472
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001473 if quiet:
1474 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001475 if not self.worktree:
1476 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001477 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001478
1479 if not current_branch_only or is_sha1:
1480 # Fetch whole repo
1481 cmd.append('--tags')
1482 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1483 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001484 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001485 cmd.append(tag_name)
1486 else:
1487 branch = self.revisionExpr
1488 if branch.startswith(R_HEADS):
1489 branch = branch[len(R_HEADS):]
1490 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001491
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001492 ok = False
1493 for i in range(2):
1494 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1495 ok = True
1496 break
1497 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001498
1499 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001500 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001501 if old_packed != '':
1502 _lwrite(packed_refs, old_packed)
1503 else:
1504 os.remove(packed_refs)
1505 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001506 return ok
1507
1508 def _ApplyCloneBundle(self, initial=False, quiet=False):
1509 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1510 return False
1511
1512 remote = self.GetRemote(self.remote.name)
1513 bundle_url = remote.url + '/clone.bundle'
1514 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001515 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1516 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001517 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1518 return False
1519
1520 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1521 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001522
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001523 exist_dst = os.path.exists(bundle_dst)
1524 exist_tmp = os.path.exists(bundle_tmp)
1525
1526 if not initial and not exist_dst and not exist_tmp:
1527 return False
1528
1529 if not exist_dst:
1530 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1531 if not exist_dst:
1532 return False
1533
1534 cmd = ['fetch']
1535 if quiet:
1536 cmd.append('--quiet')
1537 if not self.worktree:
1538 cmd.append('--update-head-ok')
1539 cmd.append(bundle_dst)
1540 for f in remote.fetch:
1541 cmd.append(str(f))
1542 cmd.append('refs/tags/*:refs/tags/*')
1543
1544 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001545 if os.path.exists(bundle_dst):
1546 os.remove(bundle_dst)
1547 if os.path.exists(bundle_tmp):
1548 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001549 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001550
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001551 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001552 keep = True
1553 done = False
1554 dest = open(tmpPath, 'a+b')
1555 try:
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -07001556 dest.seek(0, SEEK_END)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001557 pos = dest.tell()
1558
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001559 _urllib_lock.acquire()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001560 try:
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001561 req = urllib2.Request(srcUrl)
1562 if pos > 0:
1563 req.add_header('Range', 'bytes=%d-' % pos)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001564
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001565 try:
1566 r = urllib2.urlopen(req)
1567 except urllib2.HTTPError, e:
1568 def _content_type():
1569 try:
1570 return e.info()['content-type']
1571 except:
1572 return None
1573
Shawn O. Pearcec3d2f2b2012-03-22 14:09:22 -07001574 if e.code in (401, 403, 404):
Shawn O. Pearcefab96c62011-10-11 12:00:38 -07001575 keep = False
1576 return False
1577 elif _content_type() == 'text/plain':
1578 try:
1579 msg = e.read()
1580 if len(msg) > 0 and msg[-1] == '\n':
1581 msg = msg[0:-1]
1582 msg = ' (%s)' % msg
1583 except:
1584 msg = ''
1585 else:
1586 try:
1587 from BaseHTTPServer import BaseHTTPRequestHandler
1588 res = BaseHTTPRequestHandler.responses[e.code]
1589 msg = ' (%s: %s)' % (res[0], res[1])
1590 except:
1591 msg = ''
1592 raise DownloadError('HTTP %s%s' % (e.code, msg))
1593 except urllib2.URLError, e:
1594 raise DownloadError('%s: %s ' % (req.get_host(), str(e)))
1595 finally:
1596 _urllib_lock.release()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001597
1598 p = None
1599 try:
Conley Owens43bda842012-03-12 11:25:04 -07001600 size = r.headers.get('content-length', 0)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001601 unit = 1 << 10
1602
1603 if size and not quiet:
1604 if size > 1024 * 1.3:
1605 unit = 1 << 20
1606 desc = 'MB'
1607 else:
1608 desc = 'KB'
1609 p = Progress(
1610 'Downloading %s' % self.relpath,
1611 int(size) / unit,
1612 units=desc)
1613 if pos > 0:
1614 p.update(pos / unit)
1615
1616 s = 0
1617 while True:
1618 d = r.read(8192)
1619 if d == '':
1620 done = True
1621 return True
1622 dest.write(d)
1623 if p:
1624 s += len(d)
1625 if s >= unit:
1626 p.update(s / unit)
1627 s = s % unit
1628 if p:
1629 if s >= unit:
1630 p.update(s / unit)
1631 else:
1632 p.update(1)
1633 finally:
1634 r.close()
1635 if p:
1636 p.end()
1637 finally:
1638 dest.close()
1639
1640 if os.path.exists(dstPath):
1641 os.remove(dstPath)
1642 if done:
1643 os.rename(tmpPath, dstPath)
1644 elif not keep:
1645 os.remove(tmpPath)
1646
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001647 def _Checkout(self, rev, quiet=False):
1648 cmd = ['checkout']
1649 if quiet:
1650 cmd.append('-q')
1651 cmd.append(rev)
1652 cmd.append('--')
1653 if GitCommand(self, cmd).Wait() != 0:
1654 if self._allrefs:
1655 raise GitError('%s checkout %s ' % (self.name, rev))
1656
1657 def _ResetHard(self, rev, quiet=True):
1658 cmd = ['reset', '--hard']
1659 if quiet:
1660 cmd.append('-q')
1661 cmd.append(rev)
1662 if GitCommand(self, cmd).Wait() != 0:
1663 raise GitError('%s reset --hard %s ' % (self.name, rev))
1664
1665 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001666 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001667 if onto is not None:
1668 cmd.extend(['--onto', onto])
1669 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001670 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001671 raise GitError('%s rebase %s ' % (self.name, upstream))
1672
1673 def _FastForward(self, head):
1674 cmd = ['merge', head]
1675 if GitCommand(self, cmd).Wait() != 0:
1676 raise GitError('%s merge %s ' % (self.name, head))
1677
1678 def _InitGitDir(self):
1679 if not os.path.exists(self.gitdir):
1680 os.makedirs(self.gitdir)
1681 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001682
Shawn O. Pearce88443382010-10-08 10:02:09 +02001683 mp = self.manifest.manifestProject
1684 ref_dir = mp.config.GetString('repo.reference')
1685
1686 if ref_dir:
1687 mirror_git = os.path.join(ref_dir, self.name + '.git')
1688 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1689 self.relpath + '.git')
1690
1691 if os.path.exists(mirror_git):
1692 ref_dir = mirror_git
1693
1694 elif os.path.exists(repo_git):
1695 ref_dir = repo_git
1696
1697 else:
1698 ref_dir = None
1699
1700 if ref_dir:
1701 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1702 os.path.join(ref_dir, 'objects') + '\n')
1703
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001704 if self.manifest.IsMirror:
1705 self.config.SetString('core.bare', 'true')
1706 else:
1707 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001708
1709 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001710 try:
1711 to_rm = os.listdir(hooks)
1712 except OSError:
1713 to_rm = []
1714 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001715 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001716 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001717
1718 m = self.manifest.manifestProject.config
1719 for key in ['user.name', 'user.email']:
1720 if m.Has(key, include_defaults = False):
1721 self.config.SetString(key, m.GetString(key))
1722
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001723 def _InitHooks(self):
1724 hooks = self._gitdir_path('hooks')
1725 if not os.path.exists(hooks):
1726 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001727 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001728 name = os.path.basename(stock_hook)
1729
Victor Boivie65e0f352011-04-18 11:23:29 +02001730 if name in ('commit-msg',) and not self.remote.review \
1731 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001732 # Don't install a Gerrit Code Review hook if this
1733 # project does not appear to use it for reviews.
1734 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001735 # Since the manifest project is one of those, but also
1736 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001737 continue
1738
1739 dst = os.path.join(hooks, name)
1740 if os.path.islink(dst):
1741 continue
1742 if os.path.exists(dst):
1743 if filecmp.cmp(stock_hook, dst, shallow=False):
1744 os.remove(dst)
1745 else:
1746 _error("%s: Not replacing %s hook", self.relpath, name)
1747 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001748 try:
1749 os.symlink(relpath(stock_hook, dst), dst)
1750 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001751 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001752 raise GitError('filesystem must support symlinks')
1753 else:
1754 raise
1755
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001756 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001757 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001759 remote.url = self.remote.url
1760 remote.review = self.remote.review
1761 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001762
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001763 if self.worktree:
1764 remote.ResetFetch(mirror=False)
1765 else:
1766 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001767 remote.Save()
1768
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769 def _InitMRef(self):
1770 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001771 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001772
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001773 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001774 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001775
1776 def _InitAnyMRef(self, ref):
1777 cur = self.bare_ref.symref(ref)
1778
1779 if self.revisionId:
1780 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1781 msg = 'manifest set to %s' % self.revisionId
1782 dst = self.revisionId + '^0'
1783 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1784 else:
1785 remote = self.GetRemote(self.remote.name)
1786 dst = remote.ToLocal(self.revisionExpr)
1787 if cur != dst:
1788 msg = 'manifest set to %s' % self.revisionExpr
1789 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001790
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001791 def _InitWorkTree(self):
1792 dotgit = os.path.join(self.worktree, '.git')
1793 if not os.path.exists(dotgit):
1794 os.makedirs(dotgit)
1795
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001796 for name in ['config',
1797 'description',
1798 'hooks',
1799 'info',
1800 'logs',
1801 'objects',
1802 'packed-refs',
1803 'refs',
1804 'rr-cache',
1805 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001806 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001807 src = os.path.join(self.gitdir, name)
1808 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001809 if os.path.islink(dst) or not os.path.exists(dst):
1810 os.symlink(relpath(src, dst), dst)
1811 else:
1812 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001813 except OSError, e:
1814 if e.errno == errno.EPERM:
1815 raise GitError('filesystem must support symlinks')
1816 else:
1817 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001818
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001819 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001820
1821 cmd = ['read-tree', '--reset', '-u']
1822 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001823 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001824 if GitCommand(self, cmd).Wait() != 0:
1825 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001826
1827 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1828 if not os.path.exists(rr_cache):
1829 os.makedirs(rr_cache)
1830
Shawn O. Pearce93609662009-04-21 10:50:33 -07001831 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001832
1833 def _gitdir_path(self, path):
1834 return os.path.join(self.gitdir, path)
1835
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001836 def _revlist(self, *args, **kw):
1837 a = []
1838 a.extend(args)
1839 a.append('--')
1840 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001841
1842 @property
1843 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001844 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001845
1846 class _GitGetByExec(object):
1847 def __init__(self, project, bare):
1848 self._project = project
1849 self._bare = bare
1850
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001851 def LsOthers(self):
1852 p = GitCommand(self._project,
1853 ['ls-files',
1854 '-z',
1855 '--others',
1856 '--exclude-standard'],
1857 bare = False,
1858 capture_stdout = True,
1859 capture_stderr = True)
1860 if p.Wait() == 0:
1861 out = p.stdout
1862 if out:
1863 return out[:-1].split("\0")
1864 return []
1865
1866 def DiffZ(self, name, *args):
1867 cmd = [name]
1868 cmd.append('-z')
1869 cmd.extend(args)
1870 p = GitCommand(self._project,
1871 cmd,
1872 bare = False,
1873 capture_stdout = True,
1874 capture_stderr = True)
1875 try:
1876 out = p.process.stdout.read()
1877 r = {}
1878 if out:
1879 out = iter(out[:-1].split('\0'))
1880 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001881 try:
1882 info = out.next()
1883 path = out.next()
1884 except StopIteration:
1885 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001886
1887 class _Info(object):
1888 def __init__(self, path, omode, nmode, oid, nid, state):
1889 self.path = path
1890 self.src_path = None
1891 self.old_mode = omode
1892 self.new_mode = nmode
1893 self.old_id = oid
1894 self.new_id = nid
1895
1896 if len(state) == 1:
1897 self.status = state
1898 self.level = None
1899 else:
1900 self.status = state[:1]
1901 self.level = state[1:]
1902 while self.level.startswith('0'):
1903 self.level = self.level[1:]
1904
1905 info = info[1:].split(' ')
1906 info =_Info(path, *info)
1907 if info.status in ('R', 'C'):
1908 info.src_path = info.path
1909 info.path = out.next()
1910 r[info.path] = info
1911 return r
1912 finally:
1913 p.Wait()
1914
1915 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001916 if self._bare:
1917 path = os.path.join(self._project.gitdir, HEAD)
1918 else:
1919 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001920 fd = open(path, 'rb')
1921 try:
1922 line = fd.read()
1923 finally:
1924 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001925 if line.startswith('ref: '):
1926 return line[5:-1]
1927 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001928
1929 def SetHead(self, ref, message=None):
1930 cmdv = []
1931 if message is not None:
1932 cmdv.extend(['-m', message])
1933 cmdv.append(HEAD)
1934 cmdv.append(ref)
1935 self.symbolic_ref(*cmdv)
1936
1937 def DetachHead(self, new, message=None):
1938 cmdv = ['--no-deref']
1939 if message is not None:
1940 cmdv.extend(['-m', message])
1941 cmdv.append(HEAD)
1942 cmdv.append(new)
1943 self.update_ref(*cmdv)
1944
1945 def UpdateRef(self, name, new, old=None,
1946 message=None,
1947 detach=False):
1948 cmdv = []
1949 if message is not None:
1950 cmdv.extend(['-m', message])
1951 if detach:
1952 cmdv.append('--no-deref')
1953 cmdv.append(name)
1954 cmdv.append(new)
1955 if old is not None:
1956 cmdv.append(old)
1957 self.update_ref(*cmdv)
1958
1959 def DeleteRef(self, name, old=None):
1960 if not old:
1961 old = self.rev_parse(name)
1962 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001963 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001964
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001965 def rev_list(self, *args, **kw):
1966 if 'format' in kw:
1967 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1968 else:
1969 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001970 cmdv.extend(args)
1971 p = GitCommand(self._project,
1972 cmdv,
1973 bare = self._bare,
1974 capture_stdout = True,
1975 capture_stderr = True)
1976 r = []
1977 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001978 if line[-1] == '\n':
1979 line = line[:-1]
1980 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001981 if p.Wait() != 0:
1982 raise GitError('%s rev-list %s: %s' % (
1983 self._project.name,
1984 str(args),
1985 p.stderr))
1986 return r
1987
1988 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001989 """Allow arbitrary git commands using pythonic syntax.
1990
1991 This allows you to do things like:
1992 git_obj.rev_parse('HEAD')
1993
1994 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1995 run. We'll replace the '_' with a '-' and try to run a git command.
1996 Any other arguments will be passed to the git command.
1997
1998 Args:
1999 name: The name of the git command to call. Any '_' characters will
2000 be replaced with '-'.
2001
2002 Returns:
2003 A callable object that will try to call git with the named command.
2004 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002005 name = name.replace('_', '-')
2006 def runner(*args):
2007 cmdv = [name]
2008 cmdv.extend(args)
2009 p = GitCommand(self._project,
2010 cmdv,
2011 bare = self._bare,
2012 capture_stdout = True,
2013 capture_stderr = True)
2014 if p.Wait() != 0:
2015 raise GitError('%s %s: %s' % (
2016 self._project.name,
2017 name,
2018 p.stderr))
2019 r = p.stdout
2020 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2021 return r[:-1]
2022 return r
2023 return runner
2024
2025
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002026class _PriorSyncFailedError(Exception):
2027 def __str__(self):
2028 return 'prior sync failed; rebase still in progress'
2029
2030class _DirtyError(Exception):
2031 def __str__(self):
2032 return 'contains uncommitted changes'
2033
2034class _InfoMessage(object):
2035 def __init__(self, project, text):
2036 self.project = project
2037 self.text = text
2038
2039 def Print(self, syncbuf):
2040 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2041 syncbuf.out.nl()
2042
2043class _Failure(object):
2044 def __init__(self, project, why):
2045 self.project = project
2046 self.why = why
2047
2048 def Print(self, syncbuf):
2049 syncbuf.out.fail('error: %s/: %s',
2050 self.project.relpath,
2051 str(self.why))
2052 syncbuf.out.nl()
2053
2054class _Later(object):
2055 def __init__(self, project, action):
2056 self.project = project
2057 self.action = action
2058
2059 def Run(self, syncbuf):
2060 out = syncbuf.out
2061 out.project('project %s/', self.project.relpath)
2062 out.nl()
2063 try:
2064 self.action()
2065 out.nl()
2066 return True
2067 except GitError, e:
2068 out.nl()
2069 return False
2070
2071class _SyncColoring(Coloring):
2072 def __init__(self, config):
2073 Coloring.__init__(self, config, 'reposync')
2074 self.project = self.printer('header', attr = 'bold')
2075 self.info = self.printer('info')
2076 self.fail = self.printer('fail', fg='red')
2077
2078class SyncBuffer(object):
2079 def __init__(self, config, detach_head=False):
2080 self._messages = []
2081 self._failures = []
2082 self._later_queue1 = []
2083 self._later_queue2 = []
2084
2085 self.out = _SyncColoring(config)
2086 self.out.redirect(sys.stderr)
2087
2088 self.detach_head = detach_head
2089 self.clean = True
2090
2091 def info(self, project, fmt, *args):
2092 self._messages.append(_InfoMessage(project, fmt % args))
2093
2094 def fail(self, project, err=None):
2095 self._failures.append(_Failure(project, err))
2096 self.clean = False
2097
2098 def later1(self, project, what):
2099 self._later_queue1.append(_Later(project, what))
2100
2101 def later2(self, project, what):
2102 self._later_queue2.append(_Later(project, what))
2103
2104 def Finish(self):
2105 self._PrintMessages()
2106 self._RunLater()
2107 self._PrintMessages()
2108 return self.clean
2109
2110 def _RunLater(self):
2111 for q in ['_later_queue1', '_later_queue2']:
2112 if not self._RunQueue(q):
2113 return
2114
2115 def _RunQueue(self, queue):
2116 for m in getattr(self, queue):
2117 if not m.Run(self):
2118 self.clean = False
2119 return False
2120 setattr(self, queue, [])
2121 return True
2122
2123 def _PrintMessages(self):
2124 for m in self._messages:
2125 m.Print(self)
2126 for m in self._failures:
2127 m.Print(self)
2128
2129 self._messages = []
2130 self._failures = []
2131
2132
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002133class MetaProject(Project):
2134 """A special project housed under .repo.
2135 """
2136 def __init__(self, manifest, name, gitdir, worktree):
2137 repodir = manifest.repodir
2138 Project.__init__(self,
2139 manifest = manifest,
2140 name = name,
2141 gitdir = gitdir,
2142 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002143 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002144 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002145 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002146 revisionId = None,
2147 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002148
2149 def PreSync(self):
2150 if self.Exists:
2151 cb = self.CurrentBranch
2152 if cb:
2153 base = self.GetBranch(cb).merge
2154 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002155 self.revisionExpr = base
2156 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002157
2158 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002159 def LastFetch(self):
2160 try:
2161 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2162 return os.path.getmtime(fh)
2163 except OSError:
2164 return 0
2165
2166 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002167 def HasChanges(self):
2168 """Has the remote received new commits not yet checked out?
2169 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002170 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002171 return False
2172
2173 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002174 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002175 head = self.work_git.GetHead()
2176 if head.startswith(R_HEADS):
2177 try:
2178 head = all[head]
2179 except KeyError:
2180 head = None
2181
2182 if revid == head:
2183 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002184 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002185 return True
2186 return False