blob: 04c43bb76c2bc6835fb1680e15d7994e8b7ee1ab [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,
Brian Harring14a66742012-09-28 20:21:57 -0700487 sync_c = False,
488 upstream = None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700489 self.manifest = manifest
490 self.name = name
491 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800492 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800493 if worktree:
494 self.worktree = worktree.replace('\\', '/')
495 else:
496 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700497 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700498 self.revisionExpr = revisionExpr
499
500 if revisionId is None \
501 and revisionExpr \
502 and IsId(revisionExpr):
503 self.revisionId = revisionExpr
504 else:
505 self.revisionId = revisionId
506
Mike Pontillod3153822012-02-28 11:53:24 -0800507 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700508 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700509 self.sync_c = sync_c
Brian Harring14a66742012-09-28 20:21:57 -0700510 self.upstream = upstream
Mike Pontillod3153822012-02-28 11:53:24 -0800511
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700513 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500514 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700515 self.config = GitConfig.ForRepository(
516 gitdir = self.gitdir,
517 defaults = self.manifest.globalConfig)
518
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800519 if self.worktree:
520 self.work_git = self._GitGetByExec(self, bare=False)
521 else:
522 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700523 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700524 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700525
Doug Anderson37282b42011-03-04 11:54:18 -0800526 # This will be filled in if a project is later identified to be the
527 # project containing repo hooks.
528 self.enabled_repo_hooks = []
529
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700530 @property
531 def Exists(self):
532 return os.path.isdir(self.gitdir)
533
534 @property
535 def CurrentBranch(self):
536 """Obtain the name of the currently checked out branch.
537 The branch name omits the 'refs/heads/' prefix.
538 None is returned if the project is on a detached HEAD.
539 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700540 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700541 if b.startswith(R_HEADS):
542 return b[len(R_HEADS):]
543 return None
544
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700545 def IsRebaseInProgress(self):
546 w = self.worktree
547 g = os.path.join(w, '.git')
548 return os.path.exists(os.path.join(g, 'rebase-apply')) \
549 or os.path.exists(os.path.join(g, 'rebase-merge')) \
550 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200551
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552 def IsDirty(self, consider_untracked=True):
553 """Is the working directory modified in some way?
554 """
555 self.work_git.update_index('-q',
556 '--unmerged',
557 '--ignore-missing',
558 '--refresh')
559 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
560 return True
561 if self.work_git.DiffZ('diff-files'):
562 return True
563 if consider_untracked and self.work_git.LsOthers():
564 return True
565 return False
566
567 _userident_name = None
568 _userident_email = None
569
570 @property
571 def UserName(self):
572 """Obtain the user's personal name.
573 """
574 if self._userident_name is None:
575 self._LoadUserIdentity()
576 return self._userident_name
577
578 @property
579 def UserEmail(self):
580 """Obtain the user's email address. This is very likely
581 to be their Gerrit login.
582 """
583 if self._userident_email is None:
584 self._LoadUserIdentity()
585 return self._userident_email
586
587 def _LoadUserIdentity(self):
588 u = self.bare_git.var('GIT_COMMITTER_IDENT')
589 m = re.compile("^(.*) <([^>]*)> ").match(u)
590 if m:
591 self._userident_name = m.group(1)
592 self._userident_email = m.group(2)
593 else:
594 self._userident_name = ''
595 self._userident_email = ''
596
597 def GetRemote(self, name):
598 """Get the configuration for a single remote.
599 """
600 return self.config.GetRemote(name)
601
602 def GetBranch(self, name):
603 """Get the configuration for a single branch.
604 """
605 return self.config.GetBranch(name)
606
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700607 def GetBranches(self):
608 """Get all existing local branches.
609 """
610 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700611 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700612 heads = {}
613 pubd = {}
614
615 for name, id in all.iteritems():
616 if name.startswith(R_HEADS):
617 name = name[len(R_HEADS):]
618 b = self.GetBranch(name)
619 b.current = name == current
620 b.published = None
621 b.revision = id
622 heads[name] = b
623
624 for name, id in all.iteritems():
625 if name.startswith(R_PUB):
626 name = name[len(R_PUB):]
627 b = heads.get(name)
628 if b:
629 b.published = id
630
631 return heads
632
Colin Cross5acde752012-03-28 20:15:45 -0700633 def MatchesGroups(self, manifest_groups):
634 """Returns true if the manifest groups specified at init should cause
635 this project to be synced.
636 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700637 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700638
Conley Owens971de8e2012-04-16 10:36:08 -0700639 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700640 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700641 manifest_groups: "-group1,group2"
642 the project will be matched.
643 """
Conley Owensbb1b5f52012-08-13 13:11:18 -0700644 expanded_manifest_groups = manifest_groups or ['all', '-notdefault']
645 expanded_project_groups = ['all'] + (self.groups or [])
646
Conley Owens971de8e2012-04-16 10:36:08 -0700647 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700648 for group in expanded_manifest_groups:
649 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700650 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700651 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700652 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700653
Conley Owens971de8e2012-04-16 10:36:08 -0700654 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700655
656## Status Display ##
657
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500658 def HasChanges(self):
659 """Returns true if there are uncommitted changes.
660 """
661 self.work_git.update_index('-q',
662 '--unmerged',
663 '--ignore-missing',
664 '--refresh')
665 if self.IsRebaseInProgress():
666 return True
667
668 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
669 return True
670
671 if self.work_git.DiffZ('diff-files'):
672 return True
673
674 if self.work_git.LsOthers():
675 return True
676
677 return False
678
Terence Haddock4655e812011-03-31 12:33:34 +0200679 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700680 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200681
682 Args:
683 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700684 """
685 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200686 if output_redir == None:
687 output_redir = sys.stdout
688 print >>output_redir, ''
689 print >>output_redir, 'project %s/' % self.relpath
690 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700691 return
692
693 self.work_git.update_index('-q',
694 '--unmerged',
695 '--ignore-missing',
696 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700697 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700698 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
699 df = self.work_git.DiffZ('diff-files')
700 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100701 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700702 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700703
704 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200705 if not output_redir == None:
706 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700707 out.project('project %-40s', self.relpath + '/')
708
709 branch = self.CurrentBranch
710 if branch is None:
711 out.nobranch('(*** NO BRANCH ***)')
712 else:
713 out.branch('branch %s', branch)
714 out.nl()
715
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700716 if rb:
717 out.important('prior sync failed; rebase still in progress')
718 out.nl()
719
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720 paths = list()
721 paths.extend(di.keys())
722 paths.extend(df.keys())
723 paths.extend(do)
724
725 paths = list(set(paths))
726 paths.sort()
727
728 for p in paths:
729 try: i = di[p]
730 except KeyError: i = None
731
732 try: f = df[p]
733 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200734
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735 if i: i_status = i.status.upper()
736 else: i_status = '-'
737
738 if f: f_status = f.status.lower()
739 else: f_status = '-'
740
741 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800742 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743 i.src_path, p, i.level)
744 else:
745 line = ' %s%s\t%s' % (i_status, f_status, p)
746
747 if i and not f:
748 out.added('%s', line)
749 elif (i and f) or (not i and f):
750 out.changed('%s', line)
751 elif not i and not f:
752 out.untracked('%s', line)
753 else:
754 out.write('%s', line)
755 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200756
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700757 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700758
pelyad67872d2012-03-28 14:49:58 +0300759 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700760 """Prints the status of the repository to stdout.
761 """
762 out = DiffColoring(self.config)
763 cmd = ['diff']
764 if out.is_on:
765 cmd.append('--color')
766 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300767 if absolute_paths:
768 cmd.append('--src-prefix=a/%s/' % self.relpath)
769 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770 cmd.append('--')
771 p = GitCommand(self,
772 cmd,
773 capture_stdout = True,
774 capture_stderr = True)
775 has_diff = False
776 for line in p.process.stdout:
777 if not has_diff:
778 out.nl()
779 out.project('project %s/' % self.relpath)
780 out.nl()
781 has_diff = True
782 print line[:-1]
783 p.Wait()
784
785
786## Publish / Upload ##
787
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700788 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700789 """Was the branch published (uploaded) for code review?
790 If so, returns the SHA-1 hash of the last published
791 state for the branch.
792 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700793 key = R_PUB + branch
794 if all is None:
795 try:
796 return self.bare_git.rev_parse(key)
797 except GitError:
798 return None
799 else:
800 try:
801 return all[key]
802 except KeyError:
803 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700804
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700805 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700806 """Prunes any stale published refs.
807 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700808 if all is None:
809 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810 heads = set()
811 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700812 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700813 if name.startswith(R_HEADS):
814 heads.add(name)
815 elif name.startswith(R_PUB):
816 canrm[name] = id
817
818 for name, id in canrm.iteritems():
819 n = name[len(R_PUB):]
820 if R_HEADS + n not in heads:
821 self.bare_git.DeleteRef(name, id)
822
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700823 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824 """List any branches which can be uploaded for review.
825 """
826 heads = {}
827 pubed = {}
828
829 for name, id in self._allrefs.iteritems():
830 if name.startswith(R_HEADS):
831 heads[name[len(R_HEADS):]] = id
832 elif name.startswith(R_PUB):
833 pubed[name[len(R_PUB):]] = id
834
835 ready = []
836 for branch, id in heads.iteritems():
837 if branch in pubed and pubed[branch] == id:
838 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700839 if selected_branch and branch != selected_branch:
840 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800842 rb = self.GetUploadableBranch(branch)
843 if rb:
844 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845 return ready
846
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800847 def GetUploadableBranch(self, branch_name):
848 """Get a single uploadable branch, or None.
849 """
850 branch = self.GetBranch(branch_name)
851 base = branch.LocalMerge
852 if branch.LocalMerge:
853 rb = ReviewableBranch(self, branch, base)
854 if rb.commits:
855 return rb
856 return None
857
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700858 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700859 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700860 auto_topic=False,
861 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862 """Uploads the named branch for code review.
863 """
864 if branch is None:
865 branch = self.CurrentBranch
866 if branch is None:
867 raise GitError('not currently on a branch')
868
869 branch = self.GetBranch(branch)
870 if not branch.LocalMerge:
871 raise GitError('branch %s does not track a remote' % branch.name)
872 if not branch.remote.review:
873 raise GitError('remote %s has no review url' % branch.remote.name)
874
875 dest_branch = branch.merge
876 if not dest_branch.startswith(R_HEADS):
877 dest_branch = R_HEADS + dest_branch
878
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800879 if not branch.remote.projectname:
880 branch.remote.projectname = self.name
881 branch.remote.Save()
882
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800883 url = branch.remote.ReviewUrl(self.UserEmail)
884 if url is None:
885 raise UploadError('review not configured')
886 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800887
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800888 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800889 rp = ['gerrit receive-pack']
890 for e in people[0]:
891 rp.append('--reviewer=%s' % sq(e))
892 for e in people[1]:
893 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800894 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700895
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800896 cmd.append(url)
897
898 if dest_branch.startswith(R_HEADS):
899 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700900
901 upload_type = 'for'
902 if draft:
903 upload_type = 'drafts'
904
905 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
906 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800907 if auto_topic:
908 ref_spec = ref_spec + '/' + branch.name
909 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800910
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800911 if GitCommand(self, cmd, bare = True).Wait() != 0:
912 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913
914 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
915 self.bare_git.UpdateRef(R_PUB + branch.name,
916 R_HEADS + branch.name,
917 message = msg)
918
919
920## Sync ##
921
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700922 def Sync_NetworkHalf(self,
923 quiet=False,
924 is_new=None,
925 current_branch_only=False,
926 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 """Perform only the network IO portion of the sync process.
928 Local working directory/branch state is not affected.
929 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700930 if is_new is None:
931 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200932 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933 self._InitGitDir()
934 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700935
936 if is_new:
937 alt = os.path.join(self.gitdir, 'objects/info/alternates')
938 try:
939 fd = open(alt, 'rb')
940 try:
941 alt_dir = fd.readline().rstrip()
942 finally:
943 fd.close()
944 except IOError:
945 alt_dir = None
946 else:
947 alt_dir = None
948
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700949 if clone_bundle \
950 and alt_dir is None \
951 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700952 is_new = False
953
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700954 if not current_branch_only:
955 if self.sync_c:
956 current_branch_only = True
957 elif not self.manifest._loaded:
958 # Manifest cannot check defaults until it syncs.
959 current_branch_only = False
960 elif self.manifest.default.sync_c:
961 current_branch_only = True
962
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700963 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
964 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700965 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800966
967 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800968 self._InitMRef()
969 else:
970 self._InitMirrorHead()
971 try:
972 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
973 except OSError:
974 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700975 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800976
977 def PostRepoUpgrade(self):
978 self._InitHooks()
979
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980 def _CopyFiles(self):
981 for file in self.copyfiles:
982 file._Copy()
983
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700984 def GetRevisionId(self, all=None):
985 if self.revisionId:
986 return self.revisionId
987
988 rem = self.GetRemote(self.remote.name)
989 rev = rem.ToLocal(self.revisionExpr)
990
991 if all is not None and rev in all:
992 return all[rev]
993
994 try:
995 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
996 except GitError:
997 raise ManifestInvalidRevisionError(
998 'revision %s in %s not found' % (self.revisionExpr,
999 self.name))
1000
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001001 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002 """Perform only the local IO portion of the sync process.
1003 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001004 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001005 all = self.bare_ref.all
1006 self.CleanPublishedCache(all)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001007 revid = self.GetRevisionId(all)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001008
1009 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001010 head = self.work_git.GetHead()
1011 if head.startswith(R_HEADS):
1012 branch = head[len(R_HEADS):]
1013 try:
1014 head = all[head]
1015 except KeyError:
1016 head = None
1017 else:
1018 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001019
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001020 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001021 # Currently on a detached HEAD. The user is assumed to
1022 # not have any local modifications worth worrying about.
1023 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001024 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001025 syncbuf.fail(self, _PriorSyncFailedError())
1026 return
1027
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001028 if head == revid:
1029 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001030 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001031 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001032 if not syncbuf.detach_head:
1033 return
1034 else:
1035 lost = self._revlist(not_rev(revid), HEAD)
1036 if lost:
1037 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001038
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001040 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001041 except GitError, e:
1042 syncbuf.fail(self, e)
1043 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001044 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001045 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001046
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001047 if head == revid:
1048 # No changes; don't do anything further.
1049 #
1050 return
1051
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001052 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001054 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001056 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001057 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001058 syncbuf.info(self,
1059 "leaving %s; does not track upstream",
1060 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001062 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001063 except GitError, e:
1064 syncbuf.fail(self, e)
1065 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001067 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001068
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001069 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001070 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001072 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001073 if not_merged:
1074 if upstream_gain:
1075 # The user has published this branch and some of those
1076 # commits are not yet merged upstream. We do not want
1077 # to rewrite the published commits so we punt.
1078 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001079 syncbuf.fail(self,
1080 "branch %s is published (but not merged) and is now %d commits behind"
1081 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001082 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001083 elif pub == head:
1084 # All published commits are merged, and thus we are a
1085 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001086 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001087 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001088 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001089 self._CopyFiles()
1090 syncbuf.later1(self, _doff)
1091 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001092
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001093 # Examine the local commits not in the remote. Find the
1094 # last one attributed to this user, if any.
1095 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001096 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001097 last_mine = None
1098 cnt_mine = 0
1099 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001100 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001101 if committer_email == self.UserEmail:
1102 last_mine = commit_id
1103 cnt_mine += 1
1104
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001105 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001106 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001107
1108 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001109 syncbuf.fail(self, _DirtyError())
1110 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001112 # If the upstream switched on us, warn the user.
1113 #
1114 if branch.merge != self.revisionExpr:
1115 if branch.merge and self.revisionExpr:
1116 syncbuf.info(self,
1117 'manifest switched %s...%s',
1118 branch.merge,
1119 self.revisionExpr)
1120 elif branch.merge:
1121 syncbuf.info(self,
1122 'manifest no longer tracks %s',
1123 branch.merge)
1124
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001125 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001127 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001128 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001129 syncbuf.info(self,
1130 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001131 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001132
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001133 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001134 if not ID_RE.match(self.revisionExpr):
1135 # in case of manifest sync the revisionExpr might be a SHA1
1136 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001137 branch.Save()
1138
Mike Pontillod3153822012-02-28 11:53:24 -08001139 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001140 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001141 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001142 self._CopyFiles()
1143 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001144 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001145 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001146 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001147 self._CopyFiles()
1148 except GitError, e:
1149 syncbuf.fail(self, e)
1150 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001151 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001152 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001153 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001154 self._CopyFiles()
1155 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001156
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001157 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001158 # dest should already be an absolute path, but src is project relative
1159 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001160 abssrc = os.path.join(self.worktree, src)
1161 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162
James W. Mills24c13082012-04-12 15:04:13 -05001163 def AddAnnotation(self, name, value, keep):
1164 self.annotations.append(_Annotation(name, value, keep))
1165
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001166 def DownloadPatchSet(self, change_id, patch_id):
1167 """Download a single patch set of a single change to FETCH_HEAD.
1168 """
1169 remote = self.GetRemote(self.remote.name)
1170
1171 cmd = ['fetch', remote.name]
1172 cmd.append('refs/changes/%2.2d/%d/%d' \
1173 % (change_id % 100, change_id, patch_id))
1174 cmd.extend(map(lambda x: str(x), remote.fetch))
1175 if GitCommand(self, cmd, bare=True).Wait() != 0:
1176 return None
1177 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001178 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001179 change_id,
1180 patch_id,
1181 self.bare_git.rev_parse('FETCH_HEAD'))
1182
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001183
1184## Branch Management ##
1185
1186 def StartBranch(self, name):
1187 """Create a new branch off the manifest's revision.
1188 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001189 head = self.work_git.GetHead()
1190 if head == (R_HEADS + name):
1191 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001193 all = self.bare_ref.all
1194 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001195 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001196 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001197 capture_stdout = True,
1198 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001199
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001200 branch = self.GetBranch(name)
1201 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001202 branch.merge = self.revisionExpr
1203 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001204
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001205 if head.startswith(R_HEADS):
1206 try:
1207 head = all[head]
1208 except KeyError:
1209 head = None
1210
1211 if revid and head and revid == head:
1212 ref = os.path.join(self.gitdir, R_HEADS + name)
1213 try:
1214 os.makedirs(os.path.dirname(ref))
1215 except OSError:
1216 pass
1217 _lwrite(ref, '%s\n' % revid)
1218 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1219 'ref: %s%s\n' % (R_HEADS, name))
1220 branch.Save()
1221 return True
1222
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001223 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001224 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001225 capture_stdout = True,
1226 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001227 branch.Save()
1228 return True
1229 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001230
Wink Saville02d79452009-04-10 13:01:24 -07001231 def CheckoutBranch(self, name):
1232 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001233
1234 Args:
1235 name: The name of the branch to checkout.
1236
1237 Returns:
1238 True if the checkout succeeded; False if it didn't; None if the branch
1239 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001240 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001241 rev = R_HEADS + name
1242 head = self.work_git.GetHead()
1243 if head == rev:
1244 # Already on the branch
1245 #
1246 return True
Wink Saville02d79452009-04-10 13:01:24 -07001247
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001248 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001249 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001250 revid = all[rev]
1251 except KeyError:
1252 # Branch does not exist in this project
1253 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001254 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001255
1256 if head.startswith(R_HEADS):
1257 try:
1258 head = all[head]
1259 except KeyError:
1260 head = None
1261
1262 if head == revid:
1263 # Same revision; just update HEAD to point to the new
1264 # target branch, but otherwise take no other action.
1265 #
1266 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1267 'ref: %s%s\n' % (R_HEADS, name))
1268 return True
Wink Saville02d79452009-04-10 13:01:24 -07001269
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001270 return GitCommand(self,
1271 ['checkout', name, '--'],
1272 capture_stdout = True,
1273 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001274
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001275 def AbandonBranch(self, name):
1276 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001277
1278 Args:
1279 name: The name of the branch to abandon.
1280
1281 Returns:
1282 True if the abandon succeeded; False if it didn't; None if the branch
1283 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001284 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001285 rev = R_HEADS + name
1286 all = self.bare_ref.all
1287 if rev not in all:
Doug Andersondafb1d62011-04-07 11:46:59 -07001288 # Doesn't exist
1289 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001290
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001291 head = self.work_git.GetHead()
1292 if head == rev:
1293 # We can't destroy the branch while we are sitting
1294 # on it. Switch to a detached HEAD.
1295 #
1296 head = all[head]
1297
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001298 revid = self.GetRevisionId(all)
1299 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001300 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1301 '%s\n' % revid)
1302 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001303 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001304
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001305 return GitCommand(self,
1306 ['branch', '-D', name],
1307 capture_stdout = True,
1308 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001309
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310 def PruneHeads(self):
1311 """Prune any topic branches already merged into upstream.
1312 """
1313 cb = self.CurrentBranch
1314 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001315 left = self._allrefs
1316 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317 if name.startswith(R_HEADS):
1318 name = name[len(R_HEADS):]
1319 if cb is None or name != cb:
1320 kill.append(name)
1321
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001322 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323 if cb is not None \
1324 and not self._revlist(HEAD + '...' + rev) \
1325 and not self.IsDirty(consider_untracked = False):
1326 self.work_git.DetachHead(HEAD)
1327 kill.append(cb)
1328
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001330 old = self.bare_git.GetHead()
1331 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001332 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1333
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001334 try:
1335 self.bare_git.DetachHead(rev)
1336
1337 b = ['branch', '-d']
1338 b.extend(kill)
1339 b = GitCommand(self, b, bare=True,
1340 capture_stdout=True,
1341 capture_stderr=True)
1342 b.Wait()
1343 finally:
1344 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001345 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001346
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001347 for branch in kill:
1348 if (R_HEADS + branch) not in left:
1349 self.CleanPublishedCache()
1350 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001351
1352 if cb and cb not in kill:
1353 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001354 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001355
1356 kept = []
1357 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001358 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359 branch = self.GetBranch(branch)
1360 base = branch.LocalMerge
1361 if not base:
1362 base = rev
1363 kept.append(ReviewableBranch(self, branch, base))
1364 return kept
1365
1366
1367## Direct Git Commands ##
1368
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001369 def _RemoteFetch(self, name=None,
1370 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001371 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001372 quiet=False,
1373 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001374
1375 is_sha1 = False
1376 tag_name = None
1377
Brian Harring14a66742012-09-28 20:21:57 -07001378 def CheckForSha1():
1379 try:
1380 # if revision (sha or tag) is not present then following function
1381 # throws an error.
1382 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1383 return True
1384 except GitError:
1385 # There is no such persistent revision. We have to fetch it.
1386 return False
1387
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001388 if current_branch_only:
1389 if ID_RE.match(self.revisionExpr) is not None:
1390 is_sha1 = True
1391 elif self.revisionExpr.startswith(R_TAGS):
1392 # this is a tag and its sha1 value should never change
1393 tag_name = self.revisionExpr[len(R_TAGS):]
1394
1395 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001396 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001397 return True
Brian Harring14a66742012-09-28 20:21:57 -07001398 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1399 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001400
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001401 if not name:
1402 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001403
1404 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001405 remote = self.GetRemote(name)
1406 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001407 ssh_proxy = True
1408
Shawn O. Pearce88443382010-10-08 10:02:09 +02001409 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001410 if alt_dir and 'objects' == os.path.basename(alt_dir):
1411 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001412 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1413 remote = self.GetRemote(name)
1414
1415 all = self.bare_ref.all
1416 ids = set(all.values())
1417 tmp = set()
1418
1419 for r, id in GitRefs(ref_dir).all.iteritems():
1420 if r not in all:
1421 if r.startswith(R_TAGS) or remote.WritesTo(r):
1422 all[r] = id
1423 ids.add(id)
1424 continue
1425
1426 if id in ids:
1427 continue
1428
1429 r = 'refs/_alt/%s' % id
1430 all[r] = id
1431 ids.add(id)
1432 tmp.add(r)
1433
1434 ref_names = list(all.keys())
1435 ref_names.sort()
1436
1437 tmp_packed = ''
1438 old_packed = ''
1439
1440 for r in ref_names:
1441 line = '%s %s\n' % (all[r], r)
1442 tmp_packed += line
1443 if r not in tmp:
1444 old_packed += line
1445
1446 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001447 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001448 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001449
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001450 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001451
1452 # The --depth option only affects the initial fetch; after that we'll do
1453 # full fetches of changes.
1454 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1455 if depth and initial:
1456 cmd.append('--depth=%s' % depth)
1457
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001458 if quiet:
1459 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001460 if not self.worktree:
1461 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001462 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001463
Brian Harring14a66742012-09-28 20:21:57 -07001464 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001465 # Fetch whole repo
1466 cmd.append('--tags')
1467 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1468 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001469 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001470 cmd.append(tag_name)
1471 else:
1472 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001473 if is_sha1:
1474 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001475 if branch.startswith(R_HEADS):
1476 branch = branch[len(R_HEADS):]
1477 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001478
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001479 ok = False
1480 for i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001481 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1482 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001483 ok = True
1484 break
Brian Harring14a66742012-09-28 20:21:57 -07001485 elif current_branch_only and is_sha1 and ret == 128:
1486 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1487 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1488 # abort the optimization attempt and do a full sync.
1489 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001490 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001491
1492 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001493 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001494 if old_packed != '':
1495 _lwrite(packed_refs, old_packed)
1496 else:
1497 os.remove(packed_refs)
1498 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001499
1500 if is_sha1 and current_branch_only and self.upstream:
1501 # We just synced the upstream given branch; verify we
1502 # got what we wanted, else trigger a second run of all
1503 # refs.
1504 if not CheckForSha1():
1505 return self._RemoteFetch(name=name, current_branch_only=False,
1506 initial=False, quiet=quiet, alt_dir=alt_dir)
1507
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001508 return ok
1509
1510 def _ApplyCloneBundle(self, initial=False, quiet=False):
1511 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1512 return False
1513
1514 remote = self.GetRemote(self.remote.name)
1515 bundle_url = remote.url + '/clone.bundle'
1516 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001517 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1518 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001519 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1520 return False
1521
1522 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1523 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001524
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001525 exist_dst = os.path.exists(bundle_dst)
1526 exist_tmp = os.path.exists(bundle_tmp)
1527
1528 if not initial and not exist_dst and not exist_tmp:
1529 return False
1530
1531 if not exist_dst:
1532 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1533 if not exist_dst:
1534 return False
1535
1536 cmd = ['fetch']
1537 if quiet:
1538 cmd.append('--quiet')
1539 if not self.worktree:
1540 cmd.append('--update-head-ok')
1541 cmd.append(bundle_dst)
1542 for f in remote.fetch:
1543 cmd.append(str(f))
1544 cmd.append('refs/tags/*:refs/tags/*')
1545
1546 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001547 if os.path.exists(bundle_dst):
1548 os.remove(bundle_dst)
1549 if os.path.exists(bundle_tmp):
1550 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001551 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001552
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001553 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001554 if os.path.exists(dstPath):
1555 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001556
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001557 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001558 if quiet:
1559 cmd += ['--silent']
1560 if os.path.exists(tmpPath):
1561 size = os.stat(tmpPath).st_size
1562 if size >= 1024:
1563 cmd += ['--continue-at', '%d' % (size,)]
1564 else:
1565 os.remove(tmpPath)
1566 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1567 cmd += ['--proxy', os.environ['http_proxy']]
1568 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001569
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001570 if IsTrace():
1571 Trace('%s', ' '.join(cmd))
1572 try:
1573 proc = subprocess.Popen(cmd)
1574 except OSError:
1575 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001576
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001577 curlret = proc.wait()
1578
1579 if curlret == 22:
1580 # From curl man page:
1581 # 22: HTTP page not retrieved. The requested url was not found or
1582 # returned another error with the HTTP error code being 400 or above.
1583 # This return code only appears if -f, --fail is used.
1584 if not quiet:
1585 print >> sys.stderr, "Server does not provide clone.bundle; ignoring."
1586 return False
1587
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001588 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001589 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001590 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001591 return True
1592 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001593 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001594 return False
1595 else:
1596 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001597
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001598 def _Checkout(self, rev, quiet=False):
1599 cmd = ['checkout']
1600 if quiet:
1601 cmd.append('-q')
1602 cmd.append(rev)
1603 cmd.append('--')
1604 if GitCommand(self, cmd).Wait() != 0:
1605 if self._allrefs:
1606 raise GitError('%s checkout %s ' % (self.name, rev))
1607
Pierre Tardye5a21222011-03-24 16:28:18 +01001608 def _CherryPick(self, rev, quiet=False):
1609 cmd = ['cherry-pick']
1610 cmd.append(rev)
1611 cmd.append('--')
1612 if GitCommand(self, cmd).Wait() != 0:
1613 if self._allrefs:
1614 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1615
Erwan Mahea94f1622011-08-19 13:56:09 +02001616 def _Revert(self, rev, quiet=False):
1617 cmd = ['revert']
1618 cmd.append('--no-edit')
1619 cmd.append(rev)
1620 cmd.append('--')
1621 if GitCommand(self, cmd).Wait() != 0:
1622 if self._allrefs:
1623 raise GitError('%s revert %s ' % (self.name, rev))
1624
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001625 def _ResetHard(self, rev, quiet=True):
1626 cmd = ['reset', '--hard']
1627 if quiet:
1628 cmd.append('-q')
1629 cmd.append(rev)
1630 if GitCommand(self, cmd).Wait() != 0:
1631 raise GitError('%s reset --hard %s ' % (self.name, rev))
1632
1633 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001634 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001635 if onto is not None:
1636 cmd.extend(['--onto', onto])
1637 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001638 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001639 raise GitError('%s rebase %s ' % (self.name, upstream))
1640
Pierre Tardy3d125942012-05-04 12:18:12 +02001641 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001642 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001643 if ffonly:
1644 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001645 if GitCommand(self, cmd).Wait() != 0:
1646 raise GitError('%s merge %s ' % (self.name, head))
1647
1648 def _InitGitDir(self):
1649 if not os.path.exists(self.gitdir):
1650 os.makedirs(self.gitdir)
1651 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001652
Shawn O. Pearce88443382010-10-08 10:02:09 +02001653 mp = self.manifest.manifestProject
1654 ref_dir = mp.config.GetString('repo.reference')
1655
1656 if ref_dir:
1657 mirror_git = os.path.join(ref_dir, self.name + '.git')
1658 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1659 self.relpath + '.git')
1660
1661 if os.path.exists(mirror_git):
1662 ref_dir = mirror_git
1663
1664 elif os.path.exists(repo_git):
1665 ref_dir = repo_git
1666
1667 else:
1668 ref_dir = None
1669
1670 if ref_dir:
1671 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1672 os.path.join(ref_dir, 'objects') + '\n')
1673
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001674 if self.manifest.IsMirror:
1675 self.config.SetString('core.bare', 'true')
1676 else:
1677 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001678
1679 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001680 try:
1681 to_rm = os.listdir(hooks)
1682 except OSError:
1683 to_rm = []
1684 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001686 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001687
1688 m = self.manifest.manifestProject.config
1689 for key in ['user.name', 'user.email']:
1690 if m.Has(key, include_defaults = False):
1691 self.config.SetString(key, m.GetString(key))
1692
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001693 def _InitHooks(self):
1694 hooks = self._gitdir_path('hooks')
1695 if not os.path.exists(hooks):
1696 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001697 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001698 name = os.path.basename(stock_hook)
1699
Victor Boivie65e0f352011-04-18 11:23:29 +02001700 if name in ('commit-msg',) and not self.remote.review \
1701 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001702 # Don't install a Gerrit Code Review hook if this
1703 # project does not appear to use it for reviews.
1704 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001705 # Since the manifest project is one of those, but also
1706 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001707 continue
1708
1709 dst = os.path.join(hooks, name)
1710 if os.path.islink(dst):
1711 continue
1712 if os.path.exists(dst):
1713 if filecmp.cmp(stock_hook, dst, shallow=False):
1714 os.remove(dst)
1715 else:
1716 _error("%s: Not replacing %s hook", self.relpath, name)
1717 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001718 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001719 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001720 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001721 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001722 raise GitError('filesystem must support symlinks')
1723 else:
1724 raise
1725
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001726 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001727 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001728 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001729 remote.url = self.remote.url
1730 remote.review = self.remote.review
1731 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001732
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001733 if self.worktree:
1734 remote.ResetFetch(mirror=False)
1735 else:
1736 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001737 remote.Save()
1738
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001739 def _InitMRef(self):
1740 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001741 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001742
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001743 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001744 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001745
1746 def _InitAnyMRef(self, ref):
1747 cur = self.bare_ref.symref(ref)
1748
1749 if self.revisionId:
1750 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1751 msg = 'manifest set to %s' % self.revisionId
1752 dst = self.revisionId + '^0'
1753 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1754 else:
1755 remote = self.GetRemote(self.remote.name)
1756 dst = remote.ToLocal(self.revisionExpr)
1757 if cur != dst:
1758 msg = 'manifest set to %s' % self.revisionExpr
1759 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001760
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001761 def _InitWorkTree(self):
1762 dotgit = os.path.join(self.worktree, '.git')
1763 if not os.path.exists(dotgit):
1764 os.makedirs(dotgit)
1765
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766 for name in ['config',
1767 'description',
1768 'hooks',
1769 'info',
1770 'logs',
1771 'objects',
1772 'packed-refs',
1773 'refs',
1774 'rr-cache',
1775 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001776 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001777 src = os.path.join(self.gitdir, name)
1778 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001779 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001780 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001781 else:
1782 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001783 except OSError, e:
1784 if e.errno == errno.EPERM:
1785 raise GitError('filesystem must support symlinks')
1786 else:
1787 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001788
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001789 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001790
1791 cmd = ['read-tree', '--reset', '-u']
1792 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001793 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001794 if GitCommand(self, cmd).Wait() != 0:
1795 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001796
1797 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1798 if not os.path.exists(rr_cache):
1799 os.makedirs(rr_cache)
1800
Shawn O. Pearce93609662009-04-21 10:50:33 -07001801 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001802
1803 def _gitdir_path(self, path):
1804 return os.path.join(self.gitdir, path)
1805
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001806 def _revlist(self, *args, **kw):
1807 a = []
1808 a.extend(args)
1809 a.append('--')
1810 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001811
1812 @property
1813 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001814 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001815
1816 class _GitGetByExec(object):
1817 def __init__(self, project, bare):
1818 self._project = project
1819 self._bare = bare
1820
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001821 def LsOthers(self):
1822 p = GitCommand(self._project,
1823 ['ls-files',
1824 '-z',
1825 '--others',
1826 '--exclude-standard'],
1827 bare = False,
1828 capture_stdout = True,
1829 capture_stderr = True)
1830 if p.Wait() == 0:
1831 out = p.stdout
1832 if out:
1833 return out[:-1].split("\0")
1834 return []
1835
1836 def DiffZ(self, name, *args):
1837 cmd = [name]
1838 cmd.append('-z')
1839 cmd.extend(args)
1840 p = GitCommand(self._project,
1841 cmd,
1842 bare = False,
1843 capture_stdout = True,
1844 capture_stderr = True)
1845 try:
1846 out = p.process.stdout.read()
1847 r = {}
1848 if out:
1849 out = iter(out[:-1].split('\0'))
1850 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001851 try:
1852 info = out.next()
1853 path = out.next()
1854 except StopIteration:
1855 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001856
1857 class _Info(object):
1858 def __init__(self, path, omode, nmode, oid, nid, state):
1859 self.path = path
1860 self.src_path = None
1861 self.old_mode = omode
1862 self.new_mode = nmode
1863 self.old_id = oid
1864 self.new_id = nid
1865
1866 if len(state) == 1:
1867 self.status = state
1868 self.level = None
1869 else:
1870 self.status = state[:1]
1871 self.level = state[1:]
1872 while self.level.startswith('0'):
1873 self.level = self.level[1:]
1874
1875 info = info[1:].split(' ')
1876 info =_Info(path, *info)
1877 if info.status in ('R', 'C'):
1878 info.src_path = info.path
1879 info.path = out.next()
1880 r[info.path] = info
1881 return r
1882 finally:
1883 p.Wait()
1884
1885 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001886 if self._bare:
1887 path = os.path.join(self._project.gitdir, HEAD)
1888 else:
1889 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001890 fd = open(path, 'rb')
1891 try:
1892 line = fd.read()
1893 finally:
1894 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001895 if line.startswith('ref: '):
1896 return line[5:-1]
1897 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001898
1899 def SetHead(self, ref, message=None):
1900 cmdv = []
1901 if message is not None:
1902 cmdv.extend(['-m', message])
1903 cmdv.append(HEAD)
1904 cmdv.append(ref)
1905 self.symbolic_ref(*cmdv)
1906
1907 def DetachHead(self, new, message=None):
1908 cmdv = ['--no-deref']
1909 if message is not None:
1910 cmdv.extend(['-m', message])
1911 cmdv.append(HEAD)
1912 cmdv.append(new)
1913 self.update_ref(*cmdv)
1914
1915 def UpdateRef(self, name, new, old=None,
1916 message=None,
1917 detach=False):
1918 cmdv = []
1919 if message is not None:
1920 cmdv.extend(['-m', message])
1921 if detach:
1922 cmdv.append('--no-deref')
1923 cmdv.append(name)
1924 cmdv.append(new)
1925 if old is not None:
1926 cmdv.append(old)
1927 self.update_ref(*cmdv)
1928
1929 def DeleteRef(self, name, old=None):
1930 if not old:
1931 old = self.rev_parse(name)
1932 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001933 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001934
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001935 def rev_list(self, *args, **kw):
1936 if 'format' in kw:
1937 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1938 else:
1939 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001940 cmdv.extend(args)
1941 p = GitCommand(self._project,
1942 cmdv,
1943 bare = self._bare,
1944 capture_stdout = True,
1945 capture_stderr = True)
1946 r = []
1947 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001948 if line[-1] == '\n':
1949 line = line[:-1]
1950 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001951 if p.Wait() != 0:
1952 raise GitError('%s rev-list %s: %s' % (
1953 self._project.name,
1954 str(args),
1955 p.stderr))
1956 return r
1957
1958 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001959 """Allow arbitrary git commands using pythonic syntax.
1960
1961 This allows you to do things like:
1962 git_obj.rev_parse('HEAD')
1963
1964 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1965 run. We'll replace the '_' with a '-' and try to run a git command.
1966 Any other arguments will be passed to the git command.
1967
1968 Args:
1969 name: The name of the git command to call. Any '_' characters will
1970 be replaced with '-'.
1971
1972 Returns:
1973 A callable object that will try to call git with the named command.
1974 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001975 name = name.replace('_', '-')
1976 def runner(*args):
1977 cmdv = [name]
1978 cmdv.extend(args)
1979 p = GitCommand(self._project,
1980 cmdv,
1981 bare = self._bare,
1982 capture_stdout = True,
1983 capture_stderr = True)
1984 if p.Wait() != 0:
1985 raise GitError('%s %s: %s' % (
1986 self._project.name,
1987 name,
1988 p.stderr))
1989 r = p.stdout
1990 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1991 return r[:-1]
1992 return r
1993 return runner
1994
1995
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001996class _PriorSyncFailedError(Exception):
1997 def __str__(self):
1998 return 'prior sync failed; rebase still in progress'
1999
2000class _DirtyError(Exception):
2001 def __str__(self):
2002 return 'contains uncommitted changes'
2003
2004class _InfoMessage(object):
2005 def __init__(self, project, text):
2006 self.project = project
2007 self.text = text
2008
2009 def Print(self, syncbuf):
2010 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2011 syncbuf.out.nl()
2012
2013class _Failure(object):
2014 def __init__(self, project, why):
2015 self.project = project
2016 self.why = why
2017
2018 def Print(self, syncbuf):
2019 syncbuf.out.fail('error: %s/: %s',
2020 self.project.relpath,
2021 str(self.why))
2022 syncbuf.out.nl()
2023
2024class _Later(object):
2025 def __init__(self, project, action):
2026 self.project = project
2027 self.action = action
2028
2029 def Run(self, syncbuf):
2030 out = syncbuf.out
2031 out.project('project %s/', self.project.relpath)
2032 out.nl()
2033 try:
2034 self.action()
2035 out.nl()
2036 return True
2037 except GitError, e:
2038 out.nl()
2039 return False
2040
2041class _SyncColoring(Coloring):
2042 def __init__(self, config):
2043 Coloring.__init__(self, config, 'reposync')
2044 self.project = self.printer('header', attr = 'bold')
2045 self.info = self.printer('info')
2046 self.fail = self.printer('fail', fg='red')
2047
2048class SyncBuffer(object):
2049 def __init__(self, config, detach_head=False):
2050 self._messages = []
2051 self._failures = []
2052 self._later_queue1 = []
2053 self._later_queue2 = []
2054
2055 self.out = _SyncColoring(config)
2056 self.out.redirect(sys.stderr)
2057
2058 self.detach_head = detach_head
2059 self.clean = True
2060
2061 def info(self, project, fmt, *args):
2062 self._messages.append(_InfoMessage(project, fmt % args))
2063
2064 def fail(self, project, err=None):
2065 self._failures.append(_Failure(project, err))
2066 self.clean = False
2067
2068 def later1(self, project, what):
2069 self._later_queue1.append(_Later(project, what))
2070
2071 def later2(self, project, what):
2072 self._later_queue2.append(_Later(project, what))
2073
2074 def Finish(self):
2075 self._PrintMessages()
2076 self._RunLater()
2077 self._PrintMessages()
2078 return self.clean
2079
2080 def _RunLater(self):
2081 for q in ['_later_queue1', '_later_queue2']:
2082 if not self._RunQueue(q):
2083 return
2084
2085 def _RunQueue(self, queue):
2086 for m in getattr(self, queue):
2087 if not m.Run(self):
2088 self.clean = False
2089 return False
2090 setattr(self, queue, [])
2091 return True
2092
2093 def _PrintMessages(self):
2094 for m in self._messages:
2095 m.Print(self)
2096 for m in self._failures:
2097 m.Print(self)
2098
2099 self._messages = []
2100 self._failures = []
2101
2102
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002103class MetaProject(Project):
2104 """A special project housed under .repo.
2105 """
2106 def __init__(self, manifest, name, gitdir, worktree):
2107 repodir = manifest.repodir
2108 Project.__init__(self,
2109 manifest = manifest,
2110 name = name,
2111 gitdir = gitdir,
2112 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002113 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002114 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002115 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002116 revisionId = None,
2117 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002118
2119 def PreSync(self):
2120 if self.Exists:
2121 cb = self.CurrentBranch
2122 if cb:
2123 base = self.GetBranch(cb).merge
2124 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002125 self.revisionExpr = base
2126 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002127
Florian Vallee5d016502012-06-07 17:19:26 +02002128 def MetaBranchSwitch(self, target):
2129 """ Prepare MetaProject for manifest branch switch
2130 """
2131
2132 # detach and delete manifest branch, allowing a new
2133 # branch to take over
2134 syncbuf = SyncBuffer(self.config, detach_head = True)
2135 self.Sync_LocalHalf(syncbuf)
2136 syncbuf.Finish()
2137
2138 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002139 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002140 capture_stdout = True,
2141 capture_stderr = True).Wait() == 0
2142
2143
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002144 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002145 def LastFetch(self):
2146 try:
2147 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2148 return os.path.getmtime(fh)
2149 except OSError:
2150 return 0
2151
2152 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002153 def HasChanges(self):
2154 """Has the remote received new commits not yet checked out?
2155 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002156 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002157 return False
2158
2159 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002160 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002161 head = self.work_git.GetHead()
2162 if head.startswith(R_HEADS):
2163 try:
2164 head = all[head]
2165 except KeyError:
2166 head = None
2167
2168 if revid == head:
2169 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002170 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002171 return True
2172 return False