blob: 00ebb17ff6eb2e4dd714384ef4565ce5483537f8 [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
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080083
Shawn O. Pearce632768b2008-10-23 11:58:52 -070084class DownloadedChange(object):
85 _commit_cache = None
86
87 def __init__(self, project, base, change_id, ps_id, commit):
88 self.project = project
89 self.base = base
90 self.change_id = change_id
91 self.ps_id = ps_id
92 self.commit = commit
93
94 @property
95 def commits(self):
96 if self._commit_cache is None:
97 self._commit_cache = self.project.bare_git.rev_list(
98 '--abbrev=8',
99 '--abbrev-commit',
100 '--pretty=oneline',
101 '--reverse',
102 '--date-order',
103 not_rev(self.base),
104 self.commit,
105 '--')
106 return self._commit_cache
107
108
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700109class ReviewableBranch(object):
110 _commit_cache = None
111
112 def __init__(self, project, branch, base):
113 self.project = project
114 self.branch = branch
115 self.base = base
116
117 @property
118 def name(self):
119 return self.branch.name
120
121 @property
122 def commits(self):
123 if self._commit_cache is None:
124 self._commit_cache = self.project.bare_git.rev_list(
125 '--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 R_HEADS + self.name,
132 '--')
133 return self._commit_cache
134
135 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800136 def unabbrev_commits(self):
137 r = dict()
138 for commit in self.project.bare_git.rev_list(
139 not_rev(self.base),
140 R_HEADS + self.name,
141 '--'):
142 r[commit[0:8]] = commit
143 return r
144
145 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700146 def date(self):
147 return self.project.bare_git.log(
148 '--pretty=format:%cd',
149 '-n', '1',
150 R_HEADS + self.name,
151 '--')
152
Brian Harring435370c2012-07-28 15:37:04 -0700153 def UploadForReview(self, people, auto_topic=False, draft=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800154 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700155 people,
Brian Harring435370c2012-07-28 15:37:04 -0700156 auto_topic=auto_topic,
157 draft=draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700159 def GetPublishedRefs(self):
160 refs = {}
161 output = self.project.bare_git.ls_remote(
162 self.branch.remote.SshReviewUrl(self.project.UserEmail),
163 'refs/changes/*')
164 for line in output.split('\n'):
165 try:
166 (sha, ref) = line.split()
167 refs[sha] = ref
168 except ValueError:
169 pass
170
171 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172
173class StatusColoring(Coloring):
174 def __init__(self, config):
175 Coloring.__init__(self, config, 'status')
176 self.project = self.printer('header', attr = 'bold')
177 self.branch = self.printer('header', attr = 'bold')
178 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700179 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700180
181 self.added = self.printer('added', fg = 'green')
182 self.changed = self.printer('changed', fg = 'red')
183 self.untracked = self.printer('untracked', fg = 'red')
184
185
186class DiffColoring(Coloring):
187 def __init__(self, config):
188 Coloring.__init__(self, config, 'diff')
189 self.project = self.printer('header', attr = 'bold')
190
James W. Mills24c13082012-04-12 15:04:13 -0500191class _Annotation:
192 def __init__(self, name, value, keep):
193 self.name = name
194 self.value = value
195 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700196
197class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800198 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199 self.src = src
200 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800201 self.abs_src = abssrc
202 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203
204 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800205 src = self.abs_src
206 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207 # copy file if it does not exist or is out of date
208 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
209 try:
210 # remove existing file first, since it might be read-only
211 if os.path.exists(dest):
212 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400213 else:
214 dir = os.path.dirname(dest)
215 if not os.path.isdir(dir):
216 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 shutil.copy(src, dest)
218 # make the file read-only
219 mode = os.stat(dest)[stat.ST_MODE]
220 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
221 os.chmod(dest, mode)
222 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700223 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700225class RemoteSpec(object):
226 def __init__(self,
227 name,
228 url = None,
229 review = None):
230 self.name = name
231 self.url = url
232 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233
Doug Anderson37282b42011-03-04 11:54:18 -0800234class RepoHook(object):
235 """A RepoHook contains information about a script to run as a hook.
236
237 Hooks are used to run a python script before running an upload (for instance,
238 to run presubmit checks). Eventually, we may have hooks for other actions.
239
240 This shouldn't be confused with files in the 'repo/hooks' directory. Those
241 files are copied into each '.git/hooks' folder for each project. Repo-level
242 hooks are associated instead with repo actions.
243
244 Hooks are always python. When a hook is run, we will load the hook into the
245 interpreter and execute its main() function.
246 """
247 def __init__(self,
248 hook_type,
249 hooks_project,
250 topdir,
251 abort_if_user_denies=False):
252 """RepoHook constructor.
253
254 Params:
255 hook_type: A string representing the type of hook. This is also used
256 to figure out the name of the file containing the hook. For
257 example: 'pre-upload'.
258 hooks_project: The project containing the repo hooks. If you have a
259 manifest, this is manifest.repo_hooks_project. OK if this is None,
260 which will make the hook a no-op.
261 topdir: Repo's top directory (the one containing the .repo directory).
262 Scripts will run with CWD as this directory. If you have a manifest,
263 this is manifest.topdir
264 abort_if_user_denies: If True, we'll throw a HookError() if the user
265 doesn't allow us to run the hook.
266 """
267 self._hook_type = hook_type
268 self._hooks_project = hooks_project
269 self._topdir = topdir
270 self._abort_if_user_denies = abort_if_user_denies
271
272 # Store the full path to the script for convenience.
273 if self._hooks_project:
274 self._script_fullpath = os.path.join(self._hooks_project.worktree,
275 self._hook_type + '.py')
276 else:
277 self._script_fullpath = None
278
279 def _GetHash(self):
280 """Return a hash of the contents of the hooks directory.
281
282 We'll just use git to do this. This hash has the property that if anything
283 changes in the directory we will return a different has.
284
285 SECURITY CONSIDERATION:
286 This hash only represents the contents of files in the hook directory, not
287 any other files imported or called by hooks. Changes to imported files
288 can change the script behavior without affecting the hash.
289
290 Returns:
291 A string representing the hash. This will always be ASCII so that it can
292 be printed to the user easily.
293 """
294 assert self._hooks_project, "Must have hooks to calculate their hash."
295
296 # We will use the work_git object rather than just calling GetRevisionId().
297 # That gives us a hash of the latest checked in version of the files that
298 # the user will actually be executing. Specifically, GetRevisionId()
299 # doesn't appear to change even if a user checks out a different version
300 # of the hooks repo (via git checkout) nor if a user commits their own revs.
301 #
302 # NOTE: Local (non-committed) changes will not be factored into this hash.
303 # I think this is OK, since we're really only worried about warning the user
304 # about upstream changes.
305 return self._hooks_project.work_git.rev_parse('HEAD')
306
307 def _GetMustVerb(self):
308 """Return 'must' if the hook is required; 'should' if not."""
309 if self._abort_if_user_denies:
310 return 'must'
311 else:
312 return 'should'
313
314 def _CheckForHookApproval(self):
315 """Check to see whether this hook has been approved.
316
317 We'll look at the hash of all of the hooks. If this matches the hash that
318 the user last approved, we're done. If it doesn't, we'll ask the user
319 about approval.
320
321 Note that we ask permission for each individual hook even though we use
322 the hash of all hooks when detecting changes. We'd like the user to be
323 able to approve / deny each hook individually. We only use the hash of all
324 hooks because there is no other easy way to detect changes to local imports.
325
326 Returns:
327 True if this hook is approved to run; False otherwise.
328
329 Raises:
330 HookError: Raised if the user doesn't approve and abort_if_user_denies
331 was passed to the consturctor.
332 """
333 hooks_dir = self._hooks_project.worktree
334 hooks_config = self._hooks_project.config
335 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
336
337 # Get the last hash that the user approved for this hook; may be None.
338 old_hash = hooks_config.GetString(git_approval_key)
339
340 # Get the current hash so we can tell if scripts changed since approval.
341 new_hash = self._GetHash()
342
343 if old_hash is not None:
344 # User previously approved hook and asked not to be prompted again.
345 if new_hash == old_hash:
346 # Approval matched. We're done.
347 return True
348 else:
349 # Give the user a reason why we're prompting, since they last told
350 # us to "never ask again".
351 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
352 self._hook_type)
353 else:
354 prompt = ''
355
356 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
357 if sys.stdout.isatty():
358 prompt += ('Repo %s run the script:\n'
359 ' %s\n'
360 '\n'
361 'Do you want to allow this script to run '
362 '(yes/yes-never-ask-again/NO)? ') % (
363 self._GetMustVerb(), self._script_fullpath)
364 response = raw_input(prompt).lower()
365 print
366
367 # User is doing a one-time approval.
368 if response in ('y', 'yes'):
369 return True
370 elif response == 'yes-never-ask-again':
371 hooks_config.SetString(git_approval_key, new_hash)
372 return True
373
374 # For anything else, we'll assume no approval.
375 if self._abort_if_user_denies:
376 raise HookError('You must allow the %s hook or use --no-verify.' %
377 self._hook_type)
378
379 return False
380
381 def _ExecuteHook(self, **kwargs):
382 """Actually execute the given hook.
383
384 This will run the hook's 'main' function in our python interpreter.
385
386 Args:
387 kwargs: Keyword arguments to pass to the hook. These are often specific
388 to the hook type. For instance, pre-upload hooks will contain
389 a project_list.
390 """
391 # Keep sys.path and CWD stashed away so that we can always restore them
392 # upon function exit.
393 orig_path = os.getcwd()
394 orig_syspath = sys.path
395
396 try:
397 # Always run hooks with CWD as topdir.
398 os.chdir(self._topdir)
399
400 # Put the hook dir as the first item of sys.path so hooks can do
401 # relative imports. We want to replace the repo dir as [0] so
402 # hooks can't import repo files.
403 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
404
405 # Exec, storing global context in the context dict. We catch exceptions
406 # and convert to a HookError w/ just the failing traceback.
407 context = {}
408 try:
409 execfile(self._script_fullpath, context)
410 except Exception:
411 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
412 traceback.format_exc(), self._hook_type))
413
414 # Running the script should have defined a main() function.
415 if 'main' not in context:
416 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
417
418
419 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
420 # We don't actually want hooks to define their main with this argument--
421 # it's there to remind them that their hook should always take **kwargs.
422 # For instance, a pre-upload hook should be defined like:
423 # def main(project_list, **kwargs):
424 #
425 # This allows us to later expand the API without breaking old hooks.
426 kwargs = kwargs.copy()
427 kwargs['hook_should_take_kwargs'] = True
428
429 # Call the main function in the hook. If the hook should cause the
430 # build to fail, it will raise an Exception. We'll catch that convert
431 # to a HookError w/ just the failing traceback.
432 try:
433 context['main'](**kwargs)
434 except Exception:
435 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
436 'above.' % (
437 traceback.format_exc(), self._hook_type))
438 finally:
439 # Restore sys.path and CWD.
440 sys.path = orig_syspath
441 os.chdir(orig_path)
442
443 def Run(self, user_allows_all_hooks, **kwargs):
444 """Run the hook.
445
446 If the hook doesn't exist (because there is no hooks project or because
447 this particular hook is not enabled), this is a no-op.
448
449 Args:
450 user_allows_all_hooks: If True, we will never prompt about running the
451 hook--we'll just assume it's OK to run it.
452 kwargs: Keyword arguments to pass to the hook. These are often specific
453 to the hook type. For instance, pre-upload hooks will contain
454 a project_list.
455
456 Raises:
457 HookError: If there was a problem finding the hook or the user declined
458 to run a required hook (from _CheckForHookApproval).
459 """
460 # No-op if there is no hooks project or if hook is disabled.
461 if ((not self._hooks_project) or
462 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
463 return
464
465 # Bail with a nice error if we can't find the hook.
466 if not os.path.isfile(self._script_fullpath):
467 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
468
469 # Make sure the user is OK with running the hook.
470 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
471 return
472
473 # Run the hook with the same version of python we're using.
474 self._ExecuteHook(**kwargs)
475
476
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700477class Project(object):
478 def __init__(self,
479 manifest,
480 name,
481 remote,
482 gitdir,
483 worktree,
484 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700485 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800486 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700487 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700488 groups = None,
489 sync_c = False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700490 self.manifest = manifest
491 self.name = name
492 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800493 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800494 if worktree:
495 self.worktree = worktree.replace('\\', '/')
496 else:
497 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700498 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700499 self.revisionExpr = revisionExpr
500
501 if revisionId is None \
502 and revisionExpr \
503 and IsId(revisionExpr):
504 self.revisionId = revisionExpr
505 else:
506 self.revisionId = revisionId
507
Mike Pontillod3153822012-02-28 11:53:24 -0800508 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700509 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700510 self.sync_c = sync_c
Mike Pontillod3153822012-02-28 11:53:24 -0800511
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700513 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500514 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700515 self.config = GitConfig.ForRepository(
516 gitdir = self.gitdir,
517 defaults = self.manifest.globalConfig)
518
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800519 if self.worktree:
520 self.work_git = self._GitGetByExec(self, bare=False)
521 else:
522 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700523 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700524 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700525
Doug Anderson37282b42011-03-04 11:54:18 -0800526 # This will be filled in if a project is later identified to be the
527 # project containing repo hooks.
528 self.enabled_repo_hooks = []
529
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700530 @property
531 def Exists(self):
532 return os.path.isdir(self.gitdir)
533
534 @property
535 def CurrentBranch(self):
536 """Obtain the name of the currently checked out branch.
537 The branch name omits the 'refs/heads/' prefix.
538 None is returned if the project is on a detached HEAD.
539 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700540 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700541 if b.startswith(R_HEADS):
542 return b[len(R_HEADS):]
543 return None
544
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700545 def IsRebaseInProgress(self):
546 w = self.worktree
547 g = os.path.join(w, '.git')
548 return os.path.exists(os.path.join(g, 'rebase-apply')) \
549 or os.path.exists(os.path.join(g, 'rebase-merge')) \
550 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200551
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552 def IsDirty(self, consider_untracked=True):
553 """Is the working directory modified in some way?
554 """
555 self.work_git.update_index('-q',
556 '--unmerged',
557 '--ignore-missing',
558 '--refresh')
559 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
560 return True
561 if self.work_git.DiffZ('diff-files'):
562 return True
563 if consider_untracked and self.work_git.LsOthers():
564 return True
565 return False
566
567 _userident_name = None
568 _userident_email = None
569
570 @property
571 def UserName(self):
572 """Obtain the user's personal name.
573 """
574 if self._userident_name is None:
575 self._LoadUserIdentity()
576 return self._userident_name
577
578 @property
579 def UserEmail(self):
580 """Obtain the user's email address. This is very likely
581 to be their Gerrit login.
582 """
583 if self._userident_email is None:
584 self._LoadUserIdentity()
585 return self._userident_email
586
587 def _LoadUserIdentity(self):
588 u = self.bare_git.var('GIT_COMMITTER_IDENT')
589 m = re.compile("^(.*) <([^>]*)> ").match(u)
590 if m:
591 self._userident_name = m.group(1)
592 self._userident_email = m.group(2)
593 else:
594 self._userident_name = ''
595 self._userident_email = ''
596
597 def GetRemote(self, name):
598 """Get the configuration for a single remote.
599 """
600 return self.config.GetRemote(name)
601
602 def GetBranch(self, name):
603 """Get the configuration for a single branch.
604 """
605 return self.config.GetBranch(name)
606
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700607 def GetBranches(self):
608 """Get all existing local branches.
609 """
610 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700611 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700612 heads = {}
613 pubd = {}
614
615 for name, id in all.iteritems():
616 if name.startswith(R_HEADS):
617 name = name[len(R_HEADS):]
618 b = self.GetBranch(name)
619 b.current = name == current
620 b.published = None
621 b.revision = id
622 heads[name] = b
623
624 for name, id in all.iteritems():
625 if name.startswith(R_PUB):
626 name = name[len(R_PUB):]
627 b = heads.get(name)
628 if b:
629 b.published = id
630
631 return heads
632
Colin Cross5acde752012-03-28 20:15:45 -0700633 def MatchesGroups(self, manifest_groups):
634 """Returns true if the manifest groups specified at init should cause
635 this project to be synced.
636 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owens971de8e2012-04-16 10:36:08 -0700637 All projects are implicitly labelled with "default".
638
639 labels are resolved in order. In the example case of
640 project_groups: "default,group1,group2"
641 manifest_groups: "-group1,group2"
642 the project will be matched.
Colin Cross5acde752012-03-28 20:15:45 -0700643 """
Colin Crosseca119e2012-05-24 15:39:14 -0700644 if self.groups is None:
645 return True
Conley Owens971de8e2012-04-16 10:36:08 -0700646 matched = False
647 for group in manifest_groups:
648 if group.startswith('-') and group[1:] in self.groups:
649 matched = False
650 elif group in self.groups:
651 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700652
Conley Owens971de8e2012-04-16 10:36:08 -0700653 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654
655## Status Display ##
656
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500657 def HasChanges(self):
658 """Returns true if there are uncommitted changes.
659 """
660 self.work_git.update_index('-q',
661 '--unmerged',
662 '--ignore-missing',
663 '--refresh')
664 if self.IsRebaseInProgress():
665 return True
666
667 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
668 return True
669
670 if self.work_git.DiffZ('diff-files'):
671 return True
672
673 if self.work_git.LsOthers():
674 return True
675
676 return False
677
Terence Haddock4655e812011-03-31 12:33:34 +0200678 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200680
681 Args:
682 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700683 """
684 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200685 if output_redir == None:
686 output_redir = sys.stdout
687 print >>output_redir, ''
688 print >>output_redir, 'project %s/' % self.relpath
689 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700690 return
691
692 self.work_git.update_index('-q',
693 '--unmerged',
694 '--ignore-missing',
695 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700696 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700697 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
698 df = self.work_git.DiffZ('diff-files')
699 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100700 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700701 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702
703 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200704 if not output_redir == None:
705 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700706 out.project('project %-40s', self.relpath + '/')
707
708 branch = self.CurrentBranch
709 if branch is None:
710 out.nobranch('(*** NO BRANCH ***)')
711 else:
712 out.branch('branch %s', branch)
713 out.nl()
714
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700715 if rb:
716 out.important('prior sync failed; rebase still in progress')
717 out.nl()
718
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700719 paths = list()
720 paths.extend(di.keys())
721 paths.extend(df.keys())
722 paths.extend(do)
723
724 paths = list(set(paths))
725 paths.sort()
726
727 for p in paths:
728 try: i = di[p]
729 except KeyError: i = None
730
731 try: f = df[p]
732 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200733
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700734 if i: i_status = i.status.upper()
735 else: i_status = '-'
736
737 if f: f_status = f.status.lower()
738 else: f_status = '-'
739
740 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800741 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742 i.src_path, p, i.level)
743 else:
744 line = ' %s%s\t%s' % (i_status, f_status, p)
745
746 if i and not f:
747 out.added('%s', line)
748 elif (i and f) or (not i and f):
749 out.changed('%s', line)
750 elif not i and not f:
751 out.untracked('%s', line)
752 else:
753 out.write('%s', line)
754 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200755
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700756 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757
pelyad67872d2012-03-28 14:49:58 +0300758 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759 """Prints the status of the repository to stdout.
760 """
761 out = DiffColoring(self.config)
762 cmd = ['diff']
763 if out.is_on:
764 cmd.append('--color')
765 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300766 if absolute_paths:
767 cmd.append('--src-prefix=a/%s/' % self.relpath)
768 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700769 cmd.append('--')
770 p = GitCommand(self,
771 cmd,
772 capture_stdout = True,
773 capture_stderr = True)
774 has_diff = False
775 for line in p.process.stdout:
776 if not has_diff:
777 out.nl()
778 out.project('project %s/' % self.relpath)
779 out.nl()
780 has_diff = True
781 print line[:-1]
782 p.Wait()
783
784
785## Publish / Upload ##
786
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700787 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788 """Was the branch published (uploaded) for code review?
789 If so, returns the SHA-1 hash of the last published
790 state for the branch.
791 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700792 key = R_PUB + branch
793 if all is None:
794 try:
795 return self.bare_git.rev_parse(key)
796 except GitError:
797 return None
798 else:
799 try:
800 return all[key]
801 except KeyError:
802 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700804 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700805 """Prunes any stale published refs.
806 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700807 if all is None:
808 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809 heads = set()
810 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700811 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812 if name.startswith(R_HEADS):
813 heads.add(name)
814 elif name.startswith(R_PUB):
815 canrm[name] = id
816
817 for name, id in canrm.iteritems():
818 n = name[len(R_PUB):]
819 if R_HEADS + n not in heads:
820 self.bare_git.DeleteRef(name, id)
821
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700822 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700823 """List any branches which can be uploaded for review.
824 """
825 heads = {}
826 pubed = {}
827
828 for name, id in self._allrefs.iteritems():
829 if name.startswith(R_HEADS):
830 heads[name[len(R_HEADS):]] = id
831 elif name.startswith(R_PUB):
832 pubed[name[len(R_PUB):]] = id
833
834 ready = []
835 for branch, id in heads.iteritems():
836 if branch in pubed and pubed[branch] == id:
837 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700838 if selected_branch and branch != selected_branch:
839 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700840
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800841 rb = self.GetUploadableBranch(branch)
842 if rb:
843 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844 return ready
845
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800846 def GetUploadableBranch(self, branch_name):
847 """Get a single uploadable branch, or None.
848 """
849 branch = self.GetBranch(branch_name)
850 base = branch.LocalMerge
851 if branch.LocalMerge:
852 rb = ReviewableBranch(self, branch, base)
853 if rb.commits:
854 return rb
855 return None
856
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700857 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700858 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700859 auto_topic=False,
860 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700861 """Uploads the named branch for code review.
862 """
863 if branch is None:
864 branch = self.CurrentBranch
865 if branch is None:
866 raise GitError('not currently on a branch')
867
868 branch = self.GetBranch(branch)
869 if not branch.LocalMerge:
870 raise GitError('branch %s does not track a remote' % branch.name)
871 if not branch.remote.review:
872 raise GitError('remote %s has no review url' % branch.remote.name)
873
874 dest_branch = branch.merge
875 if not dest_branch.startswith(R_HEADS):
876 dest_branch = R_HEADS + dest_branch
877
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800878 if not branch.remote.projectname:
879 branch.remote.projectname = self.name
880 branch.remote.Save()
881
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800882 url = branch.remote.ReviewUrl(self.UserEmail)
883 if url is None:
884 raise UploadError('review not configured')
885 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800886
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800887 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800888 rp = ['gerrit receive-pack']
889 for e in people[0]:
890 rp.append('--reviewer=%s' % sq(e))
891 for e in people[1]:
892 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800893 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700894
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800895 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800896
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800897 if dest_branch.startswith(R_HEADS):
898 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700899
900 upload_type = 'for'
901 if draft:
902 upload_type = 'drafts'
903
904 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
905 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800906 if auto_topic:
907 ref_spec = ref_spec + '/' + branch.name
908 cmd.append(ref_spec)
909
910 if GitCommand(self, cmd, bare = True).Wait() != 0:
911 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700912
913 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
914 self.bare_git.UpdateRef(R_PUB + branch.name,
915 R_HEADS + branch.name,
916 message = msg)
917
918
919## Sync ##
920
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700921 def Sync_NetworkHalf(self,
922 quiet=False,
923 is_new=None,
924 current_branch_only=False,
925 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926 """Perform only the network IO portion of the sync process.
927 Local working directory/branch state is not affected.
928 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700929 if is_new is None:
930 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200931 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932 self._InitGitDir()
933 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700934
935 if is_new:
936 alt = os.path.join(self.gitdir, 'objects/info/alternates')
937 try:
938 fd = open(alt, 'rb')
939 try:
940 alt_dir = fd.readline().rstrip()
941 finally:
942 fd.close()
943 except IOError:
944 alt_dir = None
945 else:
946 alt_dir = None
947
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700948 if clone_bundle \
949 and alt_dir is None \
950 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700951 is_new = False
952
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700953 if not current_branch_only:
954 if self.sync_c:
955 current_branch_only = True
956 elif not self.manifest._loaded:
957 # Manifest cannot check defaults until it syncs.
958 current_branch_only = False
959 elif self.manifest.default.sync_c:
960 current_branch_only = True
961
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700962 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
963 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800965
966 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800967 self._InitMRef()
968 else:
969 self._InitMirrorHead()
970 try:
971 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
972 except OSError:
973 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700974 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800975
976 def PostRepoUpgrade(self):
977 self._InitHooks()
978
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 def _CopyFiles(self):
980 for file in self.copyfiles:
981 file._Copy()
982
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700983 def GetRevisionId(self, all=None):
984 if self.revisionId:
985 return self.revisionId
986
987 rem = self.GetRemote(self.remote.name)
988 rev = rem.ToLocal(self.revisionExpr)
989
990 if all is not None and rev in all:
991 return all[rev]
992
993 try:
994 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
995 except GitError:
996 raise ManifestInvalidRevisionError(
997 'revision %s in %s not found' % (self.revisionExpr,
998 self.name))
999
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001000 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001 """Perform only the local IO portion of the sync process.
1002 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001004 all = self.bare_ref.all
1005 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001006 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001007
1008 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001009 head = self.work_git.GetHead()
1010 if head.startswith(R_HEADS):
1011 branch = head[len(R_HEADS):]
1012 try:
1013 head = all[head]
1014 except KeyError:
1015 head = None
1016 else:
1017 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001018
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001019 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001020 # Currently on a detached HEAD. The user is assumed to
1021 # not have any local modifications worth worrying about.
1022 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001023 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001024 syncbuf.fail(self, _PriorSyncFailedError())
1025 return
1026
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001027 if head == revid:
1028 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001029 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001030 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001031 if not syncbuf.detach_head:
1032 return
1033 else:
1034 lost = self._revlist(not_rev(revid), HEAD)
1035 if lost:
1036 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001037
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001039 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001040 except GitError, e:
1041 syncbuf.fail(self, e)
1042 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001043 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001044 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001045
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001046 if head == revid:
1047 # No changes; don't do anything further.
1048 #
1049 return
1050
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001052
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001053 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001055 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001057 syncbuf.info(self,
1058 "leaving %s; does not track upstream",
1059 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001061 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001062 except GitError, e:
1063 syncbuf.fail(self, e)
1064 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001066 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001068 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001069 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001071 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 if not_merged:
1073 if upstream_gain:
1074 # The user has published this branch and some of those
1075 # commits are not yet merged upstream. We do not want
1076 # to rewrite the published commits so we punt.
1077 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001078 syncbuf.fail(self,
1079 "branch %s is published (but not merged) and is now %d commits behind"
1080 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001081 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001082 elif pub == head:
1083 # All published commits are merged, and thus we are a
1084 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001085 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001086 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001087 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001088 self._CopyFiles()
1089 syncbuf.later1(self, _doff)
1090 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001091
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001092 # Examine the local commits not in the remote. Find the
1093 # last one attributed to this user, if any.
1094 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001095 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001096 last_mine = None
1097 cnt_mine = 0
1098 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001099 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001100 if committer_email == self.UserEmail:
1101 last_mine = commit_id
1102 cnt_mine += 1
1103
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001104 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001105 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106
1107 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001108 syncbuf.fail(self, _DirtyError())
1109 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001110
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001111 # If the upstream switched on us, warn the user.
1112 #
1113 if branch.merge != self.revisionExpr:
1114 if branch.merge and self.revisionExpr:
1115 syncbuf.info(self,
1116 'manifest switched %s...%s',
1117 branch.merge,
1118 self.revisionExpr)
1119 elif branch.merge:
1120 syncbuf.info(self,
1121 'manifest no longer tracks %s',
1122 branch.merge)
1123
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001124 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001125 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001126 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001127 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001128 syncbuf.info(self,
1129 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001130 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001131
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001132 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001133 if not ID_RE.match(self.revisionExpr):
1134 # in case of manifest sync the revisionExpr might be a SHA1
1135 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136 branch.Save()
1137
Mike Pontillod3153822012-02-28 11:53:24 -08001138 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001139 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001140 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001141 self._CopyFiles()
1142 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001143 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001144 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001145 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001146 self._CopyFiles()
1147 except GitError, e:
1148 syncbuf.fail(self, e)
1149 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001150 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001151 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001152 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001153 self._CopyFiles()
1154 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001155
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001156 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001157 # dest should already be an absolute path, but src is project relative
1158 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001159 abssrc = os.path.join(self.worktree, src)
1160 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001161
James W. Mills24c13082012-04-12 15:04:13 -05001162 def AddAnnotation(self, name, value, keep):
1163 self.annotations.append(_Annotation(name, value, keep))
1164
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001165 def DownloadPatchSet(self, change_id, patch_id):
1166 """Download a single patch set of a single change to FETCH_HEAD.
1167 """
1168 remote = self.GetRemote(self.remote.name)
1169
1170 cmd = ['fetch', remote.name]
1171 cmd.append('refs/changes/%2.2d/%d/%d' \
1172 % (change_id % 100, change_id, patch_id))
1173 cmd.extend(map(lambda x: str(x), remote.fetch))
1174 if GitCommand(self, cmd, bare=True).Wait() != 0:
1175 return None
1176 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001177 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001178 change_id,
1179 patch_id,
1180 self.bare_git.rev_parse('FETCH_HEAD'))
1181
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001182
1183## Branch Management ##
1184
1185 def StartBranch(self, name):
1186 """Create a new branch off the manifest's revision.
1187 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001188 head = self.work_git.GetHead()
1189 if head == (R_HEADS + name):
1190 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001191
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001192 all = self.bare_ref.all
1193 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001194 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001195 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001196 capture_stdout = True,
1197 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001198
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001199 branch = self.GetBranch(name)
1200 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001201 branch.merge = self.revisionExpr
1202 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001203
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001204 if head.startswith(R_HEADS):
1205 try:
1206 head = all[head]
1207 except KeyError:
1208 head = None
1209
1210 if revid and head and revid == head:
1211 ref = os.path.join(self.gitdir, R_HEADS + name)
1212 try:
1213 os.makedirs(os.path.dirname(ref))
1214 except OSError:
1215 pass
1216 _lwrite(ref, '%s\n' % revid)
1217 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1218 'ref: %s%s\n' % (R_HEADS, name))
1219 branch.Save()
1220 return True
1221
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001222 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001223 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001224 capture_stdout = True,
1225 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001226 branch.Save()
1227 return True
1228 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001229
Wink Saville02d79452009-04-10 13:01:24 -07001230 def CheckoutBranch(self, name):
1231 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001232
1233 Args:
1234 name: The name of the branch to checkout.
1235
1236 Returns:
1237 True if the checkout succeeded; False if it didn't; None if the branch
1238 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001239 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001240 rev = R_HEADS + name
1241 head = self.work_git.GetHead()
1242 if head == rev:
1243 # Already on the branch
1244 #
1245 return True
Wink Saville02d79452009-04-10 13:01:24 -07001246
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001247 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001248 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001249 revid = all[rev]
1250 except KeyError:
1251 # Branch does not exist in this project
1252 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001253 return None
Wink Saville02d79452009-04-10 13:01:24 -07001254
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001255 if head.startswith(R_HEADS):
1256 try:
1257 head = all[head]
1258 except KeyError:
1259 head = None
1260
1261 if head == revid:
1262 # Same revision; just update HEAD to point to the new
1263 # target branch, but otherwise take no other action.
1264 #
1265 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1266 'ref: %s%s\n' % (R_HEADS, name))
1267 return True
1268
1269 return GitCommand(self,
1270 ['checkout', name, '--'],
1271 capture_stdout = True,
1272 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001273
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001274 def AbandonBranch(self, name):
1275 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001276
1277 Args:
1278 name: The name of the branch to abandon.
1279
1280 Returns:
1281 True if the abandon succeeded; False if it didn't; None if the branch
1282 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001283 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001284 rev = R_HEADS + name
1285 all = self.bare_ref.all
1286 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001287 # Doesn't exist
1288 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001289
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001290 head = self.work_git.GetHead()
1291 if head == rev:
1292 # We can't destroy the branch while we are sitting
1293 # on it. Switch to a detached HEAD.
1294 #
1295 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001296
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001297 revid = self.GetRevisionId(all)
1298 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001299 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1300 '%s\n' % revid)
1301 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001302 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001303
1304 return GitCommand(self,
1305 ['branch', '-D', name],
1306 capture_stdout = True,
1307 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001308
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001309 def PruneHeads(self):
1310 """Prune any topic branches already merged into upstream.
1311 """
1312 cb = self.CurrentBranch
1313 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001314 left = self._allrefs
1315 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316 if name.startswith(R_HEADS):
1317 name = name[len(R_HEADS):]
1318 if cb is None or name != cb:
1319 kill.append(name)
1320
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001321 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 if cb is not None \
1323 and not self._revlist(HEAD + '...' + rev) \
1324 and not self.IsDirty(consider_untracked = False):
1325 self.work_git.DetachHead(HEAD)
1326 kill.append(cb)
1327
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001329 old = self.bare_git.GetHead()
1330 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001331 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1332
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001333 try:
1334 self.bare_git.DetachHead(rev)
1335
1336 b = ['branch', '-d']
1337 b.extend(kill)
1338 b = GitCommand(self, b, bare=True,
1339 capture_stdout=True,
1340 capture_stderr=True)
1341 b.Wait()
1342 finally:
1343 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001344 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001345
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001346 for branch in kill:
1347 if (R_HEADS + branch) not in left:
1348 self.CleanPublishedCache()
1349 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350
1351 if cb and cb not in kill:
1352 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001353 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001354
1355 kept = []
1356 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001357 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358 branch = self.GetBranch(branch)
1359 base = branch.LocalMerge
1360 if not base:
1361 base = rev
1362 kept.append(ReviewableBranch(self, branch, base))
1363 return kept
1364
1365
1366## Direct Git Commands ##
1367
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001368 def _RemoteFetch(self, name=None,
1369 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001370 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001371 quiet=False,
1372 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001373
1374 is_sha1 = False
1375 tag_name = None
1376
1377 if current_branch_only:
1378 if ID_RE.match(self.revisionExpr) is not None:
1379 is_sha1 = True
1380 elif self.revisionExpr.startswith(R_TAGS):
1381 # this is a tag and its sha1 value should never change
1382 tag_name = self.revisionExpr[len(R_TAGS):]
1383
1384 if is_sha1 or tag_name is not None:
1385 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001386 # if revision (sha or tag) is not present then following function
1387 # throws an error.
1388 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001389 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001390 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001391 # There is no such persistent revision. We have to fetch it.
1392 pass
1393
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394 if not name:
1395 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001396
1397 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001398 remote = self.GetRemote(name)
1399 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001400 ssh_proxy = True
1401
Shawn O. Pearce88443382010-10-08 10:02:09 +02001402 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001403 if alt_dir and 'objects' == os.path.basename(alt_dir):
1404 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001405 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1406 remote = self.GetRemote(name)
1407
1408 all = self.bare_ref.all
1409 ids = set(all.values())
1410 tmp = set()
1411
1412 for r, id in GitRefs(ref_dir).all.iteritems():
1413 if r not in all:
1414 if r.startswith(R_TAGS) or remote.WritesTo(r):
1415 all[r] = id
1416 ids.add(id)
1417 continue
1418
1419 if id in ids:
1420 continue
1421
1422 r = 'refs/_alt/%s' % id
1423 all[r] = id
1424 ids.add(id)
1425 tmp.add(r)
1426
1427 ref_names = list(all.keys())
1428 ref_names.sort()
1429
1430 tmp_packed = ''
1431 old_packed = ''
1432
1433 for r in ref_names:
1434 line = '%s %s\n' % (all[r], r)
1435 tmp_packed += line
1436 if r not in tmp:
1437 old_packed += line
1438
1439 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001440 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001441 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001442
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001443 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001444
1445 # The --depth option only affects the initial fetch; after that we'll do
1446 # full fetches of changes.
1447 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1448 if depth and initial:
1449 cmd.append('--depth=%s' % depth)
1450
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001451 if quiet:
1452 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001453 if not self.worktree:
1454 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001455 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001456
1457 if not current_branch_only or is_sha1:
1458 # Fetch whole repo
1459 cmd.append('--tags')
1460 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1461 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001462 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001463 cmd.append(tag_name)
1464 else:
1465 branch = self.revisionExpr
1466 if branch.startswith(R_HEADS):
1467 branch = branch[len(R_HEADS):]
1468 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001469
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001470 ok = False
1471 for i in range(2):
1472 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1473 ok = True
1474 break
1475 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001476
1477 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001478 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001479 if old_packed != '':
1480 _lwrite(packed_refs, old_packed)
1481 else:
1482 os.remove(packed_refs)
1483 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001484 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001485
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001486 def _ApplyCloneBundle(self, initial=False, quiet=False):
1487 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1488 return False
1489
1490 remote = self.GetRemote(self.remote.name)
1491 bundle_url = remote.url + '/clone.bundle'
1492 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001493 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1494 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001495 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1496 return False
1497
1498 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1499 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1500
1501 exist_dst = os.path.exists(bundle_dst)
1502 exist_tmp = os.path.exists(bundle_tmp)
1503
1504 if not initial and not exist_dst and not exist_tmp:
1505 return False
1506
1507 if not exist_dst:
1508 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1509 if not exist_dst:
1510 return False
1511
1512 cmd = ['fetch']
1513 if quiet:
1514 cmd.append('--quiet')
1515 if not self.worktree:
1516 cmd.append('--update-head-ok')
1517 cmd.append(bundle_dst)
1518 for f in remote.fetch:
1519 cmd.append(str(f))
1520 cmd.append('refs/tags/*:refs/tags/*')
1521
1522 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001523 if os.path.exists(bundle_dst):
1524 os.remove(bundle_dst)
1525 if os.path.exists(bundle_tmp):
1526 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001527 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001528
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001529 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001530 if os.path.exists(dstPath):
1531 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001532
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001533 cmd = ['curl', '--output', tmpPath, '--netrc', '--location']
1534 if quiet:
1535 cmd += ['--silent']
1536 if os.path.exists(tmpPath):
1537 size = os.stat(tmpPath).st_size
1538 if size >= 1024:
1539 cmd += ['--continue-at', '%d' % (size,)]
1540 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001541 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001542 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1543 cmd += ['--proxy', os.environ['http_proxy']]
1544 cmd += [srcUrl]
1545
1546 if IsTrace():
1547 Trace('%s', ' '.join(cmd))
1548 try:
1549 proc = subprocess.Popen(cmd)
1550 except OSError:
1551 return False
1552
1553 ok = proc.wait() == 0
1554 if os.path.exists(tmpPath):
1555 if ok and os.stat(tmpPath).st_size > 16:
1556 os.rename(tmpPath, dstPath)
1557 return True
1558 else:
1559 os.remove(tmpPath)
1560 return False
1561 else:
1562 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001563
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001564 def _Checkout(self, rev, quiet=False):
1565 cmd = ['checkout']
1566 if quiet:
1567 cmd.append('-q')
1568 cmd.append(rev)
1569 cmd.append('--')
1570 if GitCommand(self, cmd).Wait() != 0:
1571 if self._allrefs:
1572 raise GitError('%s checkout %s ' % (self.name, rev))
1573
Pierre Tardye5a21222011-03-24 16:28:18 +01001574 def _CherryPick(self, rev, quiet=False):
1575 cmd = ['cherry-pick']
1576 cmd.append(rev)
1577 cmd.append('--')
1578 if GitCommand(self, cmd).Wait() != 0:
1579 if self._allrefs:
1580 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1581
Erwan Mahea94f1622011-08-19 13:56:09 +02001582 def _Revert(self, rev, quiet=False):
1583 cmd = ['revert']
1584 cmd.append('--no-edit')
1585 cmd.append(rev)
1586 cmd.append('--')
1587 if GitCommand(self, cmd).Wait() != 0:
1588 if self._allrefs:
1589 raise GitError('%s revert %s ' % (self.name, rev))
1590
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001591 def _ResetHard(self, rev, quiet=True):
1592 cmd = ['reset', '--hard']
1593 if quiet:
1594 cmd.append('-q')
1595 cmd.append(rev)
1596 if GitCommand(self, cmd).Wait() != 0:
1597 raise GitError('%s reset --hard %s ' % (self.name, rev))
1598
1599 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001600 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001601 if onto is not None:
1602 cmd.extend(['--onto', onto])
1603 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001604 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001605 raise GitError('%s rebase %s ' % (self.name, upstream))
1606
Pierre Tardy3d125942012-05-04 12:18:12 +02001607 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001608 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001609 if ffonly:
1610 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001611 if GitCommand(self, cmd).Wait() != 0:
1612 raise GitError('%s merge %s ' % (self.name, head))
1613
1614 def _InitGitDir(self):
1615 if not os.path.exists(self.gitdir):
1616 os.makedirs(self.gitdir)
1617 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001618
Shawn O. Pearce88443382010-10-08 10:02:09 +02001619 mp = self.manifest.manifestProject
1620 ref_dir = mp.config.GetString('repo.reference')
1621
1622 if ref_dir:
1623 mirror_git = os.path.join(ref_dir, self.name + '.git')
1624 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1625 self.relpath + '.git')
1626
1627 if os.path.exists(mirror_git):
1628 ref_dir = mirror_git
1629
1630 elif os.path.exists(repo_git):
1631 ref_dir = repo_git
1632
1633 else:
1634 ref_dir = None
1635
1636 if ref_dir:
1637 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1638 os.path.join(ref_dir, 'objects') + '\n')
1639
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001640 if self.manifest.IsMirror:
1641 self.config.SetString('core.bare', 'true')
1642 else:
1643 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001644
1645 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001646 try:
1647 to_rm = os.listdir(hooks)
1648 except OSError:
1649 to_rm = []
1650 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001651 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001652 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001653
1654 m = self.manifest.manifestProject.config
1655 for key in ['user.name', 'user.email']:
1656 if m.Has(key, include_defaults = False):
1657 self.config.SetString(key, m.GetString(key))
1658
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001659 def _InitHooks(self):
1660 hooks = self._gitdir_path('hooks')
1661 if not os.path.exists(hooks):
1662 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001663 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001664 name = os.path.basename(stock_hook)
1665
Victor Boivie65e0f352011-04-18 11:23:29 +02001666 if name in ('commit-msg',) and not self.remote.review \
1667 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001668 # Don't install a Gerrit Code Review hook if this
1669 # project does not appear to use it for reviews.
1670 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001671 # Since the manifest project is one of those, but also
1672 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001673 continue
1674
1675 dst = os.path.join(hooks, name)
1676 if os.path.islink(dst):
1677 continue
1678 if os.path.exists(dst):
1679 if filecmp.cmp(stock_hook, dst, shallow=False):
1680 os.remove(dst)
1681 else:
1682 _error("%s: Not replacing %s hook", self.relpath, name)
1683 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001684 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001685 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001686 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001687 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001688 raise GitError('filesystem must support symlinks')
1689 else:
1690 raise
1691
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001692 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001693 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001694 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001695 remote.url = self.remote.url
1696 remote.review = self.remote.review
1697 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001698
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001699 if self.worktree:
1700 remote.ResetFetch(mirror=False)
1701 else:
1702 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001703 remote.Save()
1704
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705 def _InitMRef(self):
1706 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001707 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001708
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001709 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001710 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001711
1712 def _InitAnyMRef(self, ref):
1713 cur = self.bare_ref.symref(ref)
1714
1715 if self.revisionId:
1716 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1717 msg = 'manifest set to %s' % self.revisionId
1718 dst = self.revisionId + '^0'
1719 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1720 else:
1721 remote = self.GetRemote(self.remote.name)
1722 dst = remote.ToLocal(self.revisionExpr)
1723 if cur != dst:
1724 msg = 'manifest set to %s' % self.revisionExpr
1725 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001726
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001727 def _InitWorkTree(self):
1728 dotgit = os.path.join(self.worktree, '.git')
1729 if not os.path.exists(dotgit):
1730 os.makedirs(dotgit)
1731
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001732 for name in ['config',
1733 'description',
1734 'hooks',
1735 'info',
1736 'logs',
1737 'objects',
1738 'packed-refs',
1739 'refs',
1740 'rr-cache',
1741 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001742 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001743 src = os.path.join(self.gitdir, name)
1744 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001745 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001746 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001747 else:
1748 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001749 except OSError, e:
1750 if e.errno == errno.EPERM:
1751 raise GitError('filesystem must support symlinks')
1752 else:
1753 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001754
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001755 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001756
1757 cmd = ['read-tree', '--reset', '-u']
1758 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001759 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001760 if GitCommand(self, cmd).Wait() != 0:
1761 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001762
1763 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1764 if not os.path.exists(rr_cache):
1765 os.makedirs(rr_cache)
1766
Shawn O. Pearce93609662009-04-21 10:50:33 -07001767 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768
1769 def _gitdir_path(self, path):
1770 return os.path.join(self.gitdir, path)
1771
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001772 def _revlist(self, *args, **kw):
1773 a = []
1774 a.extend(args)
1775 a.append('--')
1776 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001777
1778 @property
1779 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001780 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001781
1782 class _GitGetByExec(object):
1783 def __init__(self, project, bare):
1784 self._project = project
1785 self._bare = bare
1786
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001787 def LsOthers(self):
1788 p = GitCommand(self._project,
1789 ['ls-files',
1790 '-z',
1791 '--others',
1792 '--exclude-standard'],
1793 bare = False,
1794 capture_stdout = True,
1795 capture_stderr = True)
1796 if p.Wait() == 0:
1797 out = p.stdout
1798 if out:
1799 return out[:-1].split("\0")
1800 return []
1801
1802 def DiffZ(self, name, *args):
1803 cmd = [name]
1804 cmd.append('-z')
1805 cmd.extend(args)
1806 p = GitCommand(self._project,
1807 cmd,
1808 bare = False,
1809 capture_stdout = True,
1810 capture_stderr = True)
1811 try:
1812 out = p.process.stdout.read()
1813 r = {}
1814 if out:
1815 out = iter(out[:-1].split('\0'))
1816 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001817 try:
1818 info = out.next()
1819 path = out.next()
1820 except StopIteration:
1821 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001822
1823 class _Info(object):
1824 def __init__(self, path, omode, nmode, oid, nid, state):
1825 self.path = path
1826 self.src_path = None
1827 self.old_mode = omode
1828 self.new_mode = nmode
1829 self.old_id = oid
1830 self.new_id = nid
1831
1832 if len(state) == 1:
1833 self.status = state
1834 self.level = None
1835 else:
1836 self.status = state[:1]
1837 self.level = state[1:]
1838 while self.level.startswith('0'):
1839 self.level = self.level[1:]
1840
1841 info = info[1:].split(' ')
1842 info =_Info(path, *info)
1843 if info.status in ('R', 'C'):
1844 info.src_path = info.path
1845 info.path = out.next()
1846 r[info.path] = info
1847 return r
1848 finally:
1849 p.Wait()
1850
1851 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001852 if self._bare:
1853 path = os.path.join(self._project.gitdir, HEAD)
1854 else:
1855 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001856 fd = open(path, 'rb')
1857 try:
1858 line = fd.read()
1859 finally:
1860 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001861 if line.startswith('ref: '):
1862 return line[5:-1]
1863 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001864
1865 def SetHead(self, ref, message=None):
1866 cmdv = []
1867 if message is not None:
1868 cmdv.extend(['-m', message])
1869 cmdv.append(HEAD)
1870 cmdv.append(ref)
1871 self.symbolic_ref(*cmdv)
1872
1873 def DetachHead(self, new, message=None):
1874 cmdv = ['--no-deref']
1875 if message is not None:
1876 cmdv.extend(['-m', message])
1877 cmdv.append(HEAD)
1878 cmdv.append(new)
1879 self.update_ref(*cmdv)
1880
1881 def UpdateRef(self, name, new, old=None,
1882 message=None,
1883 detach=False):
1884 cmdv = []
1885 if message is not None:
1886 cmdv.extend(['-m', message])
1887 if detach:
1888 cmdv.append('--no-deref')
1889 cmdv.append(name)
1890 cmdv.append(new)
1891 if old is not None:
1892 cmdv.append(old)
1893 self.update_ref(*cmdv)
1894
1895 def DeleteRef(self, name, old=None):
1896 if not old:
1897 old = self.rev_parse(name)
1898 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001899 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001900
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001901 def rev_list(self, *args, **kw):
1902 if 'format' in kw:
1903 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1904 else:
1905 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001906 cmdv.extend(args)
1907 p = GitCommand(self._project,
1908 cmdv,
1909 bare = self._bare,
1910 capture_stdout = True,
1911 capture_stderr = True)
1912 r = []
1913 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001914 if line[-1] == '\n':
1915 line = line[:-1]
1916 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001917 if p.Wait() != 0:
1918 raise GitError('%s rev-list %s: %s' % (
1919 self._project.name,
1920 str(args),
1921 p.stderr))
1922 return r
1923
1924 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001925 """Allow arbitrary git commands using pythonic syntax.
1926
1927 This allows you to do things like:
1928 git_obj.rev_parse('HEAD')
1929
1930 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1931 run. We'll replace the '_' with a '-' and try to run a git command.
1932 Any other arguments will be passed to the git command.
1933
1934 Args:
1935 name: The name of the git command to call. Any '_' characters will
1936 be replaced with '-'.
1937
1938 Returns:
1939 A callable object that will try to call git with the named command.
1940 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001941 name = name.replace('_', '-')
1942 def runner(*args):
1943 cmdv = [name]
1944 cmdv.extend(args)
1945 p = GitCommand(self._project,
1946 cmdv,
1947 bare = self._bare,
1948 capture_stdout = True,
1949 capture_stderr = True)
1950 if p.Wait() != 0:
1951 raise GitError('%s %s: %s' % (
1952 self._project.name,
1953 name,
1954 p.stderr))
1955 r = p.stdout
1956 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1957 return r[:-1]
1958 return r
1959 return runner
1960
1961
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001962class _PriorSyncFailedError(Exception):
1963 def __str__(self):
1964 return 'prior sync failed; rebase still in progress'
1965
1966class _DirtyError(Exception):
1967 def __str__(self):
1968 return 'contains uncommitted changes'
1969
1970class _InfoMessage(object):
1971 def __init__(self, project, text):
1972 self.project = project
1973 self.text = text
1974
1975 def Print(self, syncbuf):
1976 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1977 syncbuf.out.nl()
1978
1979class _Failure(object):
1980 def __init__(self, project, why):
1981 self.project = project
1982 self.why = why
1983
1984 def Print(self, syncbuf):
1985 syncbuf.out.fail('error: %s/: %s',
1986 self.project.relpath,
1987 str(self.why))
1988 syncbuf.out.nl()
1989
1990class _Later(object):
1991 def __init__(self, project, action):
1992 self.project = project
1993 self.action = action
1994
1995 def Run(self, syncbuf):
1996 out = syncbuf.out
1997 out.project('project %s/', self.project.relpath)
1998 out.nl()
1999 try:
2000 self.action()
2001 out.nl()
2002 return True
2003 except GitError, e:
2004 out.nl()
2005 return False
2006
2007class _SyncColoring(Coloring):
2008 def __init__(self, config):
2009 Coloring.__init__(self, config, 'reposync')
2010 self.project = self.printer('header', attr = 'bold')
2011 self.info = self.printer('info')
2012 self.fail = self.printer('fail', fg='red')
2013
2014class SyncBuffer(object):
2015 def __init__(self, config, detach_head=False):
2016 self._messages = []
2017 self._failures = []
2018 self._later_queue1 = []
2019 self._later_queue2 = []
2020
2021 self.out = _SyncColoring(config)
2022 self.out.redirect(sys.stderr)
2023
2024 self.detach_head = detach_head
2025 self.clean = True
2026
2027 def info(self, project, fmt, *args):
2028 self._messages.append(_InfoMessage(project, fmt % args))
2029
2030 def fail(self, project, err=None):
2031 self._failures.append(_Failure(project, err))
2032 self.clean = False
2033
2034 def later1(self, project, what):
2035 self._later_queue1.append(_Later(project, what))
2036
2037 def later2(self, project, what):
2038 self._later_queue2.append(_Later(project, what))
2039
2040 def Finish(self):
2041 self._PrintMessages()
2042 self._RunLater()
2043 self._PrintMessages()
2044 return self.clean
2045
2046 def _RunLater(self):
2047 for q in ['_later_queue1', '_later_queue2']:
2048 if not self._RunQueue(q):
2049 return
2050
2051 def _RunQueue(self, queue):
2052 for m in getattr(self, queue):
2053 if not m.Run(self):
2054 self.clean = False
2055 return False
2056 setattr(self, queue, [])
2057 return True
2058
2059 def _PrintMessages(self):
2060 for m in self._messages:
2061 m.Print(self)
2062 for m in self._failures:
2063 m.Print(self)
2064
2065 self._messages = []
2066 self._failures = []
2067
2068
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002069class MetaProject(Project):
2070 """A special project housed under .repo.
2071 """
2072 def __init__(self, manifest, name, gitdir, worktree):
2073 repodir = manifest.repodir
2074 Project.__init__(self,
2075 manifest = manifest,
2076 name = name,
2077 gitdir = gitdir,
2078 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002079 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002080 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002081 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002082 revisionId = None,
2083 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002084
2085 def PreSync(self):
2086 if self.Exists:
2087 cb = self.CurrentBranch
2088 if cb:
2089 base = self.GetBranch(cb).merge
2090 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002091 self.revisionExpr = base
2092 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002093
Florian Vallee5d016502012-06-07 17:19:26 +02002094 def MetaBranchSwitch(self, target):
2095 """ Prepare MetaProject for manifest branch switch
2096 """
2097
2098 # detach and delete manifest branch, allowing a new
2099 # branch to take over
2100 syncbuf = SyncBuffer(self.config, detach_head = True)
2101 self.Sync_LocalHalf(syncbuf)
2102 syncbuf.Finish()
2103
2104 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002105 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002106 capture_stdout = True,
2107 capture_stderr = True).Wait() == 0
2108
2109
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002110 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002111 def LastFetch(self):
2112 try:
2113 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2114 return os.path.getmtime(fh)
2115 except OSError:
2116 return 0
2117
2118 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002119 def HasChanges(self):
2120 """Has the remote received new commits not yet checked out?
2121 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002122 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002123 return False
2124
2125 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002126 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002127 head = self.work_git.GetHead()
2128 if head.startswith(R_HEADS):
2129 try:
2130 head = all[head]
2131 except KeyError:
2132 head = None
2133
2134 if revid == head:
2135 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002136 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002137 return True
2138 return False