blob: b7295790397fff6547140760a66dd77fe1a9d5d7 [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
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070023import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import sys
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070025import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070026
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070027from color import Coloring
28from git_command import GitCommand
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070029from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070030from error import DownloadError
Doug Anderson37282b42011-03-04 11:54:18 -080031from error import GitError, HookError, ImportError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080032from error import ManifestInvalidRevisionError
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -070033from progress import Progress
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070034from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070035
Shawn O. Pearced237b692009-04-17 18:49:50 -070036from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070038def _lwrite(path, content):
39 lock = '%s.lock' % path
40
41 fd = open(lock, 'wb')
42 try:
43 fd.write(content)
44 finally:
45 fd.close()
46
47 try:
48 os.rename(lock, path)
49 except OSError:
50 os.remove(lock)
51 raise
52
Shawn O. Pearce48244782009-04-16 08:25:57 -070053def _error(fmt, *args):
54 msg = fmt % args
55 print >>sys.stderr, 'error: %s' % msg
56
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070057def not_rev(r):
58 return '^' + r
59
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080060def sq(r):
61 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080062
Doug Anderson8ced8642011-01-10 14:16:30 -080063_project_hook_list = None
64def _ProjectHooks():
65 """List the hooks present in the 'hooks' directory.
66
67 These hooks are project hooks and are copied to the '.git/hooks' directory
68 of all subprojects.
69
70 This function caches the list of hooks (based on the contents of the
71 'repo/hooks' directory) on the first call.
72
73 Returns:
74 A list of absolute paths to all of the files in the hooks directory.
75 """
76 global _project_hook_list
77 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080078 d = os.path.abspath(os.path.dirname(__file__))
79 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080080 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
81 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080082
83def relpath(dst, src):
84 src = os.path.dirname(src)
85 top = os.path.commonprefix([dst, src])
86 if top.endswith('/'):
87 top = top[:-1]
88 else:
89 top = os.path.dirname(top)
90
91 tmp = src
92 rel = ''
93 while top != tmp:
94 rel += '../'
95 tmp = os.path.dirname(tmp)
96 return rel + dst[len(top) + 1:]
97
98
Shawn O. Pearce632768b2008-10-23 11:58:52 -070099class DownloadedChange(object):
100 _commit_cache = None
101
102 def __init__(self, project, base, change_id, ps_id, commit):
103 self.project = project
104 self.base = base
105 self.change_id = change_id
106 self.ps_id = ps_id
107 self.commit = commit
108
109 @property
110 def commits(self):
111 if self._commit_cache is None:
112 self._commit_cache = self.project.bare_git.rev_list(
113 '--abbrev=8',
114 '--abbrev-commit',
115 '--pretty=oneline',
116 '--reverse',
117 '--date-order',
118 not_rev(self.base),
119 self.commit,
120 '--')
121 return self._commit_cache
122
123
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700124class ReviewableBranch(object):
125 _commit_cache = None
126
127 def __init__(self, project, branch, base):
128 self.project = project
129 self.branch = branch
130 self.base = base
131
132 @property
133 def name(self):
134 return self.branch.name
135
136 @property
137 def commits(self):
138 if self._commit_cache is None:
139 self._commit_cache = self.project.bare_git.rev_list(
140 '--abbrev=8',
141 '--abbrev-commit',
142 '--pretty=oneline',
143 '--reverse',
144 '--date-order',
145 not_rev(self.base),
146 R_HEADS + self.name,
147 '--')
148 return self._commit_cache
149
150 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800151 def unabbrev_commits(self):
152 r = dict()
153 for commit in self.project.bare_git.rev_list(
154 not_rev(self.base),
155 R_HEADS + self.name,
156 '--'):
157 r[commit[0:8]] = commit
158 return r
159
160 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700161 def date(self):
162 return self.project.bare_git.log(
163 '--pretty=format:%cd',
164 '-n', '1',
165 R_HEADS + self.name,
166 '--')
167
Brian Harring435370c2012-07-28 15:37:04 -0700168 def UploadForReview(self, people, auto_topic=False, draft=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800169 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700170 people,
Brian Harring435370c2012-07-28 15:37:04 -0700171 auto_topic=auto_topic,
172 draft=draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700173
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700174 def GetPublishedRefs(self):
175 refs = {}
176 output = self.project.bare_git.ls_remote(
177 self.branch.remote.SshReviewUrl(self.project.UserEmail),
178 'refs/changes/*')
179 for line in output.split('\n'):
180 try:
181 (sha, ref) = line.split()
182 refs[sha] = ref
183 except ValueError:
184 pass
185
186 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700187
188class StatusColoring(Coloring):
189 def __init__(self, config):
190 Coloring.__init__(self, config, 'status')
191 self.project = self.printer('header', attr = 'bold')
192 self.branch = self.printer('header', attr = 'bold')
193 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700194 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195
196 self.added = self.printer('added', fg = 'green')
197 self.changed = self.printer('changed', fg = 'red')
198 self.untracked = self.printer('untracked', fg = 'red')
199
200
201class DiffColoring(Coloring):
202 def __init__(self, config):
203 Coloring.__init__(self, config, 'diff')
204 self.project = self.printer('header', attr = 'bold')
205
James W. Mills24c13082012-04-12 15:04:13 -0500206class _Annotation:
207 def __init__(self, name, value, keep):
208 self.name = name
209 self.value = value
210 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211
212class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800213 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214 self.src = src
215 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800216 self.abs_src = abssrc
217 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218
219 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800220 src = self.abs_src
221 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222 # copy file if it does not exist or is out of date
223 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
224 try:
225 # remove existing file first, since it might be read-only
226 if os.path.exists(dest):
227 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400228 else:
229 dir = os.path.dirname(dest)
230 if not os.path.isdir(dir):
231 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232 shutil.copy(src, dest)
233 # make the file read-only
234 mode = os.stat(dest)[stat.ST_MODE]
235 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
236 os.chmod(dest, mode)
237 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700238 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700240class RemoteSpec(object):
241 def __init__(self,
242 name,
243 url = None,
244 review = None):
245 self.name = name
246 self.url = url
247 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248
Doug Anderson37282b42011-03-04 11:54:18 -0800249class RepoHook(object):
250 """A RepoHook contains information about a script to run as a hook.
251
252 Hooks are used to run a python script before running an upload (for instance,
253 to run presubmit checks). Eventually, we may have hooks for other actions.
254
255 This shouldn't be confused with files in the 'repo/hooks' directory. Those
256 files are copied into each '.git/hooks' folder for each project. Repo-level
257 hooks are associated instead with repo actions.
258
259 Hooks are always python. When a hook is run, we will load the hook into the
260 interpreter and execute its main() function.
261 """
262 def __init__(self,
263 hook_type,
264 hooks_project,
265 topdir,
266 abort_if_user_denies=False):
267 """RepoHook constructor.
268
269 Params:
270 hook_type: A string representing the type of hook. This is also used
271 to figure out the name of the file containing the hook. For
272 example: 'pre-upload'.
273 hooks_project: The project containing the repo hooks. If you have a
274 manifest, this is manifest.repo_hooks_project. OK if this is None,
275 which will make the hook a no-op.
276 topdir: Repo's top directory (the one containing the .repo directory).
277 Scripts will run with CWD as this directory. If you have a manifest,
278 this is manifest.topdir
279 abort_if_user_denies: If True, we'll throw a HookError() if the user
280 doesn't allow us to run the hook.
281 """
282 self._hook_type = hook_type
283 self._hooks_project = hooks_project
284 self._topdir = topdir
285 self._abort_if_user_denies = abort_if_user_denies
286
287 # Store the full path to the script for convenience.
288 if self._hooks_project:
289 self._script_fullpath = os.path.join(self._hooks_project.worktree,
290 self._hook_type + '.py')
291 else:
292 self._script_fullpath = None
293
294 def _GetHash(self):
295 """Return a hash of the contents of the hooks directory.
296
297 We'll just use git to do this. This hash has the property that if anything
298 changes in the directory we will return a different has.
299
300 SECURITY CONSIDERATION:
301 This hash only represents the contents of files in the hook directory, not
302 any other files imported or called by hooks. Changes to imported files
303 can change the script behavior without affecting the hash.
304
305 Returns:
306 A string representing the hash. This will always be ASCII so that it can
307 be printed to the user easily.
308 """
309 assert self._hooks_project, "Must have hooks to calculate their hash."
310
311 # We will use the work_git object rather than just calling GetRevisionId().
312 # That gives us a hash of the latest checked in version of the files that
313 # the user will actually be executing. Specifically, GetRevisionId()
314 # doesn't appear to change even if a user checks out a different version
315 # of the hooks repo (via git checkout) nor if a user commits their own revs.
316 #
317 # NOTE: Local (non-committed) changes will not be factored into this hash.
318 # I think this is OK, since we're really only worried about warning the user
319 # about upstream changes.
320 return self._hooks_project.work_git.rev_parse('HEAD')
321
322 def _GetMustVerb(self):
323 """Return 'must' if the hook is required; 'should' if not."""
324 if self._abort_if_user_denies:
325 return 'must'
326 else:
327 return 'should'
328
329 def _CheckForHookApproval(self):
330 """Check to see whether this hook has been approved.
331
332 We'll look at the hash of all of the hooks. If this matches the hash that
333 the user last approved, we're done. If it doesn't, we'll ask the user
334 about approval.
335
336 Note that we ask permission for each individual hook even though we use
337 the hash of all hooks when detecting changes. We'd like the user to be
338 able to approve / deny each hook individually. We only use the hash of all
339 hooks because there is no other easy way to detect changes to local imports.
340
341 Returns:
342 True if this hook is approved to run; False otherwise.
343
344 Raises:
345 HookError: Raised if the user doesn't approve and abort_if_user_denies
346 was passed to the consturctor.
347 """
348 hooks_dir = self._hooks_project.worktree
349 hooks_config = self._hooks_project.config
350 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
351
352 # Get the last hash that the user approved for this hook; may be None.
353 old_hash = hooks_config.GetString(git_approval_key)
354
355 # Get the current hash so we can tell if scripts changed since approval.
356 new_hash = self._GetHash()
357
358 if old_hash is not None:
359 # User previously approved hook and asked not to be prompted again.
360 if new_hash == old_hash:
361 # Approval matched. We're done.
362 return True
363 else:
364 # Give the user a reason why we're prompting, since they last told
365 # us to "never ask again".
366 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
367 self._hook_type)
368 else:
369 prompt = ''
370
371 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
372 if sys.stdout.isatty():
373 prompt += ('Repo %s run the script:\n'
374 ' %s\n'
375 '\n'
376 'Do you want to allow this script to run '
377 '(yes/yes-never-ask-again/NO)? ') % (
378 self._GetMustVerb(), self._script_fullpath)
379 response = raw_input(prompt).lower()
380 print
381
382 # User is doing a one-time approval.
383 if response in ('y', 'yes'):
384 return True
385 elif response == 'yes-never-ask-again':
386 hooks_config.SetString(git_approval_key, new_hash)
387 return True
388
389 # For anything else, we'll assume no approval.
390 if self._abort_if_user_denies:
391 raise HookError('You must allow the %s hook or use --no-verify.' %
392 self._hook_type)
393
394 return False
395
396 def _ExecuteHook(self, **kwargs):
397 """Actually execute the given hook.
398
399 This will run the hook's 'main' function in our python interpreter.
400
401 Args:
402 kwargs: Keyword arguments to pass to the hook. These are often specific
403 to the hook type. For instance, pre-upload hooks will contain
404 a project_list.
405 """
406 # Keep sys.path and CWD stashed away so that we can always restore them
407 # upon function exit.
408 orig_path = os.getcwd()
409 orig_syspath = sys.path
410
411 try:
412 # Always run hooks with CWD as topdir.
413 os.chdir(self._topdir)
414
415 # Put the hook dir as the first item of sys.path so hooks can do
416 # relative imports. We want to replace the repo dir as [0] so
417 # hooks can't import repo files.
418 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
419
420 # Exec, storing global context in the context dict. We catch exceptions
421 # and convert to a HookError w/ just the failing traceback.
422 context = {}
423 try:
424 execfile(self._script_fullpath, context)
425 except Exception:
426 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
427 traceback.format_exc(), self._hook_type))
428
429 # Running the script should have defined a main() function.
430 if 'main' not in context:
431 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
432
433
434 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
435 # We don't actually want hooks to define their main with this argument--
436 # it's there to remind them that their hook should always take **kwargs.
437 # For instance, a pre-upload hook should be defined like:
438 # def main(project_list, **kwargs):
439 #
440 # This allows us to later expand the API without breaking old hooks.
441 kwargs = kwargs.copy()
442 kwargs['hook_should_take_kwargs'] = True
443
444 # Call the main function in the hook. If the hook should cause the
445 # build to fail, it will raise an Exception. We'll catch that convert
446 # to a HookError w/ just the failing traceback.
447 try:
448 context['main'](**kwargs)
449 except Exception:
450 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
451 'above.' % (
452 traceback.format_exc(), self._hook_type))
453 finally:
454 # Restore sys.path and CWD.
455 sys.path = orig_syspath
456 os.chdir(orig_path)
457
458 def Run(self, user_allows_all_hooks, **kwargs):
459 """Run the hook.
460
461 If the hook doesn't exist (because there is no hooks project or because
462 this particular hook is not enabled), this is a no-op.
463
464 Args:
465 user_allows_all_hooks: If True, we will never prompt about running the
466 hook--we'll just assume it's OK to run it.
467 kwargs: Keyword arguments to pass to the hook. These are often specific
468 to the hook type. For instance, pre-upload hooks will contain
469 a project_list.
470
471 Raises:
472 HookError: If there was a problem finding the hook or the user declined
473 to run a required hook (from _CheckForHookApproval).
474 """
475 # No-op if there is no hooks project or if hook is disabled.
476 if ((not self._hooks_project) or
477 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
478 return
479
480 # Bail with a nice error if we can't find the hook.
481 if not os.path.isfile(self._script_fullpath):
482 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
483
484 # Make sure the user is OK with running the hook.
485 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
486 return
487
488 # Run the hook with the same version of python we're using.
489 self._ExecuteHook(**kwargs)
490
491
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700492class Project(object):
493 def __init__(self,
494 manifest,
495 name,
496 remote,
497 gitdir,
498 worktree,
499 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700500 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800501 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700502 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700503 groups = None,
504 sync_c = False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700505 self.manifest = manifest
506 self.name = name
507 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800508 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800509 if worktree:
510 self.worktree = worktree.replace('\\', '/')
511 else:
512 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700513 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700514 self.revisionExpr = revisionExpr
515
516 if revisionId is None \
517 and revisionExpr \
518 and IsId(revisionExpr):
519 self.revisionId = revisionExpr
520 else:
521 self.revisionId = revisionId
522
Mike Pontillod3153822012-02-28 11:53:24 -0800523 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700524 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700525 self.sync_c = sync_c
Mike Pontillod3153822012-02-28 11:53:24 -0800526
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700527 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700528 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500529 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700530 self.config = GitConfig.ForRepository(
531 gitdir = self.gitdir,
532 defaults = self.manifest.globalConfig)
533
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800534 if self.worktree:
535 self.work_git = self._GitGetByExec(self, bare=False)
536 else:
537 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700538 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700539 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540
Doug Anderson37282b42011-03-04 11:54:18 -0800541 # This will be filled in if a project is later identified to be the
542 # project containing repo hooks.
543 self.enabled_repo_hooks = []
544
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700545 @property
546 def Exists(self):
547 return os.path.isdir(self.gitdir)
548
549 @property
550 def CurrentBranch(self):
551 """Obtain the name of the currently checked out branch.
552 The branch name omits the 'refs/heads/' prefix.
553 None is returned if the project is on a detached HEAD.
554 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700555 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700556 if b.startswith(R_HEADS):
557 return b[len(R_HEADS):]
558 return None
559
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700560 def IsRebaseInProgress(self):
561 w = self.worktree
562 g = os.path.join(w, '.git')
563 return os.path.exists(os.path.join(g, 'rebase-apply')) \
564 or os.path.exists(os.path.join(g, 'rebase-merge')) \
565 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200566
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567 def IsDirty(self, consider_untracked=True):
568 """Is the working directory modified in some way?
569 """
570 self.work_git.update_index('-q',
571 '--unmerged',
572 '--ignore-missing',
573 '--refresh')
574 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
575 return True
576 if self.work_git.DiffZ('diff-files'):
577 return True
578 if consider_untracked and self.work_git.LsOthers():
579 return True
580 return False
581
582 _userident_name = None
583 _userident_email = None
584
585 @property
586 def UserName(self):
587 """Obtain the user's personal name.
588 """
589 if self._userident_name is None:
590 self._LoadUserIdentity()
591 return self._userident_name
592
593 @property
594 def UserEmail(self):
595 """Obtain the user's email address. This is very likely
596 to be their Gerrit login.
597 """
598 if self._userident_email is None:
599 self._LoadUserIdentity()
600 return self._userident_email
601
602 def _LoadUserIdentity(self):
603 u = self.bare_git.var('GIT_COMMITTER_IDENT')
604 m = re.compile("^(.*) <([^>]*)> ").match(u)
605 if m:
606 self._userident_name = m.group(1)
607 self._userident_email = m.group(2)
608 else:
609 self._userident_name = ''
610 self._userident_email = ''
611
612 def GetRemote(self, name):
613 """Get the configuration for a single remote.
614 """
615 return self.config.GetRemote(name)
616
617 def GetBranch(self, name):
618 """Get the configuration for a single branch.
619 """
620 return self.config.GetBranch(name)
621
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700622 def GetBranches(self):
623 """Get all existing local branches.
624 """
625 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700626 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700627 heads = {}
628 pubd = {}
629
630 for name, id in all.iteritems():
631 if name.startswith(R_HEADS):
632 name = name[len(R_HEADS):]
633 b = self.GetBranch(name)
634 b.current = name == current
635 b.published = None
636 b.revision = id
637 heads[name] = b
638
639 for name, id in all.iteritems():
640 if name.startswith(R_PUB):
641 name = name[len(R_PUB):]
642 b = heads.get(name)
643 if b:
644 b.published = id
645
646 return heads
647
Colin Cross5acde752012-03-28 20:15:45 -0700648 def MatchesGroups(self, manifest_groups):
649 """Returns true if the manifest groups specified at init should cause
650 this project to be synced.
651 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owens971de8e2012-04-16 10:36:08 -0700652 All projects are implicitly labelled with "default".
653
654 labels are resolved in order. In the example case of
655 project_groups: "default,group1,group2"
656 manifest_groups: "-group1,group2"
657 the project will be matched.
Colin Cross5acde752012-03-28 20:15:45 -0700658 """
Colin Crosseca119e2012-05-24 15:39:14 -0700659 if self.groups is None:
660 return True
Conley Owens971de8e2012-04-16 10:36:08 -0700661 matched = False
662 for group in manifest_groups:
663 if group.startswith('-') and group[1:] in self.groups:
664 matched = False
665 elif group in self.groups:
666 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700667
Conley Owens971de8e2012-04-16 10:36:08 -0700668 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669
670## Status Display ##
671
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500672 def HasChanges(self):
673 """Returns true if there are uncommitted changes.
674 """
675 self.work_git.update_index('-q',
676 '--unmerged',
677 '--ignore-missing',
678 '--refresh')
679 if self.IsRebaseInProgress():
680 return True
681
682 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
683 return True
684
685 if self.work_git.DiffZ('diff-files'):
686 return True
687
688 if self.work_git.LsOthers():
689 return True
690
691 return False
692
Terence Haddock4655e812011-03-31 12:33:34 +0200693 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700694 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200695
696 Args:
697 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700698 """
699 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200700 if output_redir == None:
701 output_redir = sys.stdout
702 print >>output_redir, ''
703 print >>output_redir, 'project %s/' % self.relpath
704 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700705 return
706
707 self.work_git.update_index('-q',
708 '--unmerged',
709 '--ignore-missing',
710 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700711 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
713 df = self.work_git.DiffZ('diff-files')
714 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100715 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700716 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717
718 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200719 if not output_redir == None:
720 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700721 out.project('project %-40s', self.relpath + '/')
722
723 branch = self.CurrentBranch
724 if branch is None:
725 out.nobranch('(*** NO BRANCH ***)')
726 else:
727 out.branch('branch %s', branch)
728 out.nl()
729
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700730 if rb:
731 out.important('prior sync failed; rebase still in progress')
732 out.nl()
733
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700734 paths = list()
735 paths.extend(di.keys())
736 paths.extend(df.keys())
737 paths.extend(do)
738
739 paths = list(set(paths))
740 paths.sort()
741
742 for p in paths:
743 try: i = di[p]
744 except KeyError: i = None
745
746 try: f = df[p]
747 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200748
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700749 if i: i_status = i.status.upper()
750 else: i_status = '-'
751
752 if f: f_status = f.status.lower()
753 else: f_status = '-'
754
755 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800756 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 i.src_path, p, i.level)
758 else:
759 line = ' %s%s\t%s' % (i_status, f_status, p)
760
761 if i and not f:
762 out.added('%s', line)
763 elif (i and f) or (not i and f):
764 out.changed('%s', line)
765 elif not i and not f:
766 out.untracked('%s', line)
767 else:
768 out.write('%s', line)
769 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200770
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700771 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700772
pelyad67872d2012-03-28 14:49:58 +0300773 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700774 """Prints the status of the repository to stdout.
775 """
776 out = DiffColoring(self.config)
777 cmd = ['diff']
778 if out.is_on:
779 cmd.append('--color')
780 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300781 if absolute_paths:
782 cmd.append('--src-prefix=a/%s/' % self.relpath)
783 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784 cmd.append('--')
785 p = GitCommand(self,
786 cmd,
787 capture_stdout = True,
788 capture_stderr = True)
789 has_diff = False
790 for line in p.process.stdout:
791 if not has_diff:
792 out.nl()
793 out.project('project %s/' % self.relpath)
794 out.nl()
795 has_diff = True
796 print line[:-1]
797 p.Wait()
798
799
800## Publish / Upload ##
801
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700802 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803 """Was the branch published (uploaded) for code review?
804 If so, returns the SHA-1 hash of the last published
805 state for the branch.
806 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700807 key = R_PUB + branch
808 if all is None:
809 try:
810 return self.bare_git.rev_parse(key)
811 except GitError:
812 return None
813 else:
814 try:
815 return all[key]
816 except KeyError:
817 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700818
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700819 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700820 """Prunes any stale published refs.
821 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700822 if all is None:
823 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824 heads = set()
825 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700826 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700827 if name.startswith(R_HEADS):
828 heads.add(name)
829 elif name.startswith(R_PUB):
830 canrm[name] = id
831
832 for name, id in canrm.iteritems():
833 n = name[len(R_PUB):]
834 if R_HEADS + n not in heads:
835 self.bare_git.DeleteRef(name, id)
836
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700837 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 """List any branches which can be uploaded for review.
839 """
840 heads = {}
841 pubed = {}
842
843 for name, id in self._allrefs.iteritems():
844 if name.startswith(R_HEADS):
845 heads[name[len(R_HEADS):]] = id
846 elif name.startswith(R_PUB):
847 pubed[name[len(R_PUB):]] = id
848
849 ready = []
850 for branch, id in heads.iteritems():
851 if branch in pubed and pubed[branch] == id:
852 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700853 if selected_branch and branch != selected_branch:
854 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700855
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800856 rb = self.GetUploadableBranch(branch)
857 if rb:
858 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700859 return ready
860
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800861 def GetUploadableBranch(self, branch_name):
862 """Get a single uploadable branch, or None.
863 """
864 branch = self.GetBranch(branch_name)
865 base = branch.LocalMerge
866 if branch.LocalMerge:
867 rb = ReviewableBranch(self, branch, base)
868 if rb.commits:
869 return rb
870 return None
871
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700872 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700873 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700874 auto_topic=False,
875 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700876 """Uploads the named branch for code review.
877 """
878 if branch is None:
879 branch = self.CurrentBranch
880 if branch is None:
881 raise GitError('not currently on a branch')
882
883 branch = self.GetBranch(branch)
884 if not branch.LocalMerge:
885 raise GitError('branch %s does not track a remote' % branch.name)
886 if not branch.remote.review:
887 raise GitError('remote %s has no review url' % branch.remote.name)
888
889 dest_branch = branch.merge
890 if not dest_branch.startswith(R_HEADS):
891 dest_branch = R_HEADS + dest_branch
892
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800893 if not branch.remote.projectname:
894 branch.remote.projectname = self.name
895 branch.remote.Save()
896
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800897 url = branch.remote.ReviewUrl(self.UserEmail)
898 if url is None:
899 raise UploadError('review not configured')
900 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800901
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800902 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800903 rp = ['gerrit receive-pack']
904 for e in people[0]:
905 rp.append('--reviewer=%s' % sq(e))
906 for e in people[1]:
907 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800908 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700909
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800910 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800911
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800912 if dest_branch.startswith(R_HEADS):
913 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700914
915 upload_type = 'for'
916 if draft:
917 upload_type = 'drafts'
918
919 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
920 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800921 if auto_topic:
922 ref_spec = ref_spec + '/' + branch.name
923 cmd.append(ref_spec)
924
925 if GitCommand(self, cmd, bare = True).Wait() != 0:
926 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927
928 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
929 self.bare_git.UpdateRef(R_PUB + branch.name,
930 R_HEADS + branch.name,
931 message = msg)
932
933
934## Sync ##
935
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700936 def Sync_NetworkHalf(self,
937 quiet=False,
938 is_new=None,
939 current_branch_only=False,
940 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941 """Perform only the network IO portion of the sync process.
942 Local working directory/branch state is not affected.
943 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700944 if is_new is None:
945 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200946 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947 self._InitGitDir()
948 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700949
950 if is_new:
951 alt = os.path.join(self.gitdir, 'objects/info/alternates')
952 try:
953 fd = open(alt, 'rb')
954 try:
955 alt_dir = fd.readline().rstrip()
956 finally:
957 fd.close()
958 except IOError:
959 alt_dir = None
960 else:
961 alt_dir = None
962
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700963 if clone_bundle \
964 and alt_dir is None \
965 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700966 is_new = False
967
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700968 if not current_branch_only:
969 if self.sync_c:
970 current_branch_only = True
971 elif not self.manifest._loaded:
972 # Manifest cannot check defaults until it syncs.
973 current_branch_only = False
974 elif self.manifest.default.sync_c:
975 current_branch_only = True
976
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700977 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
978 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800980
981 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800982 self._InitMRef()
983 else:
984 self._InitMirrorHead()
985 try:
986 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
987 except OSError:
988 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800990
991 def PostRepoUpgrade(self):
992 self._InitHooks()
993
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700994 def _CopyFiles(self):
995 for file in self.copyfiles:
996 file._Copy()
997
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700998 def GetRevisionId(self, all=None):
999 if self.revisionId:
1000 return self.revisionId
1001
1002 rem = self.GetRemote(self.remote.name)
1003 rev = rem.ToLocal(self.revisionExpr)
1004
1005 if all is not None and rev in all:
1006 return all[rev]
1007
1008 try:
1009 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1010 except GitError:
1011 raise ManifestInvalidRevisionError(
1012 'revision %s in %s not found' % (self.revisionExpr,
1013 self.name))
1014
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001015 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016 """Perform only the local IO portion of the sync process.
1017 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001018 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001019 all = self.bare_ref.all
1020 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001021 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001022
1023 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001024 head = self.work_git.GetHead()
1025 if head.startswith(R_HEADS):
1026 branch = head[len(R_HEADS):]
1027 try:
1028 head = all[head]
1029 except KeyError:
1030 head = None
1031 else:
1032 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001033
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001034 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001035 # Currently on a detached HEAD. The user is assumed to
1036 # not have any local modifications worth worrying about.
1037 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001038 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001039 syncbuf.fail(self, _PriorSyncFailedError())
1040 return
1041
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001042 if head == revid:
1043 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001044 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001045 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001046 if not syncbuf.detach_head:
1047 return
1048 else:
1049 lost = self._revlist(not_rev(revid), HEAD)
1050 if lost:
1051 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001052
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001054 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001055 except GitError, e:
1056 syncbuf.fail(self, e)
1057 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001059 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001061 if head == revid:
1062 # No changes; don't do anything further.
1063 #
1064 return
1065
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001068 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001070 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001072 syncbuf.info(self,
1073 "leaving %s; does not track upstream",
1074 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001076 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001077 except GitError, e:
1078 syncbuf.fail(self, e)
1079 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001081 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001083 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001084 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001086 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087 if not_merged:
1088 if upstream_gain:
1089 # The user has published this branch and some of those
1090 # commits are not yet merged upstream. We do not want
1091 # to rewrite the published commits so we punt.
1092 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001093 syncbuf.fail(self,
1094 "branch %s is published (but not merged) and is now %d commits behind"
1095 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001096 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001097 elif pub == head:
1098 # All published commits are merged, and thus we are a
1099 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001100 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001101 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001102 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001103 self._CopyFiles()
1104 syncbuf.later1(self, _doff)
1105 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001107 # Examine the local commits not in the remote. Find the
1108 # last one attributed to this user, if any.
1109 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001110 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001111 last_mine = None
1112 cnt_mine = 0
1113 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001114 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001115 if committer_email == self.UserEmail:
1116 last_mine = commit_id
1117 cnt_mine += 1
1118
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001119 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001120 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121
1122 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001123 syncbuf.fail(self, _DirtyError())
1124 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001125
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001126 # If the upstream switched on us, warn the user.
1127 #
1128 if branch.merge != self.revisionExpr:
1129 if branch.merge and self.revisionExpr:
1130 syncbuf.info(self,
1131 'manifest switched %s...%s',
1132 branch.merge,
1133 self.revisionExpr)
1134 elif branch.merge:
1135 syncbuf.info(self,
1136 'manifest no longer tracks %s',
1137 branch.merge)
1138
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001139 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001140 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001141 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001142 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001143 syncbuf.info(self,
1144 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001145 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001147 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001148 if not ID_RE.match(self.revisionExpr):
1149 # in case of manifest sync the revisionExpr might be a SHA1
1150 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001151 branch.Save()
1152
Mike Pontillod3153822012-02-28 11:53:24 -08001153 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001154 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001155 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001156 self._CopyFiles()
1157 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001158 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001159 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001160 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001161 self._CopyFiles()
1162 except GitError, e:
1163 syncbuf.fail(self, e)
1164 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001165 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001166 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001167 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001168 self._CopyFiles()
1169 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001171 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172 # dest should already be an absolute path, but src is project relative
1173 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001174 abssrc = os.path.join(self.worktree, src)
1175 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176
James W. Mills24c13082012-04-12 15:04:13 -05001177 def AddAnnotation(self, name, value, keep):
1178 self.annotations.append(_Annotation(name, value, keep))
1179
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001180 def DownloadPatchSet(self, change_id, patch_id):
1181 """Download a single patch set of a single change to FETCH_HEAD.
1182 """
1183 remote = self.GetRemote(self.remote.name)
1184
1185 cmd = ['fetch', remote.name]
1186 cmd.append('refs/changes/%2.2d/%d/%d' \
1187 % (change_id % 100, change_id, patch_id))
1188 cmd.extend(map(lambda x: str(x), remote.fetch))
1189 if GitCommand(self, cmd, bare=True).Wait() != 0:
1190 return None
1191 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001192 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001193 change_id,
1194 patch_id,
1195 self.bare_git.rev_parse('FETCH_HEAD'))
1196
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001197
1198## Branch Management ##
1199
1200 def StartBranch(self, name):
1201 """Create a new branch off the manifest's revision.
1202 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001203 head = self.work_git.GetHead()
1204 if head == (R_HEADS + name):
1205 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001206
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001207 all = self.bare_ref.all
1208 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001209 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001210 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001211 capture_stdout = True,
1212 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001213
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001214 branch = self.GetBranch(name)
1215 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001216 branch.merge = self.revisionExpr
1217 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001218
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001219 if head.startswith(R_HEADS):
1220 try:
1221 head = all[head]
1222 except KeyError:
1223 head = None
1224
1225 if revid and head and revid == head:
1226 ref = os.path.join(self.gitdir, R_HEADS + name)
1227 try:
1228 os.makedirs(os.path.dirname(ref))
1229 except OSError:
1230 pass
1231 _lwrite(ref, '%s\n' % revid)
1232 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1233 'ref: %s%s\n' % (R_HEADS, name))
1234 branch.Save()
1235 return True
1236
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001237 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001238 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001239 capture_stdout = True,
1240 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001241 branch.Save()
1242 return True
1243 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001244
Wink Saville02d79452009-04-10 13:01:24 -07001245 def CheckoutBranch(self, name):
1246 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001247
1248 Args:
1249 name: The name of the branch to checkout.
1250
1251 Returns:
1252 True if the checkout succeeded; False if it didn't; None if the branch
1253 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001254 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001255 rev = R_HEADS + name
1256 head = self.work_git.GetHead()
1257 if head == rev:
1258 # Already on the branch
1259 #
1260 return True
Wink Saville02d79452009-04-10 13:01:24 -07001261
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001262 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001263 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001264 revid = all[rev]
1265 except KeyError:
1266 # Branch does not exist in this project
1267 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001268 return None
Wink Saville02d79452009-04-10 13:01:24 -07001269
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001270 if head.startswith(R_HEADS):
1271 try:
1272 head = all[head]
1273 except KeyError:
1274 head = None
1275
1276 if head == revid:
1277 # Same revision; just update HEAD to point to the new
1278 # target branch, but otherwise take no other action.
1279 #
1280 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1281 'ref: %s%s\n' % (R_HEADS, name))
1282 return True
1283
1284 return GitCommand(self,
1285 ['checkout', name, '--'],
1286 capture_stdout = True,
1287 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001288
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001289 def AbandonBranch(self, name):
1290 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001291
1292 Args:
1293 name: The name of the branch to abandon.
1294
1295 Returns:
1296 True if the abandon succeeded; False if it didn't; None if the branch
1297 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001298 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001299 rev = R_HEADS + name
1300 all = self.bare_ref.all
1301 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001302 # Doesn't exist
1303 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001304
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001305 head = self.work_git.GetHead()
1306 if head == rev:
1307 # We can't destroy the branch while we are sitting
1308 # on it. Switch to a detached HEAD.
1309 #
1310 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001311
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001312 revid = self.GetRevisionId(all)
1313 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001314 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1315 '%s\n' % revid)
1316 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001317 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001318
1319 return GitCommand(self,
1320 ['branch', '-D', name],
1321 capture_stdout = True,
1322 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001323
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 def PruneHeads(self):
1325 """Prune any topic branches already merged into upstream.
1326 """
1327 cb = self.CurrentBranch
1328 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001329 left = self._allrefs
1330 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001331 if name.startswith(R_HEADS):
1332 name = name[len(R_HEADS):]
1333 if cb is None or name != cb:
1334 kill.append(name)
1335
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001336 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337 if cb is not None \
1338 and not self._revlist(HEAD + '...' + rev) \
1339 and not self.IsDirty(consider_untracked = False):
1340 self.work_git.DetachHead(HEAD)
1341 kill.append(cb)
1342
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001343 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001344 old = self.bare_git.GetHead()
1345 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001346 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1347
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001348 try:
1349 self.bare_git.DetachHead(rev)
1350
1351 b = ['branch', '-d']
1352 b.extend(kill)
1353 b = GitCommand(self, b, bare=True,
1354 capture_stdout=True,
1355 capture_stderr=True)
1356 b.Wait()
1357 finally:
1358 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001359 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001360
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001361 for branch in kill:
1362 if (R_HEADS + branch) not in left:
1363 self.CleanPublishedCache()
1364 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365
1366 if cb and cb not in kill:
1367 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001368 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001369
1370 kept = []
1371 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001372 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373 branch = self.GetBranch(branch)
1374 base = branch.LocalMerge
1375 if not base:
1376 base = rev
1377 kept.append(ReviewableBranch(self, branch, base))
1378 return kept
1379
1380
1381## Direct Git Commands ##
1382
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001383 def _RemoteFetch(self, name=None,
1384 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001385 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001386 quiet=False,
1387 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001388
1389 is_sha1 = False
1390 tag_name = None
1391
1392 if current_branch_only:
1393 if ID_RE.match(self.revisionExpr) is not None:
1394 is_sha1 = True
1395 elif self.revisionExpr.startswith(R_TAGS):
1396 # this is a tag and its sha1 value should never change
1397 tag_name = self.revisionExpr[len(R_TAGS):]
1398
1399 if is_sha1 or tag_name is not None:
1400 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001401 # if revision (sha or tag) is not present then following function
1402 # throws an error.
1403 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001404 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001405 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001406 # There is no such persistent revision. We have to fetch it.
1407 pass
1408
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001409 if not name:
1410 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001411
1412 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001413 remote = self.GetRemote(name)
1414 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001415 ssh_proxy = True
1416
Shawn O. Pearce88443382010-10-08 10:02:09 +02001417 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001418 if alt_dir and 'objects' == os.path.basename(alt_dir):
1419 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001420 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1421 remote = self.GetRemote(name)
1422
1423 all = self.bare_ref.all
1424 ids = set(all.values())
1425 tmp = set()
1426
1427 for r, id in GitRefs(ref_dir).all.iteritems():
1428 if r not in all:
1429 if r.startswith(R_TAGS) or remote.WritesTo(r):
1430 all[r] = id
1431 ids.add(id)
1432 continue
1433
1434 if id in ids:
1435 continue
1436
1437 r = 'refs/_alt/%s' % id
1438 all[r] = id
1439 ids.add(id)
1440 tmp.add(r)
1441
1442 ref_names = list(all.keys())
1443 ref_names.sort()
1444
1445 tmp_packed = ''
1446 old_packed = ''
1447
1448 for r in ref_names:
1449 line = '%s %s\n' % (all[r], r)
1450 tmp_packed += line
1451 if r not in tmp:
1452 old_packed += line
1453
1454 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001455 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001456 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001457
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001458 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001459
1460 # The --depth option only affects the initial fetch; after that we'll do
1461 # full fetches of changes.
1462 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1463 if depth and initial:
1464 cmd.append('--depth=%s' % depth)
1465
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001466 if quiet:
1467 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001468 if not self.worktree:
1469 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001470 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001471
1472 if not current_branch_only or is_sha1:
1473 # Fetch whole repo
1474 cmd.append('--tags')
1475 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1476 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001477 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001478 cmd.append(tag_name)
1479 else:
1480 branch = self.revisionExpr
1481 if branch.startswith(R_HEADS):
1482 branch = branch[len(R_HEADS):]
1483 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001484
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001485 ok = False
1486 for i in range(2):
1487 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1488 ok = True
1489 break
1490 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001491
1492 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001493 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001494 if old_packed != '':
1495 _lwrite(packed_refs, old_packed)
1496 else:
1497 os.remove(packed_refs)
1498 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001499 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001500
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001501 def _ApplyCloneBundle(self, initial=False, quiet=False):
1502 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1503 return False
1504
1505 remote = self.GetRemote(self.remote.name)
1506 bundle_url = remote.url + '/clone.bundle'
1507 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001508 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1509 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001510 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1511 return False
1512
1513 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1514 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1515
1516 exist_dst = os.path.exists(bundle_dst)
1517 exist_tmp = os.path.exists(bundle_tmp)
1518
1519 if not initial and not exist_dst and not exist_tmp:
1520 return False
1521
1522 if not exist_dst:
1523 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1524 if not exist_dst:
1525 return False
1526
1527 cmd = ['fetch']
1528 if quiet:
1529 cmd.append('--quiet')
1530 if not self.worktree:
1531 cmd.append('--update-head-ok')
1532 cmd.append(bundle_dst)
1533 for f in remote.fetch:
1534 cmd.append(str(f))
1535 cmd.append('refs/tags/*:refs/tags/*')
1536
1537 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001538 if os.path.exists(bundle_dst):
1539 os.remove(bundle_dst)
1540 if os.path.exists(bundle_tmp):
1541 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001542 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001543
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001544 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001545 if os.path.exists(dstPath):
1546 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001547
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001548 cmd = ['curl', '--output', tmpPath, '--netrc', '--location']
1549 if quiet:
1550 cmd += ['--silent']
1551 if os.path.exists(tmpPath):
1552 size = os.stat(tmpPath).st_size
1553 if size >= 1024:
1554 cmd += ['--continue-at', '%d' % (size,)]
1555 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001556 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001557 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1558 cmd += ['--proxy', os.environ['http_proxy']]
1559 cmd += [srcUrl]
1560
1561 if IsTrace():
1562 Trace('%s', ' '.join(cmd))
1563 try:
1564 proc = subprocess.Popen(cmd)
1565 except OSError:
1566 return False
1567
1568 ok = proc.wait() == 0
1569 if os.path.exists(tmpPath):
1570 if ok and os.stat(tmpPath).st_size > 16:
1571 os.rename(tmpPath, dstPath)
1572 return True
1573 else:
1574 os.remove(tmpPath)
1575 return False
1576 else:
1577 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001578
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001579 def _Checkout(self, rev, quiet=False):
1580 cmd = ['checkout']
1581 if quiet:
1582 cmd.append('-q')
1583 cmd.append(rev)
1584 cmd.append('--')
1585 if GitCommand(self, cmd).Wait() != 0:
1586 if self._allrefs:
1587 raise GitError('%s checkout %s ' % (self.name, rev))
1588
Pierre Tardye5a21222011-03-24 16:28:18 +01001589 def _CherryPick(self, rev, quiet=False):
1590 cmd = ['cherry-pick']
1591 cmd.append(rev)
1592 cmd.append('--')
1593 if GitCommand(self, cmd).Wait() != 0:
1594 if self._allrefs:
1595 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1596
Erwan Mahea94f1622011-08-19 13:56:09 +02001597 def _Revert(self, rev, quiet=False):
1598 cmd = ['revert']
1599 cmd.append('--no-edit')
1600 cmd.append(rev)
1601 cmd.append('--')
1602 if GitCommand(self, cmd).Wait() != 0:
1603 if self._allrefs:
1604 raise GitError('%s revert %s ' % (self.name, rev))
1605
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001606 def _ResetHard(self, rev, quiet=True):
1607 cmd = ['reset', '--hard']
1608 if quiet:
1609 cmd.append('-q')
1610 cmd.append(rev)
1611 if GitCommand(self, cmd).Wait() != 0:
1612 raise GitError('%s reset --hard %s ' % (self.name, rev))
1613
1614 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001615 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001616 if onto is not None:
1617 cmd.extend(['--onto', onto])
1618 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001619 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001620 raise GitError('%s rebase %s ' % (self.name, upstream))
1621
Pierre Tardy3d125942012-05-04 12:18:12 +02001622 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001623 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001624 if ffonly:
1625 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001626 if GitCommand(self, cmd).Wait() != 0:
1627 raise GitError('%s merge %s ' % (self.name, head))
1628
1629 def _InitGitDir(self):
1630 if not os.path.exists(self.gitdir):
1631 os.makedirs(self.gitdir)
1632 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001633
Shawn O. Pearce88443382010-10-08 10:02:09 +02001634 mp = self.manifest.manifestProject
1635 ref_dir = mp.config.GetString('repo.reference')
1636
1637 if ref_dir:
1638 mirror_git = os.path.join(ref_dir, self.name + '.git')
1639 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1640 self.relpath + '.git')
1641
1642 if os.path.exists(mirror_git):
1643 ref_dir = mirror_git
1644
1645 elif os.path.exists(repo_git):
1646 ref_dir = repo_git
1647
1648 else:
1649 ref_dir = None
1650
1651 if ref_dir:
1652 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1653 os.path.join(ref_dir, 'objects') + '\n')
1654
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001655 if self.manifest.IsMirror:
1656 self.config.SetString('core.bare', 'true')
1657 else:
1658 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001659
1660 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001661 try:
1662 to_rm = os.listdir(hooks)
1663 except OSError:
1664 to_rm = []
1665 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001666 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001667 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001668
1669 m = self.manifest.manifestProject.config
1670 for key in ['user.name', 'user.email']:
1671 if m.Has(key, include_defaults = False):
1672 self.config.SetString(key, m.GetString(key))
1673
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001674 def _InitHooks(self):
1675 hooks = self._gitdir_path('hooks')
1676 if not os.path.exists(hooks):
1677 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001678 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001679 name = os.path.basename(stock_hook)
1680
Victor Boivie65e0f352011-04-18 11:23:29 +02001681 if name in ('commit-msg',) and not self.remote.review \
1682 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001683 # Don't install a Gerrit Code Review hook if this
1684 # project does not appear to use it for reviews.
1685 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001686 # Since the manifest project is one of those, but also
1687 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001688 continue
1689
1690 dst = os.path.join(hooks, name)
1691 if os.path.islink(dst):
1692 continue
1693 if os.path.exists(dst):
1694 if filecmp.cmp(stock_hook, dst, shallow=False):
1695 os.remove(dst)
1696 else:
1697 _error("%s: Not replacing %s hook", self.relpath, name)
1698 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001699 try:
1700 os.symlink(relpath(stock_hook, dst), dst)
1701 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001702 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001703 raise GitError('filesystem must support symlinks')
1704 else:
1705 raise
1706
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001707 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001708 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001709 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001710 remote.url = self.remote.url
1711 remote.review = self.remote.review
1712 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001713
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001714 if self.worktree:
1715 remote.ResetFetch(mirror=False)
1716 else:
1717 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001718 remote.Save()
1719
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001720 def _InitMRef(self):
1721 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001722 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001723
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001724 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001725 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001726
1727 def _InitAnyMRef(self, ref):
1728 cur = self.bare_ref.symref(ref)
1729
1730 if self.revisionId:
1731 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1732 msg = 'manifest set to %s' % self.revisionId
1733 dst = self.revisionId + '^0'
1734 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1735 else:
1736 remote = self.GetRemote(self.remote.name)
1737 dst = remote.ToLocal(self.revisionExpr)
1738 if cur != dst:
1739 msg = 'manifest set to %s' % self.revisionExpr
1740 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001741
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001742 def _InitWorkTree(self):
1743 dotgit = os.path.join(self.worktree, '.git')
1744 if not os.path.exists(dotgit):
1745 os.makedirs(dotgit)
1746
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001747 for name in ['config',
1748 'description',
1749 'hooks',
1750 'info',
1751 'logs',
1752 'objects',
1753 'packed-refs',
1754 'refs',
1755 'rr-cache',
1756 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001757 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001758 src = os.path.join(self.gitdir, name)
1759 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001760 if os.path.islink(dst) or not os.path.exists(dst):
1761 os.symlink(relpath(src, dst), dst)
1762 else:
1763 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001764 except OSError, e:
1765 if e.errno == errno.EPERM:
1766 raise GitError('filesystem must support symlinks')
1767 else:
1768 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001770 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001771
1772 cmd = ['read-tree', '--reset', '-u']
1773 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001774 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001775 if GitCommand(self, cmd).Wait() != 0:
1776 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001777
1778 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1779 if not os.path.exists(rr_cache):
1780 os.makedirs(rr_cache)
1781
Shawn O. Pearce93609662009-04-21 10:50:33 -07001782 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001783
1784 def _gitdir_path(self, path):
1785 return os.path.join(self.gitdir, path)
1786
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001787 def _revlist(self, *args, **kw):
1788 a = []
1789 a.extend(args)
1790 a.append('--')
1791 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001792
1793 @property
1794 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001795 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001796
1797 class _GitGetByExec(object):
1798 def __init__(self, project, bare):
1799 self._project = project
1800 self._bare = bare
1801
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001802 def LsOthers(self):
1803 p = GitCommand(self._project,
1804 ['ls-files',
1805 '-z',
1806 '--others',
1807 '--exclude-standard'],
1808 bare = False,
1809 capture_stdout = True,
1810 capture_stderr = True)
1811 if p.Wait() == 0:
1812 out = p.stdout
1813 if out:
1814 return out[:-1].split("\0")
1815 return []
1816
1817 def DiffZ(self, name, *args):
1818 cmd = [name]
1819 cmd.append('-z')
1820 cmd.extend(args)
1821 p = GitCommand(self._project,
1822 cmd,
1823 bare = False,
1824 capture_stdout = True,
1825 capture_stderr = True)
1826 try:
1827 out = p.process.stdout.read()
1828 r = {}
1829 if out:
1830 out = iter(out[:-1].split('\0'))
1831 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001832 try:
1833 info = out.next()
1834 path = out.next()
1835 except StopIteration:
1836 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001837
1838 class _Info(object):
1839 def __init__(self, path, omode, nmode, oid, nid, state):
1840 self.path = path
1841 self.src_path = None
1842 self.old_mode = omode
1843 self.new_mode = nmode
1844 self.old_id = oid
1845 self.new_id = nid
1846
1847 if len(state) == 1:
1848 self.status = state
1849 self.level = None
1850 else:
1851 self.status = state[:1]
1852 self.level = state[1:]
1853 while self.level.startswith('0'):
1854 self.level = self.level[1:]
1855
1856 info = info[1:].split(' ')
1857 info =_Info(path, *info)
1858 if info.status in ('R', 'C'):
1859 info.src_path = info.path
1860 info.path = out.next()
1861 r[info.path] = info
1862 return r
1863 finally:
1864 p.Wait()
1865
1866 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001867 if self._bare:
1868 path = os.path.join(self._project.gitdir, HEAD)
1869 else:
1870 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001871 fd = open(path, 'rb')
1872 try:
1873 line = fd.read()
1874 finally:
1875 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001876 if line.startswith('ref: '):
1877 return line[5:-1]
1878 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001879
1880 def SetHead(self, ref, message=None):
1881 cmdv = []
1882 if message is not None:
1883 cmdv.extend(['-m', message])
1884 cmdv.append(HEAD)
1885 cmdv.append(ref)
1886 self.symbolic_ref(*cmdv)
1887
1888 def DetachHead(self, new, message=None):
1889 cmdv = ['--no-deref']
1890 if message is not None:
1891 cmdv.extend(['-m', message])
1892 cmdv.append(HEAD)
1893 cmdv.append(new)
1894 self.update_ref(*cmdv)
1895
1896 def UpdateRef(self, name, new, old=None,
1897 message=None,
1898 detach=False):
1899 cmdv = []
1900 if message is not None:
1901 cmdv.extend(['-m', message])
1902 if detach:
1903 cmdv.append('--no-deref')
1904 cmdv.append(name)
1905 cmdv.append(new)
1906 if old is not None:
1907 cmdv.append(old)
1908 self.update_ref(*cmdv)
1909
1910 def DeleteRef(self, name, old=None):
1911 if not old:
1912 old = self.rev_parse(name)
1913 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001914 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001915
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001916 def rev_list(self, *args, **kw):
1917 if 'format' in kw:
1918 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1919 else:
1920 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001921 cmdv.extend(args)
1922 p = GitCommand(self._project,
1923 cmdv,
1924 bare = self._bare,
1925 capture_stdout = True,
1926 capture_stderr = True)
1927 r = []
1928 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001929 if line[-1] == '\n':
1930 line = line[:-1]
1931 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001932 if p.Wait() != 0:
1933 raise GitError('%s rev-list %s: %s' % (
1934 self._project.name,
1935 str(args),
1936 p.stderr))
1937 return r
1938
1939 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001940 """Allow arbitrary git commands using pythonic syntax.
1941
1942 This allows you to do things like:
1943 git_obj.rev_parse('HEAD')
1944
1945 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1946 run. We'll replace the '_' with a '-' and try to run a git command.
1947 Any other arguments will be passed to the git command.
1948
1949 Args:
1950 name: The name of the git command to call. Any '_' characters will
1951 be replaced with '-'.
1952
1953 Returns:
1954 A callable object that will try to call git with the named command.
1955 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001956 name = name.replace('_', '-')
1957 def runner(*args):
1958 cmdv = [name]
1959 cmdv.extend(args)
1960 p = GitCommand(self._project,
1961 cmdv,
1962 bare = self._bare,
1963 capture_stdout = True,
1964 capture_stderr = True)
1965 if p.Wait() != 0:
1966 raise GitError('%s %s: %s' % (
1967 self._project.name,
1968 name,
1969 p.stderr))
1970 r = p.stdout
1971 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1972 return r[:-1]
1973 return r
1974 return runner
1975
1976
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001977class _PriorSyncFailedError(Exception):
1978 def __str__(self):
1979 return 'prior sync failed; rebase still in progress'
1980
1981class _DirtyError(Exception):
1982 def __str__(self):
1983 return 'contains uncommitted changes'
1984
1985class _InfoMessage(object):
1986 def __init__(self, project, text):
1987 self.project = project
1988 self.text = text
1989
1990 def Print(self, syncbuf):
1991 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1992 syncbuf.out.nl()
1993
1994class _Failure(object):
1995 def __init__(self, project, why):
1996 self.project = project
1997 self.why = why
1998
1999 def Print(self, syncbuf):
2000 syncbuf.out.fail('error: %s/: %s',
2001 self.project.relpath,
2002 str(self.why))
2003 syncbuf.out.nl()
2004
2005class _Later(object):
2006 def __init__(self, project, action):
2007 self.project = project
2008 self.action = action
2009
2010 def Run(self, syncbuf):
2011 out = syncbuf.out
2012 out.project('project %s/', self.project.relpath)
2013 out.nl()
2014 try:
2015 self.action()
2016 out.nl()
2017 return True
2018 except GitError, e:
2019 out.nl()
2020 return False
2021
2022class _SyncColoring(Coloring):
2023 def __init__(self, config):
2024 Coloring.__init__(self, config, 'reposync')
2025 self.project = self.printer('header', attr = 'bold')
2026 self.info = self.printer('info')
2027 self.fail = self.printer('fail', fg='red')
2028
2029class SyncBuffer(object):
2030 def __init__(self, config, detach_head=False):
2031 self._messages = []
2032 self._failures = []
2033 self._later_queue1 = []
2034 self._later_queue2 = []
2035
2036 self.out = _SyncColoring(config)
2037 self.out.redirect(sys.stderr)
2038
2039 self.detach_head = detach_head
2040 self.clean = True
2041
2042 def info(self, project, fmt, *args):
2043 self._messages.append(_InfoMessage(project, fmt % args))
2044
2045 def fail(self, project, err=None):
2046 self._failures.append(_Failure(project, err))
2047 self.clean = False
2048
2049 def later1(self, project, what):
2050 self._later_queue1.append(_Later(project, what))
2051
2052 def later2(self, project, what):
2053 self._later_queue2.append(_Later(project, what))
2054
2055 def Finish(self):
2056 self._PrintMessages()
2057 self._RunLater()
2058 self._PrintMessages()
2059 return self.clean
2060
2061 def _RunLater(self):
2062 for q in ['_later_queue1', '_later_queue2']:
2063 if not self._RunQueue(q):
2064 return
2065
2066 def _RunQueue(self, queue):
2067 for m in getattr(self, queue):
2068 if not m.Run(self):
2069 self.clean = False
2070 return False
2071 setattr(self, queue, [])
2072 return True
2073
2074 def _PrintMessages(self):
2075 for m in self._messages:
2076 m.Print(self)
2077 for m in self._failures:
2078 m.Print(self)
2079
2080 self._messages = []
2081 self._failures = []
2082
2083
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002084class MetaProject(Project):
2085 """A special project housed under .repo.
2086 """
2087 def __init__(self, manifest, name, gitdir, worktree):
2088 repodir = manifest.repodir
2089 Project.__init__(self,
2090 manifest = manifest,
2091 name = name,
2092 gitdir = gitdir,
2093 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002094 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002095 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002096 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002097 revisionId = None,
2098 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002099
2100 def PreSync(self):
2101 if self.Exists:
2102 cb = self.CurrentBranch
2103 if cb:
2104 base = self.GetBranch(cb).merge
2105 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002106 self.revisionExpr = base
2107 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002108
Florian Vallee5d016502012-06-07 17:19:26 +02002109 def MetaBranchSwitch(self, target):
2110 """ Prepare MetaProject for manifest branch switch
2111 """
2112
2113 # detach and delete manifest branch, allowing a new
2114 # branch to take over
2115 syncbuf = SyncBuffer(self.config, detach_head = True)
2116 self.Sync_LocalHalf(syncbuf)
2117 syncbuf.Finish()
2118
2119 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002120 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002121 capture_stdout = True,
2122 capture_stderr = True).Wait() == 0
2123
2124
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002125 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002126 def LastFetch(self):
2127 try:
2128 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2129 return os.path.getmtime(fh)
2130 except OSError:
2131 return 0
2132
2133 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134 def HasChanges(self):
2135 """Has the remote received new commits not yet checked out?
2136 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002137 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002138 return False
2139
2140 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002141 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002142 head = self.work_git.GetHead()
2143 if head.startswith(R_HEADS):
2144 try:
2145 head = all[head]
2146 except KeyError:
2147 head = None
2148
2149 if revid == head:
2150 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002151 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002152 return True
2153 return False