blob: 4621013ca23bed14e11df99dd93586706f769083 [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".
Colin Cross5acde752012-03-28 20:15:45 -0700636
Conley Owens971de8e2012-04-16 10:36:08 -0700637 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.
641 """
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)
895
896 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)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800908
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800909 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
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001253
1254 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
Wink Saville02d79452009-04-10 13:01:24 -07001267
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001268 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]
1295
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. Pearce9fa44db2008-11-03 11:24:59 -08001302
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001303 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
1484
1485 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')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001499
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001500 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. Pearce29472462011-10-11 09:24:07 -07001531
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001532 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001533 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:
1540 os.remove(tmpPath)
1541 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1542 cmd += ['--proxy', os.environ['http_proxy']]
1543 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001544
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001545 if IsTrace():
1546 Trace('%s', ' '.join(cmd))
1547 try:
1548 proc = subprocess.Popen(cmd)
1549 except OSError:
1550 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001551
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001552 curlret = proc.wait()
1553
1554 if curlret == 22:
1555 # From curl man page:
1556 # 22: HTTP page not retrieved. The requested url was not found or
1557 # returned another error with the HTTP error code being 400 or above.
1558 # This return code only appears if -f, --fail is used.
1559 if not quiet:
1560 print >> sys.stderr, "Server does not provide clone.bundle; ignoring."
1561 return False
1562
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001563 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001564 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001565 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001566 return True
1567 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001568 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001569 return False
1570 else:
1571 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001572
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001573 def _Checkout(self, rev, quiet=False):
1574 cmd = ['checkout']
1575 if quiet:
1576 cmd.append('-q')
1577 cmd.append(rev)
1578 cmd.append('--')
1579 if GitCommand(self, cmd).Wait() != 0:
1580 if self._allrefs:
1581 raise GitError('%s checkout %s ' % (self.name, rev))
1582
Pierre Tardye5a21222011-03-24 16:28:18 +01001583 def _CherryPick(self, rev, quiet=False):
1584 cmd = ['cherry-pick']
1585 cmd.append(rev)
1586 cmd.append('--')
1587 if GitCommand(self, cmd).Wait() != 0:
1588 if self._allrefs:
1589 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1590
Erwan Mahea94f1622011-08-19 13:56:09 +02001591 def _Revert(self, rev, quiet=False):
1592 cmd = ['revert']
1593 cmd.append('--no-edit')
1594 cmd.append(rev)
1595 cmd.append('--')
1596 if GitCommand(self, cmd).Wait() != 0:
1597 if self._allrefs:
1598 raise GitError('%s revert %s ' % (self.name, rev))
1599
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001600 def _ResetHard(self, rev, quiet=True):
1601 cmd = ['reset', '--hard']
1602 if quiet:
1603 cmd.append('-q')
1604 cmd.append(rev)
1605 if GitCommand(self, cmd).Wait() != 0:
1606 raise GitError('%s reset --hard %s ' % (self.name, rev))
1607
1608 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001609 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001610 if onto is not None:
1611 cmd.extend(['--onto', onto])
1612 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001613 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001614 raise GitError('%s rebase %s ' % (self.name, upstream))
1615
Pierre Tardy3d125942012-05-04 12:18:12 +02001616 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001617 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001618 if ffonly:
1619 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001620 if GitCommand(self, cmd).Wait() != 0:
1621 raise GitError('%s merge %s ' % (self.name, head))
1622
1623 def _InitGitDir(self):
1624 if not os.path.exists(self.gitdir):
1625 os.makedirs(self.gitdir)
1626 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001627
Shawn O. Pearce88443382010-10-08 10:02:09 +02001628 mp = self.manifest.manifestProject
1629 ref_dir = mp.config.GetString('repo.reference')
1630
1631 if ref_dir:
1632 mirror_git = os.path.join(ref_dir, self.name + '.git')
1633 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1634 self.relpath + '.git')
1635
1636 if os.path.exists(mirror_git):
1637 ref_dir = mirror_git
1638
1639 elif os.path.exists(repo_git):
1640 ref_dir = repo_git
1641
1642 else:
1643 ref_dir = None
1644
1645 if ref_dir:
1646 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1647 os.path.join(ref_dir, 'objects') + '\n')
1648
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001649 if self.manifest.IsMirror:
1650 self.config.SetString('core.bare', 'true')
1651 else:
1652 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001653
1654 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001655 try:
1656 to_rm = os.listdir(hooks)
1657 except OSError:
1658 to_rm = []
1659 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001660 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001661 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001662
1663 m = self.manifest.manifestProject.config
1664 for key in ['user.name', 'user.email']:
1665 if m.Has(key, include_defaults = False):
1666 self.config.SetString(key, m.GetString(key))
1667
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001668 def _InitHooks(self):
1669 hooks = self._gitdir_path('hooks')
1670 if not os.path.exists(hooks):
1671 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001672 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001673 name = os.path.basename(stock_hook)
1674
Victor Boivie65e0f352011-04-18 11:23:29 +02001675 if name in ('commit-msg',) and not self.remote.review \
1676 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001677 # Don't install a Gerrit Code Review hook if this
1678 # project does not appear to use it for reviews.
1679 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001680 # Since the manifest project is one of those, but also
1681 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001682 continue
1683
1684 dst = os.path.join(hooks, name)
1685 if os.path.islink(dst):
1686 continue
1687 if os.path.exists(dst):
1688 if filecmp.cmp(stock_hook, dst, shallow=False):
1689 os.remove(dst)
1690 else:
1691 _error("%s: Not replacing %s hook", self.relpath, name)
1692 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001693 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001694 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001695 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001696 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001697 raise GitError('filesystem must support symlinks')
1698 else:
1699 raise
1700
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001701 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001702 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001703 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001704 remote.url = self.remote.url
1705 remote.review = self.remote.review
1706 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001707
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001708 if self.worktree:
1709 remote.ResetFetch(mirror=False)
1710 else:
1711 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001712 remote.Save()
1713
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001714 def _InitMRef(self):
1715 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001716 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001717
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001718 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001719 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001720
1721 def _InitAnyMRef(self, ref):
1722 cur = self.bare_ref.symref(ref)
1723
1724 if self.revisionId:
1725 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1726 msg = 'manifest set to %s' % self.revisionId
1727 dst = self.revisionId + '^0'
1728 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1729 else:
1730 remote = self.GetRemote(self.remote.name)
1731 dst = remote.ToLocal(self.revisionExpr)
1732 if cur != dst:
1733 msg = 'manifest set to %s' % self.revisionExpr
1734 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001735
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001736 def _InitWorkTree(self):
1737 dotgit = os.path.join(self.worktree, '.git')
1738 if not os.path.exists(dotgit):
1739 os.makedirs(dotgit)
1740
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001741 for name in ['config',
1742 'description',
1743 'hooks',
1744 'info',
1745 'logs',
1746 'objects',
1747 'packed-refs',
1748 'refs',
1749 'rr-cache',
1750 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001751 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001752 src = os.path.join(self.gitdir, name)
1753 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001754 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001755 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001756 else:
1757 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001758 except OSError, e:
1759 if e.errno == errno.EPERM:
1760 raise GitError('filesystem must support symlinks')
1761 else:
1762 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001763
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001764 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001765
1766 cmd = ['read-tree', '--reset', '-u']
1767 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001768 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769 if GitCommand(self, cmd).Wait() != 0:
1770 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001771
1772 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1773 if not os.path.exists(rr_cache):
1774 os.makedirs(rr_cache)
1775
Shawn O. Pearce93609662009-04-21 10:50:33 -07001776 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001777
1778 def _gitdir_path(self, path):
1779 return os.path.join(self.gitdir, path)
1780
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001781 def _revlist(self, *args, **kw):
1782 a = []
1783 a.extend(args)
1784 a.append('--')
1785 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001786
1787 @property
1788 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001789 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001790
1791 class _GitGetByExec(object):
1792 def __init__(self, project, bare):
1793 self._project = project
1794 self._bare = bare
1795
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001796 def LsOthers(self):
1797 p = GitCommand(self._project,
1798 ['ls-files',
1799 '-z',
1800 '--others',
1801 '--exclude-standard'],
1802 bare = False,
1803 capture_stdout = True,
1804 capture_stderr = True)
1805 if p.Wait() == 0:
1806 out = p.stdout
1807 if out:
1808 return out[:-1].split("\0")
1809 return []
1810
1811 def DiffZ(self, name, *args):
1812 cmd = [name]
1813 cmd.append('-z')
1814 cmd.extend(args)
1815 p = GitCommand(self._project,
1816 cmd,
1817 bare = False,
1818 capture_stdout = True,
1819 capture_stderr = True)
1820 try:
1821 out = p.process.stdout.read()
1822 r = {}
1823 if out:
1824 out = iter(out[:-1].split('\0'))
1825 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001826 try:
1827 info = out.next()
1828 path = out.next()
1829 except StopIteration:
1830 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001831
1832 class _Info(object):
1833 def __init__(self, path, omode, nmode, oid, nid, state):
1834 self.path = path
1835 self.src_path = None
1836 self.old_mode = omode
1837 self.new_mode = nmode
1838 self.old_id = oid
1839 self.new_id = nid
1840
1841 if len(state) == 1:
1842 self.status = state
1843 self.level = None
1844 else:
1845 self.status = state[:1]
1846 self.level = state[1:]
1847 while self.level.startswith('0'):
1848 self.level = self.level[1:]
1849
1850 info = info[1:].split(' ')
1851 info =_Info(path, *info)
1852 if info.status in ('R', 'C'):
1853 info.src_path = info.path
1854 info.path = out.next()
1855 r[info.path] = info
1856 return r
1857 finally:
1858 p.Wait()
1859
1860 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001861 if self._bare:
1862 path = os.path.join(self._project.gitdir, HEAD)
1863 else:
1864 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001865 fd = open(path, 'rb')
1866 try:
1867 line = fd.read()
1868 finally:
1869 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001870 if line.startswith('ref: '):
1871 return line[5:-1]
1872 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001873
1874 def SetHead(self, ref, message=None):
1875 cmdv = []
1876 if message is not None:
1877 cmdv.extend(['-m', message])
1878 cmdv.append(HEAD)
1879 cmdv.append(ref)
1880 self.symbolic_ref(*cmdv)
1881
1882 def DetachHead(self, new, message=None):
1883 cmdv = ['--no-deref']
1884 if message is not None:
1885 cmdv.extend(['-m', message])
1886 cmdv.append(HEAD)
1887 cmdv.append(new)
1888 self.update_ref(*cmdv)
1889
1890 def UpdateRef(self, name, new, old=None,
1891 message=None,
1892 detach=False):
1893 cmdv = []
1894 if message is not None:
1895 cmdv.extend(['-m', message])
1896 if detach:
1897 cmdv.append('--no-deref')
1898 cmdv.append(name)
1899 cmdv.append(new)
1900 if old is not None:
1901 cmdv.append(old)
1902 self.update_ref(*cmdv)
1903
1904 def DeleteRef(self, name, old=None):
1905 if not old:
1906 old = self.rev_parse(name)
1907 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001908 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001909
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001910 def rev_list(self, *args, **kw):
1911 if 'format' in kw:
1912 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1913 else:
1914 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001915 cmdv.extend(args)
1916 p = GitCommand(self._project,
1917 cmdv,
1918 bare = self._bare,
1919 capture_stdout = True,
1920 capture_stderr = True)
1921 r = []
1922 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001923 if line[-1] == '\n':
1924 line = line[:-1]
1925 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001926 if p.Wait() != 0:
1927 raise GitError('%s rev-list %s: %s' % (
1928 self._project.name,
1929 str(args),
1930 p.stderr))
1931 return r
1932
1933 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001934 """Allow arbitrary git commands using pythonic syntax.
1935
1936 This allows you to do things like:
1937 git_obj.rev_parse('HEAD')
1938
1939 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1940 run. We'll replace the '_' with a '-' and try to run a git command.
1941 Any other arguments will be passed to the git command.
1942
1943 Args:
1944 name: The name of the git command to call. Any '_' characters will
1945 be replaced with '-'.
1946
1947 Returns:
1948 A callable object that will try to call git with the named command.
1949 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001950 name = name.replace('_', '-')
1951 def runner(*args):
1952 cmdv = [name]
1953 cmdv.extend(args)
1954 p = GitCommand(self._project,
1955 cmdv,
1956 bare = self._bare,
1957 capture_stdout = True,
1958 capture_stderr = True)
1959 if p.Wait() != 0:
1960 raise GitError('%s %s: %s' % (
1961 self._project.name,
1962 name,
1963 p.stderr))
1964 r = p.stdout
1965 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1966 return r[:-1]
1967 return r
1968 return runner
1969
1970
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001971class _PriorSyncFailedError(Exception):
1972 def __str__(self):
1973 return 'prior sync failed; rebase still in progress'
1974
1975class _DirtyError(Exception):
1976 def __str__(self):
1977 return 'contains uncommitted changes'
1978
1979class _InfoMessage(object):
1980 def __init__(self, project, text):
1981 self.project = project
1982 self.text = text
1983
1984 def Print(self, syncbuf):
1985 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1986 syncbuf.out.nl()
1987
1988class _Failure(object):
1989 def __init__(self, project, why):
1990 self.project = project
1991 self.why = why
1992
1993 def Print(self, syncbuf):
1994 syncbuf.out.fail('error: %s/: %s',
1995 self.project.relpath,
1996 str(self.why))
1997 syncbuf.out.nl()
1998
1999class _Later(object):
2000 def __init__(self, project, action):
2001 self.project = project
2002 self.action = action
2003
2004 def Run(self, syncbuf):
2005 out = syncbuf.out
2006 out.project('project %s/', self.project.relpath)
2007 out.nl()
2008 try:
2009 self.action()
2010 out.nl()
2011 return True
2012 except GitError, e:
2013 out.nl()
2014 return False
2015
2016class _SyncColoring(Coloring):
2017 def __init__(self, config):
2018 Coloring.__init__(self, config, 'reposync')
2019 self.project = self.printer('header', attr = 'bold')
2020 self.info = self.printer('info')
2021 self.fail = self.printer('fail', fg='red')
2022
2023class SyncBuffer(object):
2024 def __init__(self, config, detach_head=False):
2025 self._messages = []
2026 self._failures = []
2027 self._later_queue1 = []
2028 self._later_queue2 = []
2029
2030 self.out = _SyncColoring(config)
2031 self.out.redirect(sys.stderr)
2032
2033 self.detach_head = detach_head
2034 self.clean = True
2035
2036 def info(self, project, fmt, *args):
2037 self._messages.append(_InfoMessage(project, fmt % args))
2038
2039 def fail(self, project, err=None):
2040 self._failures.append(_Failure(project, err))
2041 self.clean = False
2042
2043 def later1(self, project, what):
2044 self._later_queue1.append(_Later(project, what))
2045
2046 def later2(self, project, what):
2047 self._later_queue2.append(_Later(project, what))
2048
2049 def Finish(self):
2050 self._PrintMessages()
2051 self._RunLater()
2052 self._PrintMessages()
2053 return self.clean
2054
2055 def _RunLater(self):
2056 for q in ['_later_queue1', '_later_queue2']:
2057 if not self._RunQueue(q):
2058 return
2059
2060 def _RunQueue(self, queue):
2061 for m in getattr(self, queue):
2062 if not m.Run(self):
2063 self.clean = False
2064 return False
2065 setattr(self, queue, [])
2066 return True
2067
2068 def _PrintMessages(self):
2069 for m in self._messages:
2070 m.Print(self)
2071 for m in self._failures:
2072 m.Print(self)
2073
2074 self._messages = []
2075 self._failures = []
2076
2077
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002078class MetaProject(Project):
2079 """A special project housed under .repo.
2080 """
2081 def __init__(self, manifest, name, gitdir, worktree):
2082 repodir = manifest.repodir
2083 Project.__init__(self,
2084 manifest = manifest,
2085 name = name,
2086 gitdir = gitdir,
2087 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002088 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002089 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002090 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002091 revisionId = None,
2092 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002093
2094 def PreSync(self):
2095 if self.Exists:
2096 cb = self.CurrentBranch
2097 if cb:
2098 base = self.GetBranch(cb).merge
2099 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002100 self.revisionExpr = base
2101 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002102
Florian Vallee5d016502012-06-07 17:19:26 +02002103 def MetaBranchSwitch(self, target):
2104 """ Prepare MetaProject for manifest branch switch
2105 """
2106
2107 # detach and delete manifest branch, allowing a new
2108 # branch to take over
2109 syncbuf = SyncBuffer(self.config, detach_head = True)
2110 self.Sync_LocalHalf(syncbuf)
2111 syncbuf.Finish()
2112
2113 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002114 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002115 capture_stdout = True,
2116 capture_stderr = True).Wait() == 0
2117
2118
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002119 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002120 def LastFetch(self):
2121 try:
2122 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2123 return os.path.getmtime(fh)
2124 except OSError:
2125 return 0
2126
2127 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002128 def HasChanges(self):
2129 """Has the remote received new commits not yet checked out?
2130 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002131 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002132 return False
2133
2134 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002135 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002136 head = self.work_git.GetHead()
2137 if head.startswith(R_HEADS):
2138 try:
2139 head = all[head]
2140 except KeyError:
2141 head = None
2142
2143 if revid == head:
2144 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002145 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002146 return True
2147 return False