blob: 49da34a8656f148ec6b8f1614cef05da67fa5736 [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".
Colin Cross5acde752012-03-28 20:15:45 -0700663
Conley Owens971de8e2012-04-16 10:36:08 -0700664 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.
668 """
669 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)
918
919 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)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800925
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800926 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
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001259
1260 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
Wink Saville02d79452009-04-10 13:01:24 -07001273
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001274 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]
1301
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. Pearce9fa44db2008-11-03 11:24:59 -08001308
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001309 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
1490
1491 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')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001505
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001506 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
Pierre Tardye5a21222011-03-24 16:28:18 +01001640 def _CherryPick(self, rev, quiet=False):
1641 cmd = ['cherry-pick']
1642 cmd.append(rev)
1643 cmd.append('--')
1644 if GitCommand(self, cmd).Wait() != 0:
1645 if self._allrefs:
1646 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1647
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001648 def _ResetHard(self, rev, quiet=True):
1649 cmd = ['reset', '--hard']
1650 if quiet:
1651 cmd.append('-q')
1652 cmd.append(rev)
1653 if GitCommand(self, cmd).Wait() != 0:
1654 raise GitError('%s reset --hard %s ' % (self.name, rev))
1655
1656 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001657 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001658 if onto is not None:
1659 cmd.extend(['--onto', onto])
1660 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001661 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001662 raise GitError('%s rebase %s ' % (self.name, upstream))
1663
1664 def _FastForward(self, head):
1665 cmd = ['merge', head]
1666 if GitCommand(self, cmd).Wait() != 0:
1667 raise GitError('%s merge %s ' % (self.name, head))
1668
1669 def _InitGitDir(self):
1670 if not os.path.exists(self.gitdir):
1671 os.makedirs(self.gitdir)
1672 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001673
Shawn O. Pearce88443382010-10-08 10:02:09 +02001674 mp = self.manifest.manifestProject
1675 ref_dir = mp.config.GetString('repo.reference')
1676
1677 if ref_dir:
1678 mirror_git = os.path.join(ref_dir, self.name + '.git')
1679 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1680 self.relpath + '.git')
1681
1682 if os.path.exists(mirror_git):
1683 ref_dir = mirror_git
1684
1685 elif os.path.exists(repo_git):
1686 ref_dir = repo_git
1687
1688 else:
1689 ref_dir = None
1690
1691 if ref_dir:
1692 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1693 os.path.join(ref_dir, 'objects') + '\n')
1694
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001695 if self.manifest.IsMirror:
1696 self.config.SetString('core.bare', 'true')
1697 else:
1698 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001699
1700 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001701 try:
1702 to_rm = os.listdir(hooks)
1703 except OSError:
1704 to_rm = []
1705 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001706 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001707 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001708
1709 m = self.manifest.manifestProject.config
1710 for key in ['user.name', 'user.email']:
1711 if m.Has(key, include_defaults = False):
1712 self.config.SetString(key, m.GetString(key))
1713
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001714 def _InitHooks(self):
1715 hooks = self._gitdir_path('hooks')
1716 if not os.path.exists(hooks):
1717 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001718 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001719 name = os.path.basename(stock_hook)
1720
Victor Boivie65e0f352011-04-18 11:23:29 +02001721 if name in ('commit-msg',) and not self.remote.review \
1722 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001723 # Don't install a Gerrit Code Review hook if this
1724 # project does not appear to use it for reviews.
1725 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001726 # Since the manifest project is one of those, but also
1727 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001728 continue
1729
1730 dst = os.path.join(hooks, name)
1731 if os.path.islink(dst):
1732 continue
1733 if os.path.exists(dst):
1734 if filecmp.cmp(stock_hook, dst, shallow=False):
1735 os.remove(dst)
1736 else:
1737 _error("%s: Not replacing %s hook", self.relpath, name)
1738 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001739 try:
1740 os.symlink(relpath(stock_hook, dst), dst)
1741 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001742 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001743 raise GitError('filesystem must support symlinks')
1744 else:
1745 raise
1746
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001747 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001748 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001749 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001750 remote.url = self.remote.url
1751 remote.review = self.remote.review
1752 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001753
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001754 if self.worktree:
1755 remote.ResetFetch(mirror=False)
1756 else:
1757 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758 remote.Save()
1759
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001760 def _InitMRef(self):
1761 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001762 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001763
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001764 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001765 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001766
1767 def _InitAnyMRef(self, ref):
1768 cur = self.bare_ref.symref(ref)
1769
1770 if self.revisionId:
1771 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1772 msg = 'manifest set to %s' % self.revisionId
1773 dst = self.revisionId + '^0'
1774 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1775 else:
1776 remote = self.GetRemote(self.remote.name)
1777 dst = remote.ToLocal(self.revisionExpr)
1778 if cur != dst:
1779 msg = 'manifest set to %s' % self.revisionExpr
1780 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001781
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001782 def _InitWorkTree(self):
1783 dotgit = os.path.join(self.worktree, '.git')
1784 if not os.path.exists(dotgit):
1785 os.makedirs(dotgit)
1786
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001787 for name in ['config',
1788 'description',
1789 'hooks',
1790 'info',
1791 'logs',
1792 'objects',
1793 'packed-refs',
1794 'refs',
1795 'rr-cache',
1796 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001797 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001798 src = os.path.join(self.gitdir, name)
1799 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001800 if os.path.islink(dst) or not os.path.exists(dst):
1801 os.symlink(relpath(src, dst), dst)
1802 else:
1803 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001804 except OSError, e:
1805 if e.errno == errno.EPERM:
1806 raise GitError('filesystem must support symlinks')
1807 else:
1808 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001809
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001810 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001811
1812 cmd = ['read-tree', '--reset', '-u']
1813 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001814 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001815 if GitCommand(self, cmd).Wait() != 0:
1816 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001817
1818 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1819 if not os.path.exists(rr_cache):
1820 os.makedirs(rr_cache)
1821
Shawn O. Pearce93609662009-04-21 10:50:33 -07001822 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001823
1824 def _gitdir_path(self, path):
1825 return os.path.join(self.gitdir, path)
1826
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001827 def _revlist(self, *args, **kw):
1828 a = []
1829 a.extend(args)
1830 a.append('--')
1831 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001832
1833 @property
1834 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001835 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001836
1837 class _GitGetByExec(object):
1838 def __init__(self, project, bare):
1839 self._project = project
1840 self._bare = bare
1841
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001842 def LsOthers(self):
1843 p = GitCommand(self._project,
1844 ['ls-files',
1845 '-z',
1846 '--others',
1847 '--exclude-standard'],
1848 bare = False,
1849 capture_stdout = True,
1850 capture_stderr = True)
1851 if p.Wait() == 0:
1852 out = p.stdout
1853 if out:
1854 return out[:-1].split("\0")
1855 return []
1856
1857 def DiffZ(self, name, *args):
1858 cmd = [name]
1859 cmd.append('-z')
1860 cmd.extend(args)
1861 p = GitCommand(self._project,
1862 cmd,
1863 bare = False,
1864 capture_stdout = True,
1865 capture_stderr = True)
1866 try:
1867 out = p.process.stdout.read()
1868 r = {}
1869 if out:
1870 out = iter(out[:-1].split('\0'))
1871 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001872 try:
1873 info = out.next()
1874 path = out.next()
1875 except StopIteration:
1876 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001877
1878 class _Info(object):
1879 def __init__(self, path, omode, nmode, oid, nid, state):
1880 self.path = path
1881 self.src_path = None
1882 self.old_mode = omode
1883 self.new_mode = nmode
1884 self.old_id = oid
1885 self.new_id = nid
1886
1887 if len(state) == 1:
1888 self.status = state
1889 self.level = None
1890 else:
1891 self.status = state[:1]
1892 self.level = state[1:]
1893 while self.level.startswith('0'):
1894 self.level = self.level[1:]
1895
1896 info = info[1:].split(' ')
1897 info =_Info(path, *info)
1898 if info.status in ('R', 'C'):
1899 info.src_path = info.path
1900 info.path = out.next()
1901 r[info.path] = info
1902 return r
1903 finally:
1904 p.Wait()
1905
1906 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001907 if self._bare:
1908 path = os.path.join(self._project.gitdir, HEAD)
1909 else:
1910 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001911 fd = open(path, 'rb')
1912 try:
1913 line = fd.read()
1914 finally:
1915 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001916 if line.startswith('ref: '):
1917 return line[5:-1]
1918 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001919
1920 def SetHead(self, ref, message=None):
1921 cmdv = []
1922 if message is not None:
1923 cmdv.extend(['-m', message])
1924 cmdv.append(HEAD)
1925 cmdv.append(ref)
1926 self.symbolic_ref(*cmdv)
1927
1928 def DetachHead(self, new, message=None):
1929 cmdv = ['--no-deref']
1930 if message is not None:
1931 cmdv.extend(['-m', message])
1932 cmdv.append(HEAD)
1933 cmdv.append(new)
1934 self.update_ref(*cmdv)
1935
1936 def UpdateRef(self, name, new, old=None,
1937 message=None,
1938 detach=False):
1939 cmdv = []
1940 if message is not None:
1941 cmdv.extend(['-m', message])
1942 if detach:
1943 cmdv.append('--no-deref')
1944 cmdv.append(name)
1945 cmdv.append(new)
1946 if old is not None:
1947 cmdv.append(old)
1948 self.update_ref(*cmdv)
1949
1950 def DeleteRef(self, name, old=None):
1951 if not old:
1952 old = self.rev_parse(name)
1953 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001954 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001955
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001956 def rev_list(self, *args, **kw):
1957 if 'format' in kw:
1958 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1959 else:
1960 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001961 cmdv.extend(args)
1962 p = GitCommand(self._project,
1963 cmdv,
1964 bare = self._bare,
1965 capture_stdout = True,
1966 capture_stderr = True)
1967 r = []
1968 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001969 if line[-1] == '\n':
1970 line = line[:-1]
1971 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001972 if p.Wait() != 0:
1973 raise GitError('%s rev-list %s: %s' % (
1974 self._project.name,
1975 str(args),
1976 p.stderr))
1977 return r
1978
1979 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001980 """Allow arbitrary git commands using pythonic syntax.
1981
1982 This allows you to do things like:
1983 git_obj.rev_parse('HEAD')
1984
1985 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1986 run. We'll replace the '_' with a '-' and try to run a git command.
1987 Any other arguments will be passed to the git command.
1988
1989 Args:
1990 name: The name of the git command to call. Any '_' characters will
1991 be replaced with '-'.
1992
1993 Returns:
1994 A callable object that will try to call git with the named command.
1995 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001996 name = name.replace('_', '-')
1997 def runner(*args):
1998 cmdv = [name]
1999 cmdv.extend(args)
2000 p = GitCommand(self._project,
2001 cmdv,
2002 bare = self._bare,
2003 capture_stdout = True,
2004 capture_stderr = True)
2005 if p.Wait() != 0:
2006 raise GitError('%s %s: %s' % (
2007 self._project.name,
2008 name,
2009 p.stderr))
2010 r = p.stdout
2011 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2012 return r[:-1]
2013 return r
2014 return runner
2015
2016
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002017class _PriorSyncFailedError(Exception):
2018 def __str__(self):
2019 return 'prior sync failed; rebase still in progress'
2020
2021class _DirtyError(Exception):
2022 def __str__(self):
2023 return 'contains uncommitted changes'
2024
2025class _InfoMessage(object):
2026 def __init__(self, project, text):
2027 self.project = project
2028 self.text = text
2029
2030 def Print(self, syncbuf):
2031 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2032 syncbuf.out.nl()
2033
2034class _Failure(object):
2035 def __init__(self, project, why):
2036 self.project = project
2037 self.why = why
2038
2039 def Print(self, syncbuf):
2040 syncbuf.out.fail('error: %s/: %s',
2041 self.project.relpath,
2042 str(self.why))
2043 syncbuf.out.nl()
2044
2045class _Later(object):
2046 def __init__(self, project, action):
2047 self.project = project
2048 self.action = action
2049
2050 def Run(self, syncbuf):
2051 out = syncbuf.out
2052 out.project('project %s/', self.project.relpath)
2053 out.nl()
2054 try:
2055 self.action()
2056 out.nl()
2057 return True
2058 except GitError, e:
2059 out.nl()
2060 return False
2061
2062class _SyncColoring(Coloring):
2063 def __init__(self, config):
2064 Coloring.__init__(self, config, 'reposync')
2065 self.project = self.printer('header', attr = 'bold')
2066 self.info = self.printer('info')
2067 self.fail = self.printer('fail', fg='red')
2068
2069class SyncBuffer(object):
2070 def __init__(self, config, detach_head=False):
2071 self._messages = []
2072 self._failures = []
2073 self._later_queue1 = []
2074 self._later_queue2 = []
2075
2076 self.out = _SyncColoring(config)
2077 self.out.redirect(sys.stderr)
2078
2079 self.detach_head = detach_head
2080 self.clean = True
2081
2082 def info(self, project, fmt, *args):
2083 self._messages.append(_InfoMessage(project, fmt % args))
2084
2085 def fail(self, project, err=None):
2086 self._failures.append(_Failure(project, err))
2087 self.clean = False
2088
2089 def later1(self, project, what):
2090 self._later_queue1.append(_Later(project, what))
2091
2092 def later2(self, project, what):
2093 self._later_queue2.append(_Later(project, what))
2094
2095 def Finish(self):
2096 self._PrintMessages()
2097 self._RunLater()
2098 self._PrintMessages()
2099 return self.clean
2100
2101 def _RunLater(self):
2102 for q in ['_later_queue1', '_later_queue2']:
2103 if not self._RunQueue(q):
2104 return
2105
2106 def _RunQueue(self, queue):
2107 for m in getattr(self, queue):
2108 if not m.Run(self):
2109 self.clean = False
2110 return False
2111 setattr(self, queue, [])
2112 return True
2113
2114 def _PrintMessages(self):
2115 for m in self._messages:
2116 m.Print(self)
2117 for m in self._failures:
2118 m.Print(self)
2119
2120 self._messages = []
2121 self._failures = []
2122
2123
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002124class MetaProject(Project):
2125 """A special project housed under .repo.
2126 """
2127 def __init__(self, manifest, name, gitdir, worktree):
2128 repodir = manifest.repodir
2129 Project.__init__(self,
2130 manifest = manifest,
2131 name = name,
2132 gitdir = gitdir,
2133 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002134 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002135 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002136 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002137 revisionId = None,
2138 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002139
2140 def PreSync(self):
2141 if self.Exists:
2142 cb = self.CurrentBranch
2143 if cb:
2144 base = self.GetBranch(cb).merge
2145 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002146 self.revisionExpr = base
2147 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002148
2149 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002150 def LastFetch(self):
2151 try:
2152 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2153 return os.path.getmtime(fh)
2154 except OSError:
2155 return 0
2156
2157 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002158 def HasChanges(self):
2159 """Has the remote received new commits not yet checked out?
2160 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002161 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002162 return False
2163
2164 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002165 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002166 head = self.work_git.GetHead()
2167 if head.startswith(R_HEADS):
2168 try:
2169 head = all[head]
2170 except KeyError:
2171 head = None
2172
2173 if revid == head:
2174 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002175 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002176 return True
2177 return False