blob: 60633b730efa6ef54c55127e7e4ba1b5ef5af48a [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 Owensbb1b5f52012-08-13 13:11:18 -0700635 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700636
637 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700638 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700639 manifest_groups: "-group1,group2"
640 the project will be matched.
Colin Cross5acde752012-03-28 20:15:45 -0700641 """
Conley Owensbb1b5f52012-08-13 13:11:18 -0700642 expanded_manifest_groups = manifest_groups or ['all', '-notdefault']
643 expanded_project_groups = ['all'] + (self.groups or [])
644
Conley Owens971de8e2012-04-16 10:36:08 -0700645 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700646 for group in expanded_manifest_groups:
647 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700648 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700649 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700650 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700651
Conley Owens971de8e2012-04-16 10:36:08 -0700652 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653
654## Status Display ##
655
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500656 def HasChanges(self):
657 """Returns true if there are uncommitted changes.
658 """
659 self.work_git.update_index('-q',
660 '--unmerged',
661 '--ignore-missing',
662 '--refresh')
663 if self.IsRebaseInProgress():
664 return True
665
666 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
667 return True
668
669 if self.work_git.DiffZ('diff-files'):
670 return True
671
672 if self.work_git.LsOthers():
673 return True
674
675 return False
676
Terence Haddock4655e812011-03-31 12:33:34 +0200677 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700678 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200679
680 Args:
681 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700682 """
683 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200684 if output_redir == None:
685 output_redir = sys.stdout
686 print >>output_redir, ''
687 print >>output_redir, 'project %s/' % self.relpath
688 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700689 return
690
691 self.work_git.update_index('-q',
692 '--unmerged',
693 '--ignore-missing',
694 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700695 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
697 df = self.work_git.DiffZ('diff-files')
698 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100699 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700700 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700701
702 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200703 if not output_redir == None:
704 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700705 out.project('project %-40s', self.relpath + '/')
706
707 branch = self.CurrentBranch
708 if branch is None:
709 out.nobranch('(*** NO BRANCH ***)')
710 else:
711 out.branch('branch %s', branch)
712 out.nl()
713
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700714 if rb:
715 out.important('prior sync failed; rebase still in progress')
716 out.nl()
717
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700718 paths = list()
719 paths.extend(di.keys())
720 paths.extend(df.keys())
721 paths.extend(do)
722
723 paths = list(set(paths))
724 paths.sort()
725
726 for p in paths:
727 try: i = di[p]
728 except KeyError: i = None
729
730 try: f = df[p]
731 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200732
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733 if i: i_status = i.status.upper()
734 else: i_status = '-'
735
736 if f: f_status = f.status.lower()
737 else: f_status = '-'
738
739 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800740 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741 i.src_path, p, i.level)
742 else:
743 line = ' %s%s\t%s' % (i_status, f_status, p)
744
745 if i and not f:
746 out.added('%s', line)
747 elif (i and f) or (not i and f):
748 out.changed('%s', line)
749 elif not i and not f:
750 out.untracked('%s', line)
751 else:
752 out.write('%s', line)
753 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200754
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700755 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700756
pelyad67872d2012-03-28 14:49:58 +0300757 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700758 """Prints the status of the repository to stdout.
759 """
760 out = DiffColoring(self.config)
761 cmd = ['diff']
762 if out.is_on:
763 cmd.append('--color')
764 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300765 if absolute_paths:
766 cmd.append('--src-prefix=a/%s/' % self.relpath)
767 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700768 cmd.append('--')
769 p = GitCommand(self,
770 cmd,
771 capture_stdout = True,
772 capture_stderr = True)
773 has_diff = False
774 for line in p.process.stdout:
775 if not has_diff:
776 out.nl()
777 out.project('project %s/' % self.relpath)
778 out.nl()
779 has_diff = True
780 print line[:-1]
781 p.Wait()
782
783
784## Publish / Upload ##
785
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700786 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700787 """Was the branch published (uploaded) for code review?
788 If so, returns the SHA-1 hash of the last published
789 state for the branch.
790 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700791 key = R_PUB + branch
792 if all is None:
793 try:
794 return self.bare_git.rev_parse(key)
795 except GitError:
796 return None
797 else:
798 try:
799 return all[key]
800 except KeyError:
801 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700802
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700803 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700804 """Prunes any stale published refs.
805 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700806 if all is None:
807 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700808 heads = set()
809 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700810 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811 if name.startswith(R_HEADS):
812 heads.add(name)
813 elif name.startswith(R_PUB):
814 canrm[name] = id
815
816 for name, id in canrm.iteritems():
817 n = name[len(R_PUB):]
818 if R_HEADS + n not in heads:
819 self.bare_git.DeleteRef(name, id)
820
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700821 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700822 """List any branches which can be uploaded for review.
823 """
824 heads = {}
825 pubed = {}
826
827 for name, id in self._allrefs.iteritems():
828 if name.startswith(R_HEADS):
829 heads[name[len(R_HEADS):]] = id
830 elif name.startswith(R_PUB):
831 pubed[name[len(R_PUB):]] = id
832
833 ready = []
834 for branch, id in heads.iteritems():
835 if branch in pubed and pubed[branch] == id:
836 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700837 if selected_branch and branch != selected_branch:
838 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800840 rb = self.GetUploadableBranch(branch)
841 if rb:
842 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843 return ready
844
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800845 def GetUploadableBranch(self, branch_name):
846 """Get a single uploadable branch, or None.
847 """
848 branch = self.GetBranch(branch_name)
849 base = branch.LocalMerge
850 if branch.LocalMerge:
851 rb = ReviewableBranch(self, branch, base)
852 if rb.commits:
853 return rb
854 return None
855
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700856 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700857 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700858 auto_topic=False,
859 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700860 """Uploads the named branch for code review.
861 """
862 if branch is None:
863 branch = self.CurrentBranch
864 if branch is None:
865 raise GitError('not currently on a branch')
866
867 branch = self.GetBranch(branch)
868 if not branch.LocalMerge:
869 raise GitError('branch %s does not track a remote' % branch.name)
870 if not branch.remote.review:
871 raise GitError('remote %s has no review url' % branch.remote.name)
872
873 dest_branch = branch.merge
874 if not dest_branch.startswith(R_HEADS):
875 dest_branch = R_HEADS + dest_branch
876
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800877 if not branch.remote.projectname:
878 branch.remote.projectname = self.name
879 branch.remote.Save()
880
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800881 url = branch.remote.ReviewUrl(self.UserEmail)
882 if url is None:
883 raise UploadError('review not configured')
884 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800885
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800886 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800887 rp = ['gerrit receive-pack']
888 for e in people[0]:
889 rp.append('--reviewer=%s' % sq(e))
890 for e in people[1]:
891 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800892 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700893
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800894 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800895
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800896 if dest_branch.startswith(R_HEADS):
897 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700898
899 upload_type = 'for'
900 if draft:
901 upload_type = 'drafts'
902
903 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
904 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800905 if auto_topic:
906 ref_spec = ref_spec + '/' + branch.name
907 cmd.append(ref_spec)
908
909 if GitCommand(self, cmd, bare = True).Wait() != 0:
910 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911
912 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
913 self.bare_git.UpdateRef(R_PUB + branch.name,
914 R_HEADS + branch.name,
915 message = msg)
916
917
918## Sync ##
919
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700920 def Sync_NetworkHalf(self,
921 quiet=False,
922 is_new=None,
923 current_branch_only=False,
924 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925 """Perform only the network IO portion of the sync process.
926 Local working directory/branch state is not affected.
927 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700928 if is_new is None:
929 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200930 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931 self._InitGitDir()
932 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700933
934 if is_new:
935 alt = os.path.join(self.gitdir, 'objects/info/alternates')
936 try:
937 fd = open(alt, 'rb')
938 try:
939 alt_dir = fd.readline().rstrip()
940 finally:
941 fd.close()
942 except IOError:
943 alt_dir = None
944 else:
945 alt_dir = None
946
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700947 if clone_bundle \
948 and alt_dir is None \
949 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700950 is_new = False
951
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700952 if not current_branch_only:
953 if self.sync_c:
954 current_branch_only = True
955 elif not self.manifest._loaded:
956 # Manifest cannot check defaults until it syncs.
957 current_branch_only = False
958 elif self.manifest.default.sync_c:
959 current_branch_only = True
960
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700961 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
962 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800964
965 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800966 self._InitMRef()
967 else:
968 self._InitMirrorHead()
969 try:
970 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
971 except OSError:
972 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800974
975 def PostRepoUpgrade(self):
976 self._InitHooks()
977
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978 def _CopyFiles(self):
979 for file in self.copyfiles:
980 file._Copy()
981
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700982 def GetRevisionId(self, all=None):
983 if self.revisionId:
984 return self.revisionId
985
986 rem = self.GetRemote(self.remote.name)
987 rev = rem.ToLocal(self.revisionExpr)
988
989 if all is not None and rev in all:
990 return all[rev]
991
992 try:
993 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
994 except GitError:
995 raise ManifestInvalidRevisionError(
996 'revision %s in %s not found' % (self.revisionExpr,
997 self.name))
998
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700999 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000 """Perform only the local IO portion of the sync process.
1001 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001003 all = self.bare_ref.all
1004 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001005 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001006
1007 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001008 head = self.work_git.GetHead()
1009 if head.startswith(R_HEADS):
1010 branch = head[len(R_HEADS):]
1011 try:
1012 head = all[head]
1013 except KeyError:
1014 head = None
1015 else:
1016 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001018 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001019 # Currently on a detached HEAD. The user is assumed to
1020 # not have any local modifications worth worrying about.
1021 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001022 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001023 syncbuf.fail(self, _PriorSyncFailedError())
1024 return
1025
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001026 if head == revid:
1027 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001028 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001029 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001030 if not syncbuf.detach_head:
1031 return
1032 else:
1033 lost = self._revlist(not_rev(revid), HEAD)
1034 if lost:
1035 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001036
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001038 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001039 except GitError, e:
1040 syncbuf.fail(self, e)
1041 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001043 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001044
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001045 if head == revid:
1046 # No changes; don't do anything further.
1047 #
1048 return
1049
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001050 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001052 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001054 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001056 syncbuf.info(self,
1057 "leaving %s; does not track upstream",
1058 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001060 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001061 except GitError, e:
1062 syncbuf.fail(self, e)
1063 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001065 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001067 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001068 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001070 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071 if not_merged:
1072 if upstream_gain:
1073 # The user has published this branch and some of those
1074 # commits are not yet merged upstream. We do not want
1075 # to rewrite the published commits so we punt.
1076 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001077 syncbuf.fail(self,
1078 "branch %s is published (but not merged) and is now %d commits behind"
1079 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001080 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001081 elif pub == head:
1082 # All published commits are merged, and thus we are a
1083 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001084 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001085 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001086 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001087 self._CopyFiles()
1088 syncbuf.later1(self, _doff)
1089 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001091 # Examine the local commits not in the remote. Find the
1092 # last one attributed to this user, if any.
1093 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001094 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001095 last_mine = None
1096 cnt_mine = 0
1097 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001098 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001099 if committer_email == self.UserEmail:
1100 last_mine = commit_id
1101 cnt_mine += 1
1102
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001103 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001104 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001105
1106 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001107 syncbuf.fail(self, _DirtyError())
1108 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001109
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001110 # If the upstream switched on us, warn the user.
1111 #
1112 if branch.merge != self.revisionExpr:
1113 if branch.merge and self.revisionExpr:
1114 syncbuf.info(self,
1115 'manifest switched %s...%s',
1116 branch.merge,
1117 self.revisionExpr)
1118 elif branch.merge:
1119 syncbuf.info(self,
1120 'manifest no longer tracks %s',
1121 branch.merge)
1122
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001123 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001124 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001125 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001127 syncbuf.info(self,
1128 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001129 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001130
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001131 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001132 if not ID_RE.match(self.revisionExpr):
1133 # in case of manifest sync the revisionExpr might be a SHA1
1134 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135 branch.Save()
1136
Mike Pontillod3153822012-02-28 11:53:24 -08001137 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001138 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001139 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001140 self._CopyFiles()
1141 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001142 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001143 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001144 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001145 self._CopyFiles()
1146 except GitError, e:
1147 syncbuf.fail(self, e)
1148 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001150 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001151 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001152 self._CopyFiles()
1153 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001154
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001155 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001156 # dest should already be an absolute path, but src is project relative
1157 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001158 abssrc = os.path.join(self.worktree, src)
1159 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160
James W. Mills24c13082012-04-12 15:04:13 -05001161 def AddAnnotation(self, name, value, keep):
1162 self.annotations.append(_Annotation(name, value, keep))
1163
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001164 def DownloadPatchSet(self, change_id, patch_id):
1165 """Download a single patch set of a single change to FETCH_HEAD.
1166 """
1167 remote = self.GetRemote(self.remote.name)
1168
1169 cmd = ['fetch', remote.name]
1170 cmd.append('refs/changes/%2.2d/%d/%d' \
1171 % (change_id % 100, change_id, patch_id))
1172 cmd.extend(map(lambda x: str(x), remote.fetch))
1173 if GitCommand(self, cmd, bare=True).Wait() != 0:
1174 return None
1175 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001176 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001177 change_id,
1178 patch_id,
1179 self.bare_git.rev_parse('FETCH_HEAD'))
1180
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001181
1182## Branch Management ##
1183
1184 def StartBranch(self, name):
1185 """Create a new branch off the manifest's revision.
1186 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001187 head = self.work_git.GetHead()
1188 if head == (R_HEADS + name):
1189 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001191 all = self.bare_ref.all
1192 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001193 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001194 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001195 capture_stdout = True,
1196 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001197
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001198 branch = self.GetBranch(name)
1199 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001200 branch.merge = self.revisionExpr
1201 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001202
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001203 if head.startswith(R_HEADS):
1204 try:
1205 head = all[head]
1206 except KeyError:
1207 head = None
1208
1209 if revid and head and revid == head:
1210 ref = os.path.join(self.gitdir, R_HEADS + name)
1211 try:
1212 os.makedirs(os.path.dirname(ref))
1213 except OSError:
1214 pass
1215 _lwrite(ref, '%s\n' % revid)
1216 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1217 'ref: %s%s\n' % (R_HEADS, name))
1218 branch.Save()
1219 return True
1220
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001221 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001222 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001223 capture_stdout = True,
1224 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001225 branch.Save()
1226 return True
1227 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001228
Wink Saville02d79452009-04-10 13:01:24 -07001229 def CheckoutBranch(self, name):
1230 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001231
1232 Args:
1233 name: The name of the branch to checkout.
1234
1235 Returns:
1236 True if the checkout succeeded; False if it didn't; None if the branch
1237 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001238 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001239 rev = R_HEADS + name
1240 head = self.work_git.GetHead()
1241 if head == rev:
1242 # Already on the branch
1243 #
1244 return True
Wink Saville02d79452009-04-10 13:01:24 -07001245
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001246 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001247 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001248 revid = all[rev]
1249 except KeyError:
1250 # Branch does not exist in this project
1251 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001252 return None
Wink Saville02d79452009-04-10 13:01:24 -07001253
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001254 if head.startswith(R_HEADS):
1255 try:
1256 head = all[head]
1257 except KeyError:
1258 head = None
1259
1260 if head == revid:
1261 # Same revision; just update HEAD to point to the new
1262 # target branch, but otherwise take no other action.
1263 #
1264 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1265 'ref: %s%s\n' % (R_HEADS, name))
1266 return True
1267
1268 return GitCommand(self,
1269 ['checkout', name, '--'],
1270 capture_stdout = True,
1271 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001272
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001273 def AbandonBranch(self, name):
1274 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001275
1276 Args:
1277 name: The name of the branch to abandon.
1278
1279 Returns:
1280 True if the abandon succeeded; False if it didn't; None if the branch
1281 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001282 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001283 rev = R_HEADS + name
1284 all = self.bare_ref.all
1285 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001286 # Doesn't exist
1287 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001288
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001289 head = self.work_git.GetHead()
1290 if head == rev:
1291 # We can't destroy the branch while we are sitting
1292 # on it. Switch to a detached HEAD.
1293 #
1294 head = all[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001295
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001296 revid = self.GetRevisionId(all)
1297 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001298 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1299 '%s\n' % revid)
1300 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001301 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001302
1303 return GitCommand(self,
1304 ['branch', '-D', name],
1305 capture_stdout = True,
1306 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001307
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308 def PruneHeads(self):
1309 """Prune any topic branches already merged into upstream.
1310 """
1311 cb = self.CurrentBranch
1312 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001313 left = self._allrefs
1314 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315 if name.startswith(R_HEADS):
1316 name = name[len(R_HEADS):]
1317 if cb is None or name != cb:
1318 kill.append(name)
1319
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001320 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 if cb is not None \
1322 and not self._revlist(HEAD + '...' + rev) \
1323 and not self.IsDirty(consider_untracked = False):
1324 self.work_git.DetachHead(HEAD)
1325 kill.append(cb)
1326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001328 old = self.bare_git.GetHead()
1329 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1331
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001332 try:
1333 self.bare_git.DetachHead(rev)
1334
1335 b = ['branch', '-d']
1336 b.extend(kill)
1337 b = GitCommand(self, b, bare=True,
1338 capture_stdout=True,
1339 capture_stderr=True)
1340 b.Wait()
1341 finally:
1342 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001343 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001345 for branch in kill:
1346 if (R_HEADS + branch) not in left:
1347 self.CleanPublishedCache()
1348 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001349
1350 if cb and cb not in kill:
1351 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001352 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353
1354 kept = []
1355 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001356 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357 branch = self.GetBranch(branch)
1358 base = branch.LocalMerge
1359 if not base:
1360 base = rev
1361 kept.append(ReviewableBranch(self, branch, base))
1362 return kept
1363
1364
1365## Direct Git Commands ##
1366
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001367 def _RemoteFetch(self, name=None,
1368 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001369 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001370 quiet=False,
1371 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001372
1373 is_sha1 = False
1374 tag_name = None
1375
1376 if current_branch_only:
1377 if ID_RE.match(self.revisionExpr) is not None:
1378 is_sha1 = True
1379 elif self.revisionExpr.startswith(R_TAGS):
1380 # this is a tag and its sha1 value should never change
1381 tag_name = self.revisionExpr[len(R_TAGS):]
1382
1383 if is_sha1 or tag_name is not None:
1384 try:
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001385 # if revision (sha or tag) is not present then following function
1386 # throws an error.
1387 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001388 return True
Anatol Pomazaub962a1f2012-03-29 18:06:43 -07001389 except GitError:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001390 # There is no such persistent revision. We have to fetch it.
1391 pass
1392
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393 if not name:
1394 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001395
1396 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001397 remote = self.GetRemote(name)
1398 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001399 ssh_proxy = True
1400
Shawn O. Pearce88443382010-10-08 10:02:09 +02001401 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001402 if alt_dir and 'objects' == os.path.basename(alt_dir):
1403 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001404 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1405 remote = self.GetRemote(name)
1406
1407 all = self.bare_ref.all
1408 ids = set(all.values())
1409 tmp = set()
1410
1411 for r, id in GitRefs(ref_dir).all.iteritems():
1412 if r not in all:
1413 if r.startswith(R_TAGS) or remote.WritesTo(r):
1414 all[r] = id
1415 ids.add(id)
1416 continue
1417
1418 if id in ids:
1419 continue
1420
1421 r = 'refs/_alt/%s' % id
1422 all[r] = id
1423 ids.add(id)
1424 tmp.add(r)
1425
1426 ref_names = list(all.keys())
1427 ref_names.sort()
1428
1429 tmp_packed = ''
1430 old_packed = ''
1431
1432 for r in ref_names:
1433 line = '%s %s\n' % (all[r], r)
1434 tmp_packed += line
1435 if r not in tmp:
1436 old_packed += line
1437
1438 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001439 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001440 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001441
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001442 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001443
1444 # The --depth option only affects the initial fetch; after that we'll do
1445 # full fetches of changes.
1446 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1447 if depth and initial:
1448 cmd.append('--depth=%s' % depth)
1449
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001450 if quiet:
1451 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001452 if not self.worktree:
1453 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001454 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001455
1456 if not current_branch_only or is_sha1:
1457 # Fetch whole repo
1458 cmd.append('--tags')
1459 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1460 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001461 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001462 cmd.append(tag_name)
1463 else:
1464 branch = self.revisionExpr
1465 if branch.startswith(R_HEADS):
1466 branch = branch[len(R_HEADS):]
1467 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001468
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001469 ok = False
1470 for i in range(2):
1471 if GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait() == 0:
1472 ok = True
1473 break
1474 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001475
1476 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001477 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001478 if old_packed != '':
1479 _lwrite(packed_refs, old_packed)
1480 else:
1481 os.remove(packed_refs)
1482 self.bare_git.pack_refs('--all', '--prune')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001483 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001484
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001485 def _ApplyCloneBundle(self, initial=False, quiet=False):
1486 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1487 return False
1488
1489 remote = self.GetRemote(self.remote.name)
1490 bundle_url = remote.url + '/clone.bundle'
1491 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001492 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1493 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001494 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1495 return False
1496
1497 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1498 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1499
1500 exist_dst = os.path.exists(bundle_dst)
1501 exist_tmp = os.path.exists(bundle_tmp)
1502
1503 if not initial and not exist_dst and not exist_tmp:
1504 return False
1505
1506 if not exist_dst:
1507 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1508 if not exist_dst:
1509 return False
1510
1511 cmd = ['fetch']
1512 if quiet:
1513 cmd.append('--quiet')
1514 if not self.worktree:
1515 cmd.append('--update-head-ok')
1516 cmd.append(bundle_dst)
1517 for f in remote.fetch:
1518 cmd.append(str(f))
1519 cmd.append('refs/tags/*:refs/tags/*')
1520
1521 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001522 if os.path.exists(bundle_dst):
1523 os.remove(bundle_dst)
1524 if os.path.exists(bundle_tmp):
1525 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001526 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001527
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001528 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001529 if os.path.exists(dstPath):
1530 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001531
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001532 cmd = ['curl', '--output', tmpPath, '--netrc', '--location']
1533 if quiet:
1534 cmd += ['--silent']
1535 if os.path.exists(tmpPath):
1536 size = os.stat(tmpPath).st_size
1537 if size >= 1024:
1538 cmd += ['--continue-at', '%d' % (size,)]
1539 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001540 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001541 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1542 cmd += ['--proxy', os.environ['http_proxy']]
1543 cmd += [srcUrl]
1544
1545 if IsTrace():
1546 Trace('%s', ' '.join(cmd))
1547 try:
1548 proc = subprocess.Popen(cmd)
1549 except OSError:
1550 return False
1551
1552 ok = proc.wait() == 0
1553 if os.path.exists(tmpPath):
1554 if ok and os.stat(tmpPath).st_size > 16:
1555 os.rename(tmpPath, dstPath)
1556 return True
1557 else:
1558 os.remove(tmpPath)
1559 return False
1560 else:
1561 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001562
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001563 def _Checkout(self, rev, quiet=False):
1564 cmd = ['checkout']
1565 if quiet:
1566 cmd.append('-q')
1567 cmd.append(rev)
1568 cmd.append('--')
1569 if GitCommand(self, cmd).Wait() != 0:
1570 if self._allrefs:
1571 raise GitError('%s checkout %s ' % (self.name, rev))
1572
Pierre Tardye5a21222011-03-24 16:28:18 +01001573 def _CherryPick(self, rev, quiet=False):
1574 cmd = ['cherry-pick']
1575 cmd.append(rev)
1576 cmd.append('--')
1577 if GitCommand(self, cmd).Wait() != 0:
1578 if self._allrefs:
1579 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1580
Erwan Mahea94f1622011-08-19 13:56:09 +02001581 def _Revert(self, rev, quiet=False):
1582 cmd = ['revert']
1583 cmd.append('--no-edit')
1584 cmd.append(rev)
1585 cmd.append('--')
1586 if GitCommand(self, cmd).Wait() != 0:
1587 if self._allrefs:
1588 raise GitError('%s revert %s ' % (self.name, rev))
1589
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001590 def _ResetHard(self, rev, quiet=True):
1591 cmd = ['reset', '--hard']
1592 if quiet:
1593 cmd.append('-q')
1594 cmd.append(rev)
1595 if GitCommand(self, cmd).Wait() != 0:
1596 raise GitError('%s reset --hard %s ' % (self.name, rev))
1597
1598 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001599 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001600 if onto is not None:
1601 cmd.extend(['--onto', onto])
1602 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001603 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001604 raise GitError('%s rebase %s ' % (self.name, upstream))
1605
Pierre Tardy3d125942012-05-04 12:18:12 +02001606 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001607 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001608 if ffonly:
1609 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001610 if GitCommand(self, cmd).Wait() != 0:
1611 raise GitError('%s merge %s ' % (self.name, head))
1612
1613 def _InitGitDir(self):
1614 if not os.path.exists(self.gitdir):
1615 os.makedirs(self.gitdir)
1616 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001617
Shawn O. Pearce88443382010-10-08 10:02:09 +02001618 mp = self.manifest.manifestProject
1619 ref_dir = mp.config.GetString('repo.reference')
1620
1621 if ref_dir:
1622 mirror_git = os.path.join(ref_dir, self.name + '.git')
1623 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1624 self.relpath + '.git')
1625
1626 if os.path.exists(mirror_git):
1627 ref_dir = mirror_git
1628
1629 elif os.path.exists(repo_git):
1630 ref_dir = repo_git
1631
1632 else:
1633 ref_dir = None
1634
1635 if ref_dir:
1636 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1637 os.path.join(ref_dir, 'objects') + '\n')
1638
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001639 if self.manifest.IsMirror:
1640 self.config.SetString('core.bare', 'true')
1641 else:
1642 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001643
1644 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001645 try:
1646 to_rm = os.listdir(hooks)
1647 except OSError:
1648 to_rm = []
1649 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001650 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001651 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001652
1653 m = self.manifest.manifestProject.config
1654 for key in ['user.name', 'user.email']:
1655 if m.Has(key, include_defaults = False):
1656 self.config.SetString(key, m.GetString(key))
1657
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001658 def _InitHooks(self):
1659 hooks = self._gitdir_path('hooks')
1660 if not os.path.exists(hooks):
1661 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001662 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001663 name = os.path.basename(stock_hook)
1664
Victor Boivie65e0f352011-04-18 11:23:29 +02001665 if name in ('commit-msg',) and not self.remote.review \
1666 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001667 # Don't install a Gerrit Code Review hook if this
1668 # project does not appear to use it for reviews.
1669 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001670 # Since the manifest project is one of those, but also
1671 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001672 continue
1673
1674 dst = os.path.join(hooks, name)
1675 if os.path.islink(dst):
1676 continue
1677 if os.path.exists(dst):
1678 if filecmp.cmp(stock_hook, dst, shallow=False):
1679 os.remove(dst)
1680 else:
1681 _error("%s: Not replacing %s hook", self.relpath, name)
1682 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001683 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001684 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001685 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001686 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001687 raise GitError('filesystem must support symlinks')
1688 else:
1689 raise
1690
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001692 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001693 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001694 remote.url = self.remote.url
1695 remote.review = self.remote.review
1696 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001697
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001698 if self.worktree:
1699 remote.ResetFetch(mirror=False)
1700 else:
1701 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001702 remote.Save()
1703
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001704 def _InitMRef(self):
1705 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001706 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001707
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001708 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001709 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001710
1711 def _InitAnyMRef(self, ref):
1712 cur = self.bare_ref.symref(ref)
1713
1714 if self.revisionId:
1715 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1716 msg = 'manifest set to %s' % self.revisionId
1717 dst = self.revisionId + '^0'
1718 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1719 else:
1720 remote = self.GetRemote(self.remote.name)
1721 dst = remote.ToLocal(self.revisionExpr)
1722 if cur != dst:
1723 msg = 'manifest set to %s' % self.revisionExpr
1724 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001725
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001726 def _InitWorkTree(self):
1727 dotgit = os.path.join(self.worktree, '.git')
1728 if not os.path.exists(dotgit):
1729 os.makedirs(dotgit)
1730
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001731 for name in ['config',
1732 'description',
1733 'hooks',
1734 'info',
1735 'logs',
1736 'objects',
1737 'packed-refs',
1738 'refs',
1739 'rr-cache',
1740 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001741 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001742 src = os.path.join(self.gitdir, name)
1743 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001744 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001745 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001746 else:
1747 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001748 except OSError, e:
1749 if e.errno == errno.EPERM:
1750 raise GitError('filesystem must support symlinks')
1751 else:
1752 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001753
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001754 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001755
1756 cmd = ['read-tree', '--reset', '-u']
1757 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001758 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001759 if GitCommand(self, cmd).Wait() != 0:
1760 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001761
1762 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1763 if not os.path.exists(rr_cache):
1764 os.makedirs(rr_cache)
1765
Shawn O. Pearce93609662009-04-21 10:50:33 -07001766 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001767
1768 def _gitdir_path(self, path):
1769 return os.path.join(self.gitdir, path)
1770
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001771 def _revlist(self, *args, **kw):
1772 a = []
1773 a.extend(args)
1774 a.append('--')
1775 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001776
1777 @property
1778 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001779 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780
1781 class _GitGetByExec(object):
1782 def __init__(self, project, bare):
1783 self._project = project
1784 self._bare = bare
1785
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001786 def LsOthers(self):
1787 p = GitCommand(self._project,
1788 ['ls-files',
1789 '-z',
1790 '--others',
1791 '--exclude-standard'],
1792 bare = False,
1793 capture_stdout = True,
1794 capture_stderr = True)
1795 if p.Wait() == 0:
1796 out = p.stdout
1797 if out:
1798 return out[:-1].split("\0")
1799 return []
1800
1801 def DiffZ(self, name, *args):
1802 cmd = [name]
1803 cmd.append('-z')
1804 cmd.extend(args)
1805 p = GitCommand(self._project,
1806 cmd,
1807 bare = False,
1808 capture_stdout = True,
1809 capture_stderr = True)
1810 try:
1811 out = p.process.stdout.read()
1812 r = {}
1813 if out:
1814 out = iter(out[:-1].split('\0'))
1815 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001816 try:
1817 info = out.next()
1818 path = out.next()
1819 except StopIteration:
1820 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001821
1822 class _Info(object):
1823 def __init__(self, path, omode, nmode, oid, nid, state):
1824 self.path = path
1825 self.src_path = None
1826 self.old_mode = omode
1827 self.new_mode = nmode
1828 self.old_id = oid
1829 self.new_id = nid
1830
1831 if len(state) == 1:
1832 self.status = state
1833 self.level = None
1834 else:
1835 self.status = state[:1]
1836 self.level = state[1:]
1837 while self.level.startswith('0'):
1838 self.level = self.level[1:]
1839
1840 info = info[1:].split(' ')
1841 info =_Info(path, *info)
1842 if info.status in ('R', 'C'):
1843 info.src_path = info.path
1844 info.path = out.next()
1845 r[info.path] = info
1846 return r
1847 finally:
1848 p.Wait()
1849
1850 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001851 if self._bare:
1852 path = os.path.join(self._project.gitdir, HEAD)
1853 else:
1854 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001855 fd = open(path, 'rb')
1856 try:
1857 line = fd.read()
1858 finally:
1859 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001860 if line.startswith('ref: '):
1861 return line[5:-1]
1862 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001863
1864 def SetHead(self, ref, message=None):
1865 cmdv = []
1866 if message is not None:
1867 cmdv.extend(['-m', message])
1868 cmdv.append(HEAD)
1869 cmdv.append(ref)
1870 self.symbolic_ref(*cmdv)
1871
1872 def DetachHead(self, new, message=None):
1873 cmdv = ['--no-deref']
1874 if message is not None:
1875 cmdv.extend(['-m', message])
1876 cmdv.append(HEAD)
1877 cmdv.append(new)
1878 self.update_ref(*cmdv)
1879
1880 def UpdateRef(self, name, new, old=None,
1881 message=None,
1882 detach=False):
1883 cmdv = []
1884 if message is not None:
1885 cmdv.extend(['-m', message])
1886 if detach:
1887 cmdv.append('--no-deref')
1888 cmdv.append(name)
1889 cmdv.append(new)
1890 if old is not None:
1891 cmdv.append(old)
1892 self.update_ref(*cmdv)
1893
1894 def DeleteRef(self, name, old=None):
1895 if not old:
1896 old = self.rev_parse(name)
1897 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001898 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001899
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001900 def rev_list(self, *args, **kw):
1901 if 'format' in kw:
1902 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1903 else:
1904 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001905 cmdv.extend(args)
1906 p = GitCommand(self._project,
1907 cmdv,
1908 bare = self._bare,
1909 capture_stdout = True,
1910 capture_stderr = True)
1911 r = []
1912 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001913 if line[-1] == '\n':
1914 line = line[:-1]
1915 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001916 if p.Wait() != 0:
1917 raise GitError('%s rev-list %s: %s' % (
1918 self._project.name,
1919 str(args),
1920 p.stderr))
1921 return r
1922
1923 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001924 """Allow arbitrary git commands using pythonic syntax.
1925
1926 This allows you to do things like:
1927 git_obj.rev_parse('HEAD')
1928
1929 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1930 run. We'll replace the '_' with a '-' and try to run a git command.
1931 Any other arguments will be passed to the git command.
1932
1933 Args:
1934 name: The name of the git command to call. Any '_' characters will
1935 be replaced with '-'.
1936
1937 Returns:
1938 A callable object that will try to call git with the named command.
1939 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001940 name = name.replace('_', '-')
1941 def runner(*args):
1942 cmdv = [name]
1943 cmdv.extend(args)
1944 p = GitCommand(self._project,
1945 cmdv,
1946 bare = self._bare,
1947 capture_stdout = True,
1948 capture_stderr = True)
1949 if p.Wait() != 0:
1950 raise GitError('%s %s: %s' % (
1951 self._project.name,
1952 name,
1953 p.stderr))
1954 r = p.stdout
1955 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1956 return r[:-1]
1957 return r
1958 return runner
1959
1960
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001961class _PriorSyncFailedError(Exception):
1962 def __str__(self):
1963 return 'prior sync failed; rebase still in progress'
1964
1965class _DirtyError(Exception):
1966 def __str__(self):
1967 return 'contains uncommitted changes'
1968
1969class _InfoMessage(object):
1970 def __init__(self, project, text):
1971 self.project = project
1972 self.text = text
1973
1974 def Print(self, syncbuf):
1975 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1976 syncbuf.out.nl()
1977
1978class _Failure(object):
1979 def __init__(self, project, why):
1980 self.project = project
1981 self.why = why
1982
1983 def Print(self, syncbuf):
1984 syncbuf.out.fail('error: %s/: %s',
1985 self.project.relpath,
1986 str(self.why))
1987 syncbuf.out.nl()
1988
1989class _Later(object):
1990 def __init__(self, project, action):
1991 self.project = project
1992 self.action = action
1993
1994 def Run(self, syncbuf):
1995 out = syncbuf.out
1996 out.project('project %s/', self.project.relpath)
1997 out.nl()
1998 try:
1999 self.action()
2000 out.nl()
2001 return True
2002 except GitError, e:
2003 out.nl()
2004 return False
2005
2006class _SyncColoring(Coloring):
2007 def __init__(self, config):
2008 Coloring.__init__(self, config, 'reposync')
2009 self.project = self.printer('header', attr = 'bold')
2010 self.info = self.printer('info')
2011 self.fail = self.printer('fail', fg='red')
2012
2013class SyncBuffer(object):
2014 def __init__(self, config, detach_head=False):
2015 self._messages = []
2016 self._failures = []
2017 self._later_queue1 = []
2018 self._later_queue2 = []
2019
2020 self.out = _SyncColoring(config)
2021 self.out.redirect(sys.stderr)
2022
2023 self.detach_head = detach_head
2024 self.clean = True
2025
2026 def info(self, project, fmt, *args):
2027 self._messages.append(_InfoMessage(project, fmt % args))
2028
2029 def fail(self, project, err=None):
2030 self._failures.append(_Failure(project, err))
2031 self.clean = False
2032
2033 def later1(self, project, what):
2034 self._later_queue1.append(_Later(project, what))
2035
2036 def later2(self, project, what):
2037 self._later_queue2.append(_Later(project, what))
2038
2039 def Finish(self):
2040 self._PrintMessages()
2041 self._RunLater()
2042 self._PrintMessages()
2043 return self.clean
2044
2045 def _RunLater(self):
2046 for q in ['_later_queue1', '_later_queue2']:
2047 if not self._RunQueue(q):
2048 return
2049
2050 def _RunQueue(self, queue):
2051 for m in getattr(self, queue):
2052 if not m.Run(self):
2053 self.clean = False
2054 return False
2055 setattr(self, queue, [])
2056 return True
2057
2058 def _PrintMessages(self):
2059 for m in self._messages:
2060 m.Print(self)
2061 for m in self._failures:
2062 m.Print(self)
2063
2064 self._messages = []
2065 self._failures = []
2066
2067
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002068class MetaProject(Project):
2069 """A special project housed under .repo.
2070 """
2071 def __init__(self, manifest, name, gitdir, worktree):
2072 repodir = manifest.repodir
2073 Project.__init__(self,
2074 manifest = manifest,
2075 name = name,
2076 gitdir = gitdir,
2077 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002078 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002079 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002080 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002081 revisionId = None,
2082 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002083
2084 def PreSync(self):
2085 if self.Exists:
2086 cb = self.CurrentBranch
2087 if cb:
2088 base = self.GetBranch(cb).merge
2089 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002090 self.revisionExpr = base
2091 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002092
Florian Vallee5d016502012-06-07 17:19:26 +02002093 def MetaBranchSwitch(self, target):
2094 """ Prepare MetaProject for manifest branch switch
2095 """
2096
2097 # detach and delete manifest branch, allowing a new
2098 # branch to take over
2099 syncbuf = SyncBuffer(self.config, detach_head = True)
2100 self.Sync_LocalHalf(syncbuf)
2101 syncbuf.Finish()
2102
2103 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002104 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002105 capture_stdout = True,
2106 capture_stderr = True).Wait() == 0
2107
2108
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002109 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002110 def LastFetch(self):
2111 try:
2112 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2113 return os.path.getmtime(fh)
2114 except OSError:
2115 return 0
2116
2117 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002118 def HasChanges(self):
2119 """Has the remote received new commits not yet checked out?
2120 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002121 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002122 return False
2123
2124 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002125 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002126 head = self.work_git.GetHead()
2127 if head.startswith(R_HEADS):
2128 try:
2129 head = all[head]
2130 except KeyError:
2131 head = None
2132
2133 if revid == head:
2134 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002135 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002136 return True
2137 return False