blob: 06baccb56f26ee8b81c5abe4f8d594a8512be69a [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
David Pursehousee15c65a2012-08-22 10:46:11 +090030from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080031from error import ManifestInvalidRevisionError
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070032from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070033
Shawn O. Pearced237b692009-04-17 18:49:50 -070034from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070035
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070036def _lwrite(path, content):
37 lock = '%s.lock' % path
38
39 fd = open(lock, 'wb')
40 try:
41 fd.write(content)
42 finally:
43 fd.close()
44
45 try:
46 os.rename(lock, path)
47 except OSError:
48 os.remove(lock)
49 raise
50
Shawn O. Pearce48244782009-04-16 08:25:57 -070051def _error(fmt, *args):
52 msg = fmt % args
53 print >>sys.stderr, 'error: %s' % msg
54
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070055def not_rev(r):
56 return '^' + r
57
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080058def sq(r):
59 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080060
Doug Anderson8ced8642011-01-10 14:16:30 -080061_project_hook_list = None
62def _ProjectHooks():
63 """List the hooks present in the 'hooks' directory.
64
65 These hooks are project hooks and are copied to the '.git/hooks' directory
66 of all subprojects.
67
68 This function caches the list of hooks (based on the contents of the
69 'repo/hooks' directory) on the first call.
70
71 Returns:
72 A list of absolute paths to all of the files in the hooks directory.
73 """
74 global _project_hook_list
75 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080076 d = os.path.abspath(os.path.dirname(__file__))
77 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080078 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
79 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080080
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080081
Shawn O. Pearce632768b2008-10-23 11:58:52 -070082class DownloadedChange(object):
83 _commit_cache = None
84
85 def __init__(self, project, base, change_id, ps_id, commit):
86 self.project = project
87 self.base = base
88 self.change_id = change_id
89 self.ps_id = ps_id
90 self.commit = commit
91
92 @property
93 def commits(self):
94 if self._commit_cache is None:
95 self._commit_cache = self.project.bare_git.rev_list(
96 '--abbrev=8',
97 '--abbrev-commit',
98 '--pretty=oneline',
99 '--reverse',
100 '--date-order',
101 not_rev(self.base),
102 self.commit,
103 '--')
104 return self._commit_cache
105
106
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700107class ReviewableBranch(object):
108 _commit_cache = None
109
110 def __init__(self, project, branch, base):
111 self.project = project
112 self.branch = branch
113 self.base = base
114
115 @property
116 def name(self):
117 return self.branch.name
118
119 @property
120 def commits(self):
121 if self._commit_cache is None:
122 self._commit_cache = self.project.bare_git.rev_list(
123 '--abbrev=8',
124 '--abbrev-commit',
125 '--pretty=oneline',
126 '--reverse',
127 '--date-order',
128 not_rev(self.base),
129 R_HEADS + self.name,
130 '--')
131 return self._commit_cache
132
133 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800134 def unabbrev_commits(self):
135 r = dict()
136 for commit in self.project.bare_git.rev_list(
137 not_rev(self.base),
138 R_HEADS + self.name,
139 '--'):
140 r[commit[0:8]] = commit
141 return r
142
143 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700144 def date(self):
145 return self.project.bare_git.log(
146 '--pretty=format:%cd',
147 '-n', '1',
148 R_HEADS + self.name,
149 '--')
150
Brian Harring435370c2012-07-28 15:37:04 -0700151 def UploadForReview(self, people, auto_topic=False, draft=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800152 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700153 people,
Brian Harring435370c2012-07-28 15:37:04 -0700154 auto_topic=auto_topic,
155 draft=draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700156
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700157 def GetPublishedRefs(self):
158 refs = {}
159 output = self.project.bare_git.ls_remote(
160 self.branch.remote.SshReviewUrl(self.project.UserEmail),
161 'refs/changes/*')
162 for line in output.split('\n'):
163 try:
164 (sha, ref) = line.split()
165 refs[sha] = ref
166 except ValueError:
167 pass
168
169 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170
171class StatusColoring(Coloring):
172 def __init__(self, config):
173 Coloring.__init__(self, config, 'status')
174 self.project = self.printer('header', attr = 'bold')
175 self.branch = self.printer('header', attr = 'bold')
176 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700177 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178
179 self.added = self.printer('added', fg = 'green')
180 self.changed = self.printer('changed', fg = 'red')
181 self.untracked = self.printer('untracked', fg = 'red')
182
183
184class DiffColoring(Coloring):
185 def __init__(self, config):
186 Coloring.__init__(self, config, 'diff')
187 self.project = self.printer('header', attr = 'bold')
188
James W. Mills24c13082012-04-12 15:04:13 -0500189class _Annotation:
190 def __init__(self, name, value, keep):
191 self.name = name
192 self.value = value
193 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700194
195class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800196 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197 self.src = src
198 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800199 self.abs_src = abssrc
200 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700201
202 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800203 src = self.abs_src
204 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205 # copy file if it does not exist or is out of date
206 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
207 try:
208 # remove existing file first, since it might be read-only
209 if os.path.exists(dest):
210 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400211 else:
212 dir = os.path.dirname(dest)
213 if not os.path.isdir(dir):
214 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700215 shutil.copy(src, dest)
216 # make the file read-only
217 mode = os.stat(dest)[stat.ST_MODE]
218 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
219 os.chmod(dest, mode)
220 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700221 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700223class RemoteSpec(object):
224 def __init__(self,
225 name,
226 url = None,
227 review = None):
228 self.name = name
229 self.url = url
230 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231
Doug Anderson37282b42011-03-04 11:54:18 -0800232class RepoHook(object):
233 """A RepoHook contains information about a script to run as a hook.
234
235 Hooks are used to run a python script before running an upload (for instance,
236 to run presubmit checks). Eventually, we may have hooks for other actions.
237
238 This shouldn't be confused with files in the 'repo/hooks' directory. Those
239 files are copied into each '.git/hooks' folder for each project. Repo-level
240 hooks are associated instead with repo actions.
241
242 Hooks are always python. When a hook is run, we will load the hook into the
243 interpreter and execute its main() function.
244 """
245 def __init__(self,
246 hook_type,
247 hooks_project,
248 topdir,
249 abort_if_user_denies=False):
250 """RepoHook constructor.
251
252 Params:
253 hook_type: A string representing the type of hook. This is also used
254 to figure out the name of the file containing the hook. For
255 example: 'pre-upload'.
256 hooks_project: The project containing the repo hooks. If you have a
257 manifest, this is manifest.repo_hooks_project. OK if this is None,
258 which will make the hook a no-op.
259 topdir: Repo's top directory (the one containing the .repo directory).
260 Scripts will run with CWD as this directory. If you have a manifest,
261 this is manifest.topdir
262 abort_if_user_denies: If True, we'll throw a HookError() if the user
263 doesn't allow us to run the hook.
264 """
265 self._hook_type = hook_type
266 self._hooks_project = hooks_project
267 self._topdir = topdir
268 self._abort_if_user_denies = abort_if_user_denies
269
270 # Store the full path to the script for convenience.
271 if self._hooks_project:
272 self._script_fullpath = os.path.join(self._hooks_project.worktree,
273 self._hook_type + '.py')
274 else:
275 self._script_fullpath = None
276
277 def _GetHash(self):
278 """Return a hash of the contents of the hooks directory.
279
280 We'll just use git to do this. This hash has the property that if anything
281 changes in the directory we will return a different has.
282
283 SECURITY CONSIDERATION:
284 This hash only represents the contents of files in the hook directory, not
285 any other files imported or called by hooks. Changes to imported files
286 can change the script behavior without affecting the hash.
287
288 Returns:
289 A string representing the hash. This will always be ASCII so that it can
290 be printed to the user easily.
291 """
292 assert self._hooks_project, "Must have hooks to calculate their hash."
293
294 # We will use the work_git object rather than just calling GetRevisionId().
295 # That gives us a hash of the latest checked in version of the files that
296 # the user will actually be executing. Specifically, GetRevisionId()
297 # doesn't appear to change even if a user checks out a different version
298 # of the hooks repo (via git checkout) nor if a user commits their own revs.
299 #
300 # NOTE: Local (non-committed) changes will not be factored into this hash.
301 # I think this is OK, since we're really only worried about warning the user
302 # about upstream changes.
303 return self._hooks_project.work_git.rev_parse('HEAD')
304
305 def _GetMustVerb(self):
306 """Return 'must' if the hook is required; 'should' if not."""
307 if self._abort_if_user_denies:
308 return 'must'
309 else:
310 return 'should'
311
312 def _CheckForHookApproval(self):
313 """Check to see whether this hook has been approved.
314
315 We'll look at the hash of all of the hooks. If this matches the hash that
316 the user last approved, we're done. If it doesn't, we'll ask the user
317 about approval.
318
319 Note that we ask permission for each individual hook even though we use
320 the hash of all hooks when detecting changes. We'd like the user to be
321 able to approve / deny each hook individually. We only use the hash of all
322 hooks because there is no other easy way to detect changes to local imports.
323
324 Returns:
325 True if this hook is approved to run; False otherwise.
326
327 Raises:
328 HookError: Raised if the user doesn't approve and abort_if_user_denies
329 was passed to the consturctor.
330 """
331 hooks_dir = self._hooks_project.worktree
332 hooks_config = self._hooks_project.config
333 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
334
335 # Get the last hash that the user approved for this hook; may be None.
336 old_hash = hooks_config.GetString(git_approval_key)
337
338 # Get the current hash so we can tell if scripts changed since approval.
339 new_hash = self._GetHash()
340
341 if old_hash is not None:
342 # User previously approved hook and asked not to be prompted again.
343 if new_hash == old_hash:
344 # Approval matched. We're done.
345 return True
346 else:
347 # Give the user a reason why we're prompting, since they last told
348 # us to "never ask again".
349 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
350 self._hook_type)
351 else:
352 prompt = ''
353
354 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
355 if sys.stdout.isatty():
356 prompt += ('Repo %s run the script:\n'
357 ' %s\n'
358 '\n'
359 'Do you want to allow this script to run '
360 '(yes/yes-never-ask-again/NO)? ') % (
361 self._GetMustVerb(), self._script_fullpath)
362 response = raw_input(prompt).lower()
363 print
364
365 # User is doing a one-time approval.
366 if response in ('y', 'yes'):
367 return True
368 elif response == 'yes-never-ask-again':
369 hooks_config.SetString(git_approval_key, new_hash)
370 return True
371
372 # For anything else, we'll assume no approval.
373 if self._abort_if_user_denies:
374 raise HookError('You must allow the %s hook or use --no-verify.' %
375 self._hook_type)
376
377 return False
378
379 def _ExecuteHook(self, **kwargs):
380 """Actually execute the given hook.
381
382 This will run the hook's 'main' function in our python interpreter.
383
384 Args:
385 kwargs: Keyword arguments to pass to the hook. These are often specific
386 to the hook type. For instance, pre-upload hooks will contain
387 a project_list.
388 """
389 # Keep sys.path and CWD stashed away so that we can always restore them
390 # upon function exit.
391 orig_path = os.getcwd()
392 orig_syspath = sys.path
393
394 try:
395 # Always run hooks with CWD as topdir.
396 os.chdir(self._topdir)
397
398 # Put the hook dir as the first item of sys.path so hooks can do
399 # relative imports. We want to replace the repo dir as [0] so
400 # hooks can't import repo files.
401 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
402
403 # Exec, storing global context in the context dict. We catch exceptions
404 # and convert to a HookError w/ just the failing traceback.
405 context = {}
406 try:
407 execfile(self._script_fullpath, context)
408 except Exception:
409 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
410 traceback.format_exc(), self._hook_type))
411
412 # Running the script should have defined a main() function.
413 if 'main' not in context:
414 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
415
416
417 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
418 # We don't actually want hooks to define their main with this argument--
419 # it's there to remind them that their hook should always take **kwargs.
420 # For instance, a pre-upload hook should be defined like:
421 # def main(project_list, **kwargs):
422 #
423 # This allows us to later expand the API without breaking old hooks.
424 kwargs = kwargs.copy()
425 kwargs['hook_should_take_kwargs'] = True
426
427 # Call the main function in the hook. If the hook should cause the
428 # build to fail, it will raise an Exception. We'll catch that convert
429 # to a HookError w/ just the failing traceback.
430 try:
431 context['main'](**kwargs)
432 except Exception:
433 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
434 'above.' % (
435 traceback.format_exc(), self._hook_type))
436 finally:
437 # Restore sys.path and CWD.
438 sys.path = orig_syspath
439 os.chdir(orig_path)
440
441 def Run(self, user_allows_all_hooks, **kwargs):
442 """Run the hook.
443
444 If the hook doesn't exist (because there is no hooks project or because
445 this particular hook is not enabled), this is a no-op.
446
447 Args:
448 user_allows_all_hooks: If True, we will never prompt about running the
449 hook--we'll just assume it's OK to run it.
450 kwargs: Keyword arguments to pass to the hook. These are often specific
451 to the hook type. For instance, pre-upload hooks will contain
452 a project_list.
453
454 Raises:
455 HookError: If there was a problem finding the hook or the user declined
456 to run a required hook (from _CheckForHookApproval).
457 """
458 # No-op if there is no hooks project or if hook is disabled.
459 if ((not self._hooks_project) or
460 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
461 return
462
463 # Bail with a nice error if we can't find the hook.
464 if not os.path.isfile(self._script_fullpath):
465 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
466
467 # Make sure the user is OK with running the hook.
468 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
469 return
470
471 # Run the hook with the same version of python we're using.
472 self._ExecuteHook(**kwargs)
473
474
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700475class Project(object):
476 def __init__(self,
477 manifest,
478 name,
479 remote,
480 gitdir,
481 worktree,
482 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700483 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800484 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700485 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700486 groups = None,
487 sync_c = False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700488 self.manifest = manifest
489 self.name = name
490 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800491 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800492 if worktree:
493 self.worktree = worktree.replace('\\', '/')
494 else:
495 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700496 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700497 self.revisionExpr = revisionExpr
498
499 if revisionId is None \
500 and revisionExpr \
501 and IsId(revisionExpr):
502 self.revisionId = revisionExpr
503 else:
504 self.revisionId = revisionId
505
Mike Pontillod3153822012-02-28 11:53:24 -0800506 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700507 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700508 self.sync_c = sync_c
Mike Pontillod3153822012-02-28 11:53:24 -0800509
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700510 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700511 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500512 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700513 self.config = GitConfig.ForRepository(
514 gitdir = self.gitdir,
515 defaults = self.manifest.globalConfig)
516
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800517 if self.worktree:
518 self.work_git = self._GitGetByExec(self, bare=False)
519 else:
520 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700521 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700522 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700523
Doug Anderson37282b42011-03-04 11:54:18 -0800524 # This will be filled in if a project is later identified to be the
525 # project containing repo hooks.
526 self.enabled_repo_hooks = []
527
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700528 @property
529 def Exists(self):
530 return os.path.isdir(self.gitdir)
531
532 @property
533 def CurrentBranch(self):
534 """Obtain the name of the currently checked out branch.
535 The branch name omits the 'refs/heads/' prefix.
536 None is returned if the project is on a detached HEAD.
537 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700538 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700539 if b.startswith(R_HEADS):
540 return b[len(R_HEADS):]
541 return None
542
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700543 def IsRebaseInProgress(self):
544 w = self.worktree
545 g = os.path.join(w, '.git')
546 return os.path.exists(os.path.join(g, 'rebase-apply')) \
547 or os.path.exists(os.path.join(g, 'rebase-merge')) \
548 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200549
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700550 def IsDirty(self, consider_untracked=True):
551 """Is the working directory modified in some way?
552 """
553 self.work_git.update_index('-q',
554 '--unmerged',
555 '--ignore-missing',
556 '--refresh')
557 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
558 return True
559 if self.work_git.DiffZ('diff-files'):
560 return True
561 if consider_untracked and self.work_git.LsOthers():
562 return True
563 return False
564
565 _userident_name = None
566 _userident_email = None
567
568 @property
569 def UserName(self):
570 """Obtain the user's personal name.
571 """
572 if self._userident_name is None:
573 self._LoadUserIdentity()
574 return self._userident_name
575
576 @property
577 def UserEmail(self):
578 """Obtain the user's email address. This is very likely
579 to be their Gerrit login.
580 """
581 if self._userident_email is None:
582 self._LoadUserIdentity()
583 return self._userident_email
584
585 def _LoadUserIdentity(self):
586 u = self.bare_git.var('GIT_COMMITTER_IDENT')
587 m = re.compile("^(.*) <([^>]*)> ").match(u)
588 if m:
589 self._userident_name = m.group(1)
590 self._userident_email = m.group(2)
591 else:
592 self._userident_name = ''
593 self._userident_email = ''
594
595 def GetRemote(self, name):
596 """Get the configuration for a single remote.
597 """
598 return self.config.GetRemote(name)
599
600 def GetBranch(self, name):
601 """Get the configuration for a single branch.
602 """
603 return self.config.GetBranch(name)
604
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700605 def GetBranches(self):
606 """Get all existing local branches.
607 """
608 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700609 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700610 heads = {}
611 pubd = {}
612
613 for name, id in all.iteritems():
614 if name.startswith(R_HEADS):
615 name = name[len(R_HEADS):]
616 b = self.GetBranch(name)
617 b.current = name == current
618 b.published = None
619 b.revision = id
620 heads[name] = b
621
622 for name, id in all.iteritems():
623 if name.startswith(R_PUB):
624 name = name[len(R_PUB):]
625 b = heads.get(name)
626 if b:
627 b.published = id
628
629 return heads
630
Colin Cross5acde752012-03-28 20:15:45 -0700631 def MatchesGroups(self, manifest_groups):
632 """Returns true if the manifest groups specified at init should cause
633 this project to be synced.
634 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owens971de8e2012-04-16 10:36:08 -0700635 All projects are implicitly labelled with "default".
636
637 labels are resolved in order. In the example case of
638 project_groups: "default,group1,group2"
639 manifest_groups: "-group1,group2"
640 the project will be matched.
Colin Cross5acde752012-03-28 20:15:45 -0700641 """
Colin Crosseca119e2012-05-24 15:39:14 -0700642 if self.groups is None:
643 return True
Conley Owens971de8e2012-04-16 10:36:08 -0700644 matched = False
645 for group in manifest_groups:
646 if group.startswith('-') and group[1:] in self.groups:
647 matched = False
648 elif group in self.groups:
649 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700650
Conley Owens971de8e2012-04-16 10:36:08 -0700651 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700652
653## Status Display ##
654
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500655 def HasChanges(self):
656 """Returns true if there are uncommitted changes.
657 """
658 self.work_git.update_index('-q',
659 '--unmerged',
660 '--ignore-missing',
661 '--refresh')
662 if self.IsRebaseInProgress():
663 return True
664
665 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
666 return True
667
668 if self.work_git.DiffZ('diff-files'):
669 return True
670
671 if self.work_git.LsOthers():
672 return True
673
674 return False
675
Terence Haddock4655e812011-03-31 12:33:34 +0200676 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700677 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200678
679 Args:
680 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700681 """
682 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200683 if output_redir == None:
684 output_redir = sys.stdout
685 print >>output_redir, ''
686 print >>output_redir, 'project %s/' % self.relpath
687 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700688 return
689
690 self.work_git.update_index('-q',
691 '--unmerged',
692 '--ignore-missing',
693 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700694 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700695 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
696 df = self.work_git.DiffZ('diff-files')
697 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100698 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700699 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700700
701 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200702 if not output_redir == None:
703 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700704 out.project('project %-40s', self.relpath + '/')
705
706 branch = self.CurrentBranch
707 if branch is None:
708 out.nobranch('(*** NO BRANCH ***)')
709 else:
710 out.branch('branch %s', branch)
711 out.nl()
712
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700713 if rb:
714 out.important('prior sync failed; rebase still in progress')
715 out.nl()
716
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717 paths = list()
718 paths.extend(di.keys())
719 paths.extend(df.keys())
720 paths.extend(do)
721
722 paths = list(set(paths))
723 paths.sort()
724
725 for p in paths:
726 try: i = di[p]
727 except KeyError: i = None
728
729 try: f = df[p]
730 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200731
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732 if i: i_status = i.status.upper()
733 else: i_status = '-'
734
735 if f: f_status = f.status.lower()
736 else: f_status = '-'
737
738 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800739 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700740 i.src_path, p, i.level)
741 else:
742 line = ' %s%s\t%s' % (i_status, f_status, p)
743
744 if i and not f:
745 out.added('%s', line)
746 elif (i and f) or (not i and f):
747 out.changed('%s', line)
748 elif not i and not f:
749 out.untracked('%s', line)
750 else:
751 out.write('%s', line)
752 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200753
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700754 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700755
pelyad67872d2012-03-28 14:49:58 +0300756 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 """Prints the status of the repository to stdout.
758 """
759 out = DiffColoring(self.config)
760 cmd = ['diff']
761 if out.is_on:
762 cmd.append('--color')
763 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300764 if absolute_paths:
765 cmd.append('--src-prefix=a/%s/' % self.relpath)
766 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700767 cmd.append('--')
768 p = GitCommand(self,
769 cmd,
770 capture_stdout = True,
771 capture_stderr = True)
772 has_diff = False
773 for line in p.process.stdout:
774 if not has_diff:
775 out.nl()
776 out.project('project %s/' % self.relpath)
777 out.nl()
778 has_diff = True
779 print line[:-1]
780 p.Wait()
781
782
783## Publish / Upload ##
784
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700785 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700786 """Was the branch published (uploaded) for code review?
787 If so, returns the SHA-1 hash of the last published
788 state for the branch.
789 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700790 key = R_PUB + branch
791 if all is None:
792 try:
793 return self.bare_git.rev_parse(key)
794 except GitError:
795 return None
796 else:
797 try:
798 return all[key]
799 except KeyError:
800 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700801
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700802 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803 """Prunes any stale published refs.
804 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700805 if all is None:
806 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807 heads = set()
808 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700809 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810 if name.startswith(R_HEADS):
811 heads.add(name)
812 elif name.startswith(R_PUB):
813 canrm[name] = id
814
815 for name, id in canrm.iteritems():
816 n = name[len(R_PUB):]
817 if R_HEADS + n not in heads:
818 self.bare_git.DeleteRef(name, id)
819
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700820 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700821 """List any branches which can be uploaded for review.
822 """
823 heads = {}
824 pubed = {}
825
826 for name, id in self._allrefs.iteritems():
827 if name.startswith(R_HEADS):
828 heads[name[len(R_HEADS):]] = id
829 elif name.startswith(R_PUB):
830 pubed[name[len(R_PUB):]] = id
831
832 ready = []
833 for branch, id in heads.iteritems():
834 if branch in pubed and pubed[branch] == id:
835 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700836 if selected_branch and branch != selected_branch:
837 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800839 rb = self.GetUploadableBranch(branch)
840 if rb:
841 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700842 return ready
843
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800844 def GetUploadableBranch(self, branch_name):
845 """Get a single uploadable branch, or None.
846 """
847 branch = self.GetBranch(branch_name)
848 base = branch.LocalMerge
849 if branch.LocalMerge:
850 rb = ReviewableBranch(self, branch, base)
851 if rb.commits:
852 return rb
853 return None
854
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700855 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700856 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700857 auto_topic=False,
858 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700859 """Uploads the named branch for code review.
860 """
861 if branch is None:
862 branch = self.CurrentBranch
863 if branch is None:
864 raise GitError('not currently on a branch')
865
866 branch = self.GetBranch(branch)
867 if not branch.LocalMerge:
868 raise GitError('branch %s does not track a remote' % branch.name)
869 if not branch.remote.review:
870 raise GitError('remote %s has no review url' % branch.remote.name)
871
872 dest_branch = branch.merge
873 if not dest_branch.startswith(R_HEADS):
874 dest_branch = R_HEADS + dest_branch
875
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800876 if not branch.remote.projectname:
877 branch.remote.projectname = self.name
878 branch.remote.Save()
879
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800880 url = branch.remote.ReviewUrl(self.UserEmail)
881 if url is None:
882 raise UploadError('review not configured')
883 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800884
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800885 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800886 rp = ['gerrit receive-pack']
887 for e in people[0]:
888 rp.append('--reviewer=%s' % sq(e))
889 for e in people[1]:
890 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800891 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700892
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800893 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800894
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800895 if dest_branch.startswith(R_HEADS):
896 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700897
898 upload_type = 'for'
899 if draft:
900 upload_type = 'drafts'
901
902 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
903 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800904 if auto_topic:
905 ref_spec = ref_spec + '/' + branch.name
906 cmd.append(ref_spec)
907
908 if GitCommand(self, cmd, bare = True).Wait() != 0:
909 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700910
911 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
912 self.bare_git.UpdateRef(R_PUB + branch.name,
913 R_HEADS + branch.name,
914 message = msg)
915
916
917## Sync ##
918
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700919 def Sync_NetworkHalf(self,
920 quiet=False,
921 is_new=None,
922 current_branch_only=False,
923 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 """Perform only the network IO portion of the sync process.
925 Local working directory/branch state is not affected.
926 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700927 if is_new is None:
928 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200929 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 self._InitGitDir()
931 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700932
933 if is_new:
934 alt = os.path.join(self.gitdir, 'objects/info/alternates')
935 try:
936 fd = open(alt, 'rb')
937 try:
938 alt_dir = fd.readline().rstrip()
939 finally:
940 fd.close()
941 except IOError:
942 alt_dir = None
943 else:
944 alt_dir = None
945
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700946 if clone_bundle \
947 and alt_dir is None \
948 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700949 is_new = False
950
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700951 if not current_branch_only:
952 if self.sync_c:
953 current_branch_only = True
954 elif not self.manifest._loaded:
955 # Manifest cannot check defaults until it syncs.
956 current_branch_only = False
957 elif self.manifest.default.sync_c:
958 current_branch_only = True
959
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700960 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
961 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800963
964 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800965 self._InitMRef()
966 else:
967 self._InitMirrorHead()
968 try:
969 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
970 except OSError:
971 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800973
974 def PostRepoUpgrade(self):
975 self._InitHooks()
976
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700977 def _CopyFiles(self):
978 for file in self.copyfiles:
979 file._Copy()
980
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700981 def GetRevisionId(self, all=None):
982 if self.revisionId:
983 return self.revisionId
984
985 rem = self.GetRemote(self.remote.name)
986 rev = rem.ToLocal(self.revisionExpr)
987
988 if all is not None and rev in all:
989 return all[rev]
990
991 try:
992 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
993 except GitError:
994 raise ManifestInvalidRevisionError(
995 'revision %s in %s not found' % (self.revisionExpr,
996 self.name))
997
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700998 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700999 """Perform only the local IO portion of the sync process.
1000 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001002 all = self.bare_ref.all
1003 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001004 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001005
1006 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001007 head = self.work_git.GetHead()
1008 if head.startswith(R_HEADS):
1009 branch = head[len(R_HEADS):]
1010 try:
1011 head = all[head]
1012 except KeyError:
1013 head = None
1014 else:
1015 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001017 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001018 # Currently on a detached HEAD. The user is assumed to
1019 # not have any local modifications worth worrying about.
1020 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001021 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001022 syncbuf.fail(self, _PriorSyncFailedError())
1023 return
1024
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001025 if head == revid:
1026 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001027 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001028 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001029 if not syncbuf.detach_head:
1030 return
1031 else:
1032 lost = self._revlist(not_rev(revid), HEAD)
1033 if lost:
1034 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001035
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001037 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001038 except GitError, e:
1039 syncbuf.fail(self, e)
1040 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001042 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001043
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001044 if head == revid:
1045 # No changes; don't do anything further.
1046 #
1047 return
1048
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001049 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001050
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001051 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001052 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001053 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001055 syncbuf.info(self,
1056 "leaving %s; does not track upstream",
1057 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001059 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001060 except GitError, e:
1061 syncbuf.fail(self, e)
1062 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001064 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001066 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001067 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001068 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001069 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 if not_merged:
1071 if upstream_gain:
1072 # The user has published this branch and some of those
1073 # commits are not yet merged upstream. We do not want
1074 # to rewrite the published commits so we punt.
1075 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001076 syncbuf.fail(self,
1077 "branch %s is published (but not merged) and is now %d commits behind"
1078 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001079 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001080 elif pub == head:
1081 # All published commits are merged, and thus we are a
1082 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001083 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001084 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001085 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001086 self._CopyFiles()
1087 syncbuf.later1(self, _doff)
1088 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001090 # Examine the local commits not in the remote. Find the
1091 # last one attributed to this user, if any.
1092 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001093 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001094 last_mine = None
1095 cnt_mine = 0
1096 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001097 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001098 if committer_email == self.UserEmail:
1099 last_mine = commit_id
1100 cnt_mine += 1
1101
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001102 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001103 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001104
1105 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001106 syncbuf.fail(self, _DirtyError())
1107 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001109 # If the upstream switched on us, warn the user.
1110 #
1111 if branch.merge != self.revisionExpr:
1112 if branch.merge and self.revisionExpr:
1113 syncbuf.info(self,
1114 'manifest switched %s...%s',
1115 branch.merge,
1116 self.revisionExpr)
1117 elif branch.merge:
1118 syncbuf.info(self,
1119 'manifest no longer tracks %s',
1120 branch.merge)
1121
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001122 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001123 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001124 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001125 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001126 syncbuf.info(self,
1127 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001128 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001129
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001130 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001131 if not ID_RE.match(self.revisionExpr):
1132 # in case of manifest sync the revisionExpr might be a SHA1
1133 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001134 branch.Save()
1135
Mike Pontillod3153822012-02-28 11:53:24 -08001136 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001137 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001138 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001139 self._CopyFiles()
1140 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001141 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001142 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001143 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001144 self._CopyFiles()
1145 except GitError, e:
1146 syncbuf.fail(self, e)
1147 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001148 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001149 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001150 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001151 self._CopyFiles()
1152 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001153
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001154 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001155 # dest should already be an absolute path, but src is project relative
1156 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001157 abssrc = os.path.join(self.worktree, src)
1158 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001159
James W. Mills24c13082012-04-12 15:04:13 -05001160 def AddAnnotation(self, name, value, keep):
1161 self.annotations.append(_Annotation(name, value, keep))
1162
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001163 def DownloadPatchSet(self, change_id, patch_id):
1164 """Download a single patch set of a single change to FETCH_HEAD.
1165 """
1166 remote = self.GetRemote(self.remote.name)
1167
1168 cmd = ['fetch', remote.name]
1169 cmd.append('refs/changes/%2.2d/%d/%d' \
1170 % (change_id % 100, change_id, patch_id))
1171 cmd.extend(map(lambda x: str(x), remote.fetch))
1172 if GitCommand(self, cmd, bare=True).Wait() != 0:
1173 return None
1174 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001175 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001176 change_id,
1177 patch_id,
1178 self.bare_git.rev_parse('FETCH_HEAD'))
1179
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001180
1181## Branch Management ##
1182
1183 def StartBranch(self, name):
1184 """Create a new branch off the manifest's revision.
1185 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001186 head = self.work_git.GetHead()
1187 if head == (R_HEADS + name):
1188 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001189
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001190 all = self.bare_ref.all
1191 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001192 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001193 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001194 capture_stdout = True,
1195 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001196
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001197 branch = self.GetBranch(name)
1198 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001199 branch.merge = self.revisionExpr
1200 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001201
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001202 if head.startswith(R_HEADS):
1203 try:
1204 head = all[head]
1205 except KeyError:
1206 head = None
1207
1208 if revid and head and revid == head:
1209 ref = os.path.join(self.gitdir, R_HEADS + name)
1210 try:
1211 os.makedirs(os.path.dirname(ref))
1212 except OSError:
1213 pass
1214 _lwrite(ref, '%s\n' % revid)
1215 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1216 'ref: %s%s\n' % (R_HEADS, name))
1217 branch.Save()
1218 return True
1219
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001220 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001221 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001222 capture_stdout = True,
1223 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001224 branch.Save()
1225 return True
1226 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227
Wink Saville02d79452009-04-10 13:01:24 -07001228 def CheckoutBranch(self, name):
1229 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001230
1231 Args:
1232 name: The name of the branch to checkout.
1233
1234 Returns:
1235 True if the checkout succeeded; False if it didn't; None if the branch
1236 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001237 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001238 rev = R_HEADS + name
1239 head = self.work_git.GetHead()
1240 if head == rev:
1241 # Already on the branch
1242 #
1243 return True
Wink Saville02d79452009-04-10 13:01:24 -07001244
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001245 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001246 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001247 revid = all[rev]
1248 except KeyError:
1249 # Branch does not exist in this project
1250 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001251 return None
Wink Saville02d79452009-04-10 13:01:24 -07001252
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001253 if head.startswith(R_HEADS):
1254 try:
1255 head = all[head]
1256 except KeyError:
1257 head = None
1258
1259 if head == revid:
1260 # Same revision; just update HEAD to point to the new
1261 # target branch, but otherwise take no other action.
1262 #
1263 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1264 'ref: %s%s\n' % (R_HEADS, name))
1265 return True
1266
1267 return GitCommand(self,
1268 ['checkout', name, '--'],
1269 capture_stdout = True,
1270 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001271
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001272 def AbandonBranch(self, name):
1273 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001274
1275 Args:
1276 name: The name of the branch to abandon.
1277
1278 Returns:
1279 True if the abandon succeeded; False if it didn't; None if the branch
1280 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001281 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001282 rev = R_HEADS + name
1283 all = self.bare_ref.all
1284 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001285 # Doesn't exist
1286 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001287
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001288 head = self.work_git.GetHead()
1289 if head == rev:
1290 # We can't destroy the branch while we are sitting
1291 # on it. Switch to a detached HEAD.
1292 #
1293 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001294
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001295 revid = self.GetRevisionId(all)
1296 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001297 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1298 '%s\n' % revid)
1299 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001300 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001301
1302 return GitCommand(self,
1303 ['branch', '-D', name],
1304 capture_stdout = True,
1305 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001306
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 def PruneHeads(self):
1308 """Prune any topic branches already merged into upstream.
1309 """
1310 cb = self.CurrentBranch
1311 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001312 left = self._allrefs
1313 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001314 if name.startswith(R_HEADS):
1315 name = name[len(R_HEADS):]
1316 if cb is None or name != cb:
1317 kill.append(name)
1318
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001319 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320 if cb is not None \
1321 and not self._revlist(HEAD + '...' + rev) \
1322 and not self.IsDirty(consider_untracked = False):
1323 self.work_git.DetachHead(HEAD)
1324 kill.append(cb)
1325
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001327 old = self.bare_git.GetHead()
1328 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001331 try:
1332 self.bare_git.DetachHead(rev)
1333
1334 b = ['branch', '-d']
1335 b.extend(kill)
1336 b = GitCommand(self, b, bare=True,
1337 capture_stdout=True,
1338 capture_stderr=True)
1339 b.Wait()
1340 finally:
1341 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001342 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001343
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001344 for branch in kill:
1345 if (R_HEADS + branch) not in left:
1346 self.CleanPublishedCache()
1347 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001348
1349 if cb and cb not in kill:
1350 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001351 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001352
1353 kept = []
1354 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001355 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001356 branch = self.GetBranch(branch)
1357 base = branch.LocalMerge
1358 if not base:
1359 base = rev
1360 kept.append(ReviewableBranch(self, branch, base))
1361 return kept
1362
1363
1364## Direct Git Commands ##
1365
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001366 def _RemoteFetch(self, name=None,
1367 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001368 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001369 quiet=False,
1370 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001371
1372 is_sha1 = False
1373 tag_name = None
1374
1375 if current_branch_only:
1376 if ID_RE.match(self.revisionExpr) is not None:
1377 is_sha1 = True
1378 elif self.revisionExpr.startswith(R_TAGS):
1379 # this is a tag and its sha1 value should never change
1380 tag_name = self.revisionExpr[len(R_TAGS):]
1381
1382 if is_sha1 or tag_name is not None:
1383 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001384 # if revision (sha or tag) is not present then following function
1385 # throws an error.
1386 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001387 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001388 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001389 # There is no such persistent revision. We have to fetch it.
1390 pass
1391
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001392 if not name:
1393 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001394
1395 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001396 remote = self.GetRemote(name)
1397 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001398 ssh_proxy = True
1399
Shawn O. Pearce88443382010-10-08 10:02:09 +02001400 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001401 if alt_dir and 'objects' == os.path.basename(alt_dir):
1402 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001403 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1404 remote = self.GetRemote(name)
1405
1406 all = self.bare_ref.all
1407 ids = set(all.values())
1408 tmp = set()
1409
1410 for r, id in GitRefs(ref_dir).all.iteritems():
1411 if r not in all:
1412 if r.startswith(R_TAGS) or remote.WritesTo(r):
1413 all[r] = id
1414 ids.add(id)
1415 continue
1416
1417 if id in ids:
1418 continue
1419
1420 r = 'refs/_alt/%s' % id
1421 all[r] = id
1422 ids.add(id)
1423 tmp.add(r)
1424
1425 ref_names = list(all.keys())
1426 ref_names.sort()
1427
1428 tmp_packed = ''
1429 old_packed = ''
1430
1431 for r in ref_names:
1432 line = '%s %s\n' % (all[r], r)
1433 tmp_packed += line
1434 if r not in tmp:
1435 old_packed += line
1436
1437 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001438 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001439 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001440
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001441 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001442
1443 # The --depth option only affects the initial fetch; after that we'll do
1444 # full fetches of changes.
1445 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1446 if depth and initial:
1447 cmd.append('--depth=%s' % depth)
1448
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001449 if quiet:
1450 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001451 if not self.worktree:
1452 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001453 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001454
1455 if not current_branch_only or is_sha1:
1456 # Fetch whole repo
1457 cmd.append('--tags')
1458 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1459 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001460 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001461 cmd.append(tag_name)
1462 else:
1463 branch = self.revisionExpr
1464 if branch.startswith(R_HEADS):
1465 branch = branch[len(R_HEADS):]
1466 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001467
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001468 ok = False
1469 for i in range(2):
1470 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1471 ok = True
1472 break
1473 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001474
1475 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001476 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001477 if old_packed != '':
1478 _lwrite(packed_refs, old_packed)
1479 else:
1480 os.remove(packed_refs)
1481 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001482 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001483
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001484 def _ApplyCloneBundle(self, initial=False, quiet=False):
1485 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1486 return False
1487
1488 remote = self.GetRemote(self.remote.name)
1489 bundle_url = remote.url + '/clone.bundle'
1490 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001491 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1492 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001493 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1494 return False
1495
1496 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1497 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1498
1499 exist_dst = os.path.exists(bundle_dst)
1500 exist_tmp = os.path.exists(bundle_tmp)
1501
1502 if not initial and not exist_dst and not exist_tmp:
1503 return False
1504
1505 if not exist_dst:
1506 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1507 if not exist_dst:
1508 return False
1509
1510 cmd = ['fetch']
1511 if quiet:
1512 cmd.append('--quiet')
1513 if not self.worktree:
1514 cmd.append('--update-head-ok')
1515 cmd.append(bundle_dst)
1516 for f in remote.fetch:
1517 cmd.append(str(f))
1518 cmd.append('refs/tags/*:refs/tags/*')
1519
1520 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001521 if os.path.exists(bundle_dst):
1522 os.remove(bundle_dst)
1523 if os.path.exists(bundle_tmp):
1524 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001525 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001526
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001527 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001528 if os.path.exists(dstPath):
1529 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001530
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001531 cmd = ['curl', '--output', tmpPath, '--netrc', '--location']
1532 if quiet:
1533 cmd += ['--silent']
1534 if os.path.exists(tmpPath):
1535 size = os.stat(tmpPath).st_size
1536 if size >= 1024:
1537 cmd += ['--continue-at', '%d' % (size,)]
1538 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001539 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001540 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1541 cmd += ['--proxy', os.environ['http_proxy']]
1542 cmd += [srcUrl]
1543
1544 if IsTrace():
1545 Trace('%s', ' '.join(cmd))
1546 try:
1547 proc = subprocess.Popen(cmd)
1548 except OSError:
1549 return False
1550
1551 ok = proc.wait() == 0
1552 if os.path.exists(tmpPath):
1553 if ok and os.stat(tmpPath).st_size > 16:
1554 os.rename(tmpPath, dstPath)
1555 return True
1556 else:
1557 os.remove(tmpPath)
1558 return False
1559 else:
1560 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001561
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001562 def _Checkout(self, rev, quiet=False):
1563 cmd = ['checkout']
1564 if quiet:
1565 cmd.append('-q')
1566 cmd.append(rev)
1567 cmd.append('--')
1568 if GitCommand(self, cmd).Wait() != 0:
1569 if self._allrefs:
1570 raise GitError('%s checkout %s ' % (self.name, rev))
1571
Pierre Tardye5a21222011-03-24 16:28:18 +01001572 def _CherryPick(self, rev, quiet=False):
1573 cmd = ['cherry-pick']
1574 cmd.append(rev)
1575 cmd.append('--')
1576 if GitCommand(self, cmd).Wait() != 0:
1577 if self._allrefs:
1578 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1579
Erwan Mahea94f1622011-08-19 13:56:09 +02001580 def _Revert(self, rev, quiet=False):
1581 cmd = ['revert']
1582 cmd.append('--no-edit')
1583 cmd.append(rev)
1584 cmd.append('--')
1585 if GitCommand(self, cmd).Wait() != 0:
1586 if self._allrefs:
1587 raise GitError('%s revert %s ' % (self.name, rev))
1588
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001589 def _ResetHard(self, rev, quiet=True):
1590 cmd = ['reset', '--hard']
1591 if quiet:
1592 cmd.append('-q')
1593 cmd.append(rev)
1594 if GitCommand(self, cmd).Wait() != 0:
1595 raise GitError('%s reset --hard %s ' % (self.name, rev))
1596
1597 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001598 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001599 if onto is not None:
1600 cmd.extend(['--onto', onto])
1601 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001602 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001603 raise GitError('%s rebase %s ' % (self.name, upstream))
1604
Pierre Tardy3d125942012-05-04 12:18:12 +02001605 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001606 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001607 if ffonly:
1608 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001609 if GitCommand(self, cmd).Wait() != 0:
1610 raise GitError('%s merge %s ' % (self.name, head))
1611
1612 def _InitGitDir(self):
1613 if not os.path.exists(self.gitdir):
1614 os.makedirs(self.gitdir)
1615 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001616
Shawn O. Pearce88443382010-10-08 10:02:09 +02001617 mp = self.manifest.manifestProject
1618 ref_dir = mp.config.GetString('repo.reference')
1619
1620 if ref_dir:
1621 mirror_git = os.path.join(ref_dir, self.name + '.git')
1622 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1623 self.relpath + '.git')
1624
1625 if os.path.exists(mirror_git):
1626 ref_dir = mirror_git
1627
1628 elif os.path.exists(repo_git):
1629 ref_dir = repo_git
1630
1631 else:
1632 ref_dir = None
1633
1634 if ref_dir:
1635 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1636 os.path.join(ref_dir, 'objects') + '\n')
1637
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001638 if self.manifest.IsMirror:
1639 self.config.SetString('core.bare', 'true')
1640 else:
1641 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001642
1643 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001644 try:
1645 to_rm = os.listdir(hooks)
1646 except OSError:
1647 to_rm = []
1648 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001649 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001650 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001651
1652 m = self.manifest.manifestProject.config
1653 for key in ['user.name', 'user.email']:
1654 if m.Has(key, include_defaults = False):
1655 self.config.SetString(key, m.GetString(key))
1656
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001657 def _InitHooks(self):
1658 hooks = self._gitdir_path('hooks')
1659 if not os.path.exists(hooks):
1660 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001661 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001662 name = os.path.basename(stock_hook)
1663
Victor Boivie65e0f352011-04-18 11:23:29 +02001664 if name in ('commit-msg',) and not self.remote.review \
1665 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001666 # Don't install a Gerrit Code Review hook if this
1667 # project does not appear to use it for reviews.
1668 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001669 # Since the manifest project is one of those, but also
1670 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001671 continue
1672
1673 dst = os.path.join(hooks, name)
1674 if os.path.islink(dst):
1675 continue
1676 if os.path.exists(dst):
1677 if filecmp.cmp(stock_hook, dst, shallow=False):
1678 os.remove(dst)
1679 else:
1680 _error("%s: Not replacing %s hook", self.relpath, name)
1681 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001682 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001683 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001684 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001685 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001686 raise GitError('filesystem must support symlinks')
1687 else:
1688 raise
1689
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001690 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001691 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001692 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001693 remote.url = self.remote.url
1694 remote.review = self.remote.review
1695 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001696
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001697 if self.worktree:
1698 remote.ResetFetch(mirror=False)
1699 else:
1700 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001701 remote.Save()
1702
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001703 def _InitMRef(self):
1704 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001705 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001706
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001707 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001708 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001709
1710 def _InitAnyMRef(self, ref):
1711 cur = self.bare_ref.symref(ref)
1712
1713 if self.revisionId:
1714 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1715 msg = 'manifest set to %s' % self.revisionId
1716 dst = self.revisionId + '^0'
1717 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1718 else:
1719 remote = self.GetRemote(self.remote.name)
1720 dst = remote.ToLocal(self.revisionExpr)
1721 if cur != dst:
1722 msg = 'manifest set to %s' % self.revisionExpr
1723 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001724
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001725 def _InitWorkTree(self):
1726 dotgit = os.path.join(self.worktree, '.git')
1727 if not os.path.exists(dotgit):
1728 os.makedirs(dotgit)
1729
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001730 for name in ['config',
1731 'description',
1732 'hooks',
1733 'info',
1734 'logs',
1735 'objects',
1736 'packed-refs',
1737 'refs',
1738 'rr-cache',
1739 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001740 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001741 src = os.path.join(self.gitdir, name)
1742 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001743 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001744 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001745 else:
1746 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001747 except OSError, e:
1748 if e.errno == errno.EPERM:
1749 raise GitError('filesystem must support symlinks')
1750 else:
1751 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001752
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001753 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001754
1755 cmd = ['read-tree', '--reset', '-u']
1756 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001757 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758 if GitCommand(self, cmd).Wait() != 0:
1759 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001760
1761 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1762 if not os.path.exists(rr_cache):
1763 os.makedirs(rr_cache)
1764
Shawn O. Pearce93609662009-04-21 10:50:33 -07001765 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766
1767 def _gitdir_path(self, path):
1768 return os.path.join(self.gitdir, path)
1769
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001770 def _revlist(self, *args, **kw):
1771 a = []
1772 a.extend(args)
1773 a.append('--')
1774 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001775
1776 @property
1777 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001778 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001779
1780 class _GitGetByExec(object):
1781 def __init__(self, project, bare):
1782 self._project = project
1783 self._bare = bare
1784
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001785 def LsOthers(self):
1786 p = GitCommand(self._project,
1787 ['ls-files',
1788 '-z',
1789 '--others',
1790 '--exclude-standard'],
1791 bare = False,
1792 capture_stdout = True,
1793 capture_stderr = True)
1794 if p.Wait() == 0:
1795 out = p.stdout
1796 if out:
1797 return out[:-1].split("\0")
1798 return []
1799
1800 def DiffZ(self, name, *args):
1801 cmd = [name]
1802 cmd.append('-z')
1803 cmd.extend(args)
1804 p = GitCommand(self._project,
1805 cmd,
1806 bare = False,
1807 capture_stdout = True,
1808 capture_stderr = True)
1809 try:
1810 out = p.process.stdout.read()
1811 r = {}
1812 if out:
1813 out = iter(out[:-1].split('\0'))
1814 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001815 try:
1816 info = out.next()
1817 path = out.next()
1818 except StopIteration:
1819 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001820
1821 class _Info(object):
1822 def __init__(self, path, omode, nmode, oid, nid, state):
1823 self.path = path
1824 self.src_path = None
1825 self.old_mode = omode
1826 self.new_mode = nmode
1827 self.old_id = oid
1828 self.new_id = nid
1829
1830 if len(state) == 1:
1831 self.status = state
1832 self.level = None
1833 else:
1834 self.status = state[:1]
1835 self.level = state[1:]
1836 while self.level.startswith('0'):
1837 self.level = self.level[1:]
1838
1839 info = info[1:].split(' ')
1840 info =_Info(path, *info)
1841 if info.status in ('R', 'C'):
1842 info.src_path = info.path
1843 info.path = out.next()
1844 r[info.path] = info
1845 return r
1846 finally:
1847 p.Wait()
1848
1849 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001850 if self._bare:
1851 path = os.path.join(self._project.gitdir, HEAD)
1852 else:
1853 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001854 fd = open(path, 'rb')
1855 try:
1856 line = fd.read()
1857 finally:
1858 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001859 if line.startswith('ref: '):
1860 return line[5:-1]
1861 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001862
1863 def SetHead(self, ref, message=None):
1864 cmdv = []
1865 if message is not None:
1866 cmdv.extend(['-m', message])
1867 cmdv.append(HEAD)
1868 cmdv.append(ref)
1869 self.symbolic_ref(*cmdv)
1870
1871 def DetachHead(self, new, message=None):
1872 cmdv = ['--no-deref']
1873 if message is not None:
1874 cmdv.extend(['-m', message])
1875 cmdv.append(HEAD)
1876 cmdv.append(new)
1877 self.update_ref(*cmdv)
1878
1879 def UpdateRef(self, name, new, old=None,
1880 message=None,
1881 detach=False):
1882 cmdv = []
1883 if message is not None:
1884 cmdv.extend(['-m', message])
1885 if detach:
1886 cmdv.append('--no-deref')
1887 cmdv.append(name)
1888 cmdv.append(new)
1889 if old is not None:
1890 cmdv.append(old)
1891 self.update_ref(*cmdv)
1892
1893 def DeleteRef(self, name, old=None):
1894 if not old:
1895 old = self.rev_parse(name)
1896 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001897 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001898
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001899 def rev_list(self, *args, **kw):
1900 if 'format' in kw:
1901 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1902 else:
1903 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001904 cmdv.extend(args)
1905 p = GitCommand(self._project,
1906 cmdv,
1907 bare = self._bare,
1908 capture_stdout = True,
1909 capture_stderr = True)
1910 r = []
1911 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001912 if line[-1] == '\n':
1913 line = line[:-1]
1914 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001915 if p.Wait() != 0:
1916 raise GitError('%s rev-list %s: %s' % (
1917 self._project.name,
1918 str(args),
1919 p.stderr))
1920 return r
1921
1922 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001923 """Allow arbitrary git commands using pythonic syntax.
1924
1925 This allows you to do things like:
1926 git_obj.rev_parse('HEAD')
1927
1928 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1929 run. We'll replace the '_' with a '-' and try to run a git command.
1930 Any other arguments will be passed to the git command.
1931
1932 Args:
1933 name: The name of the git command to call. Any '_' characters will
1934 be replaced with '-'.
1935
1936 Returns:
1937 A callable object that will try to call git with the named command.
1938 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001939 name = name.replace('_', '-')
1940 def runner(*args):
1941 cmdv = [name]
1942 cmdv.extend(args)
1943 p = GitCommand(self._project,
1944 cmdv,
1945 bare = self._bare,
1946 capture_stdout = True,
1947 capture_stderr = True)
1948 if p.Wait() != 0:
1949 raise GitError('%s %s: %s' % (
1950 self._project.name,
1951 name,
1952 p.stderr))
1953 r = p.stdout
1954 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1955 return r[:-1]
1956 return r
1957 return runner
1958
1959
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001960class _PriorSyncFailedError(Exception):
1961 def __str__(self):
1962 return 'prior sync failed; rebase still in progress'
1963
1964class _DirtyError(Exception):
1965 def __str__(self):
1966 return 'contains uncommitted changes'
1967
1968class _InfoMessage(object):
1969 def __init__(self, project, text):
1970 self.project = project
1971 self.text = text
1972
1973 def Print(self, syncbuf):
1974 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1975 syncbuf.out.nl()
1976
1977class _Failure(object):
1978 def __init__(self, project, why):
1979 self.project = project
1980 self.why = why
1981
1982 def Print(self, syncbuf):
1983 syncbuf.out.fail('error: %s/: %s',
1984 self.project.relpath,
1985 str(self.why))
1986 syncbuf.out.nl()
1987
1988class _Later(object):
1989 def __init__(self, project, action):
1990 self.project = project
1991 self.action = action
1992
1993 def Run(self, syncbuf):
1994 out = syncbuf.out
1995 out.project('project %s/', self.project.relpath)
1996 out.nl()
1997 try:
1998 self.action()
1999 out.nl()
2000 return True
2001 except GitError, e:
2002 out.nl()
2003 return False
2004
2005class _SyncColoring(Coloring):
2006 def __init__(self, config):
2007 Coloring.__init__(self, config, 'reposync')
2008 self.project = self.printer('header', attr = 'bold')
2009 self.info = self.printer('info')
2010 self.fail = self.printer('fail', fg='red')
2011
2012class SyncBuffer(object):
2013 def __init__(self, config, detach_head=False):
2014 self._messages = []
2015 self._failures = []
2016 self._later_queue1 = []
2017 self._later_queue2 = []
2018
2019 self.out = _SyncColoring(config)
2020 self.out.redirect(sys.stderr)
2021
2022 self.detach_head = detach_head
2023 self.clean = True
2024
2025 def info(self, project, fmt, *args):
2026 self._messages.append(_InfoMessage(project, fmt % args))
2027
2028 def fail(self, project, err=None):
2029 self._failures.append(_Failure(project, err))
2030 self.clean = False
2031
2032 def later1(self, project, what):
2033 self._later_queue1.append(_Later(project, what))
2034
2035 def later2(self, project, what):
2036 self._later_queue2.append(_Later(project, what))
2037
2038 def Finish(self):
2039 self._PrintMessages()
2040 self._RunLater()
2041 self._PrintMessages()
2042 return self.clean
2043
2044 def _RunLater(self):
2045 for q in ['_later_queue1', '_later_queue2']:
2046 if not self._RunQueue(q):
2047 return
2048
2049 def _RunQueue(self, queue):
2050 for m in getattr(self, queue):
2051 if not m.Run(self):
2052 self.clean = False
2053 return False
2054 setattr(self, queue, [])
2055 return True
2056
2057 def _PrintMessages(self):
2058 for m in self._messages:
2059 m.Print(self)
2060 for m in self._failures:
2061 m.Print(self)
2062
2063 self._messages = []
2064 self._failures = []
2065
2066
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002067class MetaProject(Project):
2068 """A special project housed under .repo.
2069 """
2070 def __init__(self, manifest, name, gitdir, worktree):
2071 repodir = manifest.repodir
2072 Project.__init__(self,
2073 manifest = manifest,
2074 name = name,
2075 gitdir = gitdir,
2076 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002077 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002078 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002079 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002080 revisionId = None,
2081 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002082
2083 def PreSync(self):
2084 if self.Exists:
2085 cb = self.CurrentBranch
2086 if cb:
2087 base = self.GetBranch(cb).merge
2088 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002089 self.revisionExpr = base
2090 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002091
Florian Vallee5d016502012-06-07 17:19:26 +02002092 def MetaBranchSwitch(self, target):
2093 """ Prepare MetaProject for manifest branch switch
2094 """
2095
2096 # detach and delete manifest branch, allowing a new
2097 # branch to take over
2098 syncbuf = SyncBuffer(self.config, detach_head = True)
2099 self.Sync_LocalHalf(syncbuf)
2100 syncbuf.Finish()
2101
2102 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002103 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002104 capture_stdout = True,
2105 capture_stderr = True).Wait() == 0
2106
2107
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002108 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002109 def LastFetch(self):
2110 try:
2111 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2112 return os.path.getmtime(fh)
2113 except OSError:
2114 return 0
2115
2116 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002117 def HasChanges(self):
2118 """Has the remote received new commits not yet checked out?
2119 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002120 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002121 return False
2122
2123 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002124 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002125 head = self.work_git.GetHead()
2126 if head.startswith(R_HEADS):
2127 try:
2128 head = all[head]
2129 except KeyError:
2130 head = None
2131
2132 if revid == head:
2133 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002134 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002135 return True
2136 return False