blob: cdb4ecfde5c998b8c7cc00e681c491ea6f92a794 [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
Dave Borowitzb42b4742012-10-31 12:27:27 -070028from git_command import GitCommand, git_require
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:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200212 dest_dir = os.path.dirname(dest)
213 if not os.path.isdir(dest_dir):
214 os.makedirs(dest_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 """
Doug Anderson37282b42011-03-04 11:54:18 -0800331 hooks_config = self._hooks_project.config
332 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
333
334 # Get the last hash that the user approved for this hook; may be None.
335 old_hash = hooks_config.GetString(git_approval_key)
336
337 # Get the current hash so we can tell if scripts changed since approval.
338 new_hash = self._GetHash()
339
340 if old_hash is not None:
341 # User previously approved hook and asked not to be prompted again.
342 if new_hash == old_hash:
343 # Approval matched. We're done.
344 return True
345 else:
346 # Give the user a reason why we're prompting, since they last told
347 # us to "never ask again".
348 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
349 self._hook_type)
350 else:
351 prompt = ''
352
353 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
354 if sys.stdout.isatty():
355 prompt += ('Repo %s run the script:\n'
356 ' %s\n'
357 '\n'
358 'Do you want to allow this script to run '
359 '(yes/yes-never-ask-again/NO)? ') % (
360 self._GetMustVerb(), self._script_fullpath)
361 response = raw_input(prompt).lower()
362 print
363
364 # User is doing a one-time approval.
365 if response in ('y', 'yes'):
366 return True
367 elif response == 'yes-never-ask-again':
368 hooks_config.SetString(git_approval_key, new_hash)
369 return True
370
371 # For anything else, we'll assume no approval.
372 if self._abort_if_user_denies:
373 raise HookError('You must allow the %s hook or use --no-verify.' %
374 self._hook_type)
375
376 return False
377
378 def _ExecuteHook(self, **kwargs):
379 """Actually execute the given hook.
380
381 This will run the hook's 'main' function in our python interpreter.
382
383 Args:
384 kwargs: Keyword arguments to pass to the hook. These are often specific
385 to the hook type. For instance, pre-upload hooks will contain
386 a project_list.
387 """
388 # Keep sys.path and CWD stashed away so that we can always restore them
389 # upon function exit.
390 orig_path = os.getcwd()
391 orig_syspath = sys.path
392
393 try:
394 # Always run hooks with CWD as topdir.
395 os.chdir(self._topdir)
396
397 # Put the hook dir as the first item of sys.path so hooks can do
398 # relative imports. We want to replace the repo dir as [0] so
399 # hooks can't import repo files.
400 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
401
402 # Exec, storing global context in the context dict. We catch exceptions
403 # and convert to a HookError w/ just the failing traceback.
404 context = {}
405 try:
406 execfile(self._script_fullpath, context)
407 except Exception:
408 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
409 traceback.format_exc(), self._hook_type))
410
411 # Running the script should have defined a main() function.
412 if 'main' not in context:
413 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
414
415
416 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
417 # We don't actually want hooks to define their main with this argument--
418 # it's there to remind them that their hook should always take **kwargs.
419 # For instance, a pre-upload hook should be defined like:
420 # def main(project_list, **kwargs):
421 #
422 # This allows us to later expand the API without breaking old hooks.
423 kwargs = kwargs.copy()
424 kwargs['hook_should_take_kwargs'] = True
425
426 # Call the main function in the hook. If the hook should cause the
427 # build to fail, it will raise an Exception. We'll catch that convert
428 # to a HookError w/ just the failing traceback.
429 try:
430 context['main'](**kwargs)
431 except Exception:
432 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
433 'above.' % (
434 traceback.format_exc(), self._hook_type))
435 finally:
436 # Restore sys.path and CWD.
437 sys.path = orig_syspath
438 os.chdir(orig_path)
439
440 def Run(self, user_allows_all_hooks, **kwargs):
441 """Run the hook.
442
443 If the hook doesn't exist (because there is no hooks project or because
444 this particular hook is not enabled), this is a no-op.
445
446 Args:
447 user_allows_all_hooks: If True, we will never prompt about running the
448 hook--we'll just assume it's OK to run it.
449 kwargs: Keyword arguments to pass to the hook. These are often specific
450 to the hook type. For instance, pre-upload hooks will contain
451 a project_list.
452
453 Raises:
454 HookError: If there was a problem finding the hook or the user declined
455 to run a required hook (from _CheckForHookApproval).
456 """
457 # No-op if there is no hooks project or if hook is disabled.
458 if ((not self._hooks_project) or
459 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
460 return
461
462 # Bail with a nice error if we can't find the hook.
463 if not os.path.isfile(self._script_fullpath):
464 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
465
466 # Make sure the user is OK with running the hook.
467 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
468 return
469
470 # Run the hook with the same version of python we're using.
471 self._ExecuteHook(**kwargs)
472
473
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700474class Project(object):
475 def __init__(self,
476 manifest,
477 name,
478 remote,
479 gitdir,
480 worktree,
481 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700482 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800483 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700484 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700485 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700486 sync_c = False,
Shawn O. Pearcecd81dd62012-10-26 12:18:00 -0700487 upstream = None):
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
Brian Harring14a66742012-09-28 20:21:57 -0700509 self.upstream = upstream
Mike Pontillod3153822012-02-28 11:53:24 -0800510
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700511 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500513 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700514 self.config = GitConfig.ForRepository(
515 gitdir = self.gitdir,
516 defaults = self.manifest.globalConfig)
517
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800518 if self.worktree:
519 self.work_git = self._GitGetByExec(self, bare=False)
520 else:
521 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700522 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700523 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700524
Doug Anderson37282b42011-03-04 11:54:18 -0800525 # This will be filled in if a project is later identified to be the
526 # project containing repo hooks.
527 self.enabled_repo_hooks = []
528
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700529 @property
530 def Exists(self):
531 return os.path.isdir(self.gitdir)
532
533 @property
534 def CurrentBranch(self):
535 """Obtain the name of the currently checked out branch.
536 The branch name omits the 'refs/heads/' prefix.
537 None is returned if the project is on a detached HEAD.
538 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700539 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540 if b.startswith(R_HEADS):
541 return b[len(R_HEADS):]
542 return None
543
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700544 def IsRebaseInProgress(self):
545 w = self.worktree
546 g = os.path.join(w, '.git')
547 return os.path.exists(os.path.join(g, 'rebase-apply')) \
548 or os.path.exists(os.path.join(g, 'rebase-merge')) \
549 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200550
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700551 def IsDirty(self, consider_untracked=True):
552 """Is the working directory modified in some way?
553 """
554 self.work_git.update_index('-q',
555 '--unmerged',
556 '--ignore-missing',
557 '--refresh')
558 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
559 return True
560 if self.work_git.DiffZ('diff-files'):
561 return True
562 if consider_untracked and self.work_git.LsOthers():
563 return True
564 return False
565
566 _userident_name = None
567 _userident_email = None
568
569 @property
570 def UserName(self):
571 """Obtain the user's personal name.
572 """
573 if self._userident_name is None:
574 self._LoadUserIdentity()
575 return self._userident_name
576
577 @property
578 def UserEmail(self):
579 """Obtain the user's email address. This is very likely
580 to be their Gerrit login.
581 """
582 if self._userident_email is None:
583 self._LoadUserIdentity()
584 return self._userident_email
585
586 def _LoadUserIdentity(self):
587 u = self.bare_git.var('GIT_COMMITTER_IDENT')
588 m = re.compile("^(.*) <([^>]*)> ").match(u)
589 if m:
590 self._userident_name = m.group(1)
591 self._userident_email = m.group(2)
592 else:
593 self._userident_name = ''
594 self._userident_email = ''
595
596 def GetRemote(self, name):
597 """Get the configuration for a single remote.
598 """
599 return self.config.GetRemote(name)
600
601 def GetBranch(self, name):
602 """Get the configuration for a single branch.
603 """
604 return self.config.GetBranch(name)
605
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700606 def GetBranches(self):
607 """Get all existing local branches.
608 """
609 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900610 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700611 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700612
David Pursehouse8a68ff92012-09-24 12:15:13 +0900613 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700614 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
David Pursehouse8a68ff92012-09-24 12:15:13 +0900619 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700620 heads[name] = b
621
David Pursehouse8a68ff92012-09-24 12:15:13 +0900622 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700623 if name.startswith(R_PUB):
624 name = name[len(R_PUB):]
625 b = heads.get(name)
626 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900627 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700628
629 return heads
630
Colin Cross5acde752012-03-28 20:15:45 -0700631 def MatchesGroups(self, manifest_groups):
632 """Returns true if the manifest groups specified at init should cause
633 this project to be synced.
634 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700635 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700636
637 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700638 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700639 manifest_groups: "-group1,group2"
640 the project will be matched.
Colin Cross5acde752012-03-28 20:15:45 -0700641 """
Conley Owensbb1b5f52012-08-13 13:11:18 -0700642 expanded_manifest_groups = manifest_groups or ['all', '-notdefault']
643 expanded_project_groups = ['all'] + (self.groups or [])
644
Conley Owens971de8e2012-04-16 10:36:08 -0700645 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700646 for group in expanded_manifest_groups:
647 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700648 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700649 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700650 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700651
Conley Owens971de8e2012-04-16 10:36:08 -0700652 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653
654## Status Display ##
655
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500656 def HasChanges(self):
657 """Returns true if there are uncommitted changes.
658 """
659 self.work_git.update_index('-q',
660 '--unmerged',
661 '--ignore-missing',
662 '--refresh')
663 if self.IsRebaseInProgress():
664 return True
665
666 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
667 return True
668
669 if self.work_git.DiffZ('diff-files'):
670 return True
671
672 if self.work_git.LsOthers():
673 return True
674
675 return False
676
Terence Haddock4655e812011-03-31 12:33:34 +0200677 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700678 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200679
680 Args:
681 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700682 """
683 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200684 if output_redir == None:
685 output_redir = sys.stdout
686 print >>output_redir, ''
687 print >>output_redir, 'project %s/' % self.relpath
688 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700689 return
690
691 self.work_git.update_index('-q',
692 '--unmerged',
693 '--ignore-missing',
694 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700695 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
697 df = self.work_git.DiffZ('diff-files')
698 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100699 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700700 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700701
702 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200703 if not output_redir == None:
704 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700705 out.project('project %-40s', self.relpath + '/')
706
707 branch = self.CurrentBranch
708 if branch is None:
709 out.nobranch('(*** NO BRANCH ***)')
710 else:
711 out.branch('branch %s', branch)
712 out.nl()
713
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700714 if rb:
715 out.important('prior sync failed; rebase still in progress')
716 out.nl()
717
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700718 paths = list()
719 paths.extend(di.keys())
720 paths.extend(df.keys())
721 paths.extend(do)
722
723 paths = list(set(paths))
724 paths.sort()
725
726 for p in paths:
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900727 try:
728 i = di[p]
729 except KeyError:
730 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700731
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900732 try:
733 f = df[p]
734 except KeyError:
735 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200736
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900737 if i:
738 i_status = i.status.upper()
739 else:
740 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900742 if f:
743 f_status = f.status.lower()
744 else:
745 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700746
747 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800748 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700749 i.src_path, p, i.level)
750 else:
751 line = ' %s%s\t%s' % (i_status, f_status, p)
752
753 if i and not f:
754 out.added('%s', line)
755 elif (i and f) or (not i and f):
756 out.changed('%s', line)
757 elif not i and not f:
758 out.untracked('%s', line)
759 else:
760 out.write('%s', line)
761 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200762
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700763 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700764
pelyad67872d2012-03-28 14:49:58 +0300765 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700766 """Prints the status of the repository to stdout.
767 """
768 out = DiffColoring(self.config)
769 cmd = ['diff']
770 if out.is_on:
771 cmd.append('--color')
772 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300773 if absolute_paths:
774 cmd.append('--src-prefix=a/%s/' % self.relpath)
775 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700776 cmd.append('--')
777 p = GitCommand(self,
778 cmd,
779 capture_stdout = True,
780 capture_stderr = True)
781 has_diff = False
782 for line in p.process.stdout:
783 if not has_diff:
784 out.nl()
785 out.project('project %s/' % self.relpath)
786 out.nl()
787 has_diff = True
788 print line[:-1]
789 p.Wait()
790
791
792## Publish / Upload ##
793
David Pursehouse8a68ff92012-09-24 12:15:13 +0900794 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700795 """Was the branch published (uploaded) for code review?
796 If so, returns the SHA-1 hash of the last published
797 state for the branch.
798 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700799 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900800 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700801 try:
802 return self.bare_git.rev_parse(key)
803 except GitError:
804 return None
805 else:
806 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900807 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700808 except KeyError:
809 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810
David Pursehouse8a68ff92012-09-24 12:15:13 +0900811 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812 """Prunes any stale published refs.
813 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900814 if all_refs is None:
815 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816 heads = set()
817 canrm = {}
David Pursehouse8a68ff92012-09-24 12:15:13 +0900818 for name, ref_id in all_refs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700819 if name.startswith(R_HEADS):
820 heads.add(name)
821 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900822 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700823
David Pursehouse8a68ff92012-09-24 12:15:13 +0900824 for name, ref_id in canrm.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700825 n = name[len(R_PUB):]
826 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900827 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700829 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830 """List any branches which can be uploaded for review.
831 """
832 heads = {}
833 pubed = {}
834
David Pursehouse8a68ff92012-09-24 12:15:13 +0900835 for name, ref_id in self._allrefs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900837 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900839 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700840
841 ready = []
David Pursehouse8a68ff92012-09-24 12:15:13 +0900842 for branch, ref_id in heads.iteritems():
843 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700845 if selected_branch and branch != selected_branch:
846 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700847
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800848 rb = self.GetUploadableBranch(branch)
849 if rb:
850 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700851 return ready
852
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800853 def GetUploadableBranch(self, branch_name):
854 """Get a single uploadable branch, or None.
855 """
856 branch = self.GetBranch(branch_name)
857 base = branch.LocalMerge
858 if branch.LocalMerge:
859 rb = ReviewableBranch(self, branch, base)
860 if rb.commits:
861 return rb
862 return None
863
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700864 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700865 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700866 auto_topic=False,
867 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868 """Uploads the named branch for code review.
869 """
870 if branch is None:
871 branch = self.CurrentBranch
872 if branch is None:
873 raise GitError('not currently on a branch')
874
875 branch = self.GetBranch(branch)
876 if not branch.LocalMerge:
877 raise GitError('branch %s does not track a remote' % branch.name)
878 if not branch.remote.review:
879 raise GitError('remote %s has no review url' % branch.remote.name)
880
881 dest_branch = branch.merge
882 if not dest_branch.startswith(R_HEADS):
883 dest_branch = R_HEADS + dest_branch
884
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800885 if not branch.remote.projectname:
886 branch.remote.projectname = self.name
887 branch.remote.Save()
888
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800889 url = branch.remote.ReviewUrl(self.UserEmail)
890 if url is None:
891 raise UploadError('review not configured')
892 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800893
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800894 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800895 rp = ['gerrit receive-pack']
896 for e in people[0]:
897 rp.append('--reviewer=%s' % sq(e))
898 for e in people[1]:
899 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800900 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700901
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800902 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800903
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800904 if dest_branch.startswith(R_HEADS):
905 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700906
907 upload_type = 'for'
908 if draft:
909 upload_type = 'drafts'
910
911 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
912 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800913 if auto_topic:
914 ref_spec = ref_spec + '/' + branch.name
915 cmd.append(ref_spec)
916
917 if GitCommand(self, cmd, bare = True).Wait() != 0:
918 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919
920 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
921 self.bare_git.UpdateRef(R_PUB + branch.name,
922 R_HEADS + branch.name,
923 message = msg)
924
925
926## Sync ##
927
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700928 def Sync_NetworkHalf(self,
929 quiet=False,
930 is_new=None,
931 current_branch_only=False,
932 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933 """Perform only the network IO portion of the sync process.
934 Local working directory/branch state is not affected.
935 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700936 if is_new is None:
937 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200938 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 self._InitGitDir()
940 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700941
942 if is_new:
943 alt = os.path.join(self.gitdir, 'objects/info/alternates')
944 try:
945 fd = open(alt, 'rb')
946 try:
947 alt_dir = fd.readline().rstrip()
948 finally:
949 fd.close()
950 except IOError:
951 alt_dir = None
952 else:
953 alt_dir = None
954
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700955 if clone_bundle \
956 and alt_dir is None \
957 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700958 is_new = False
959
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700960 if not current_branch_only:
961 if self.sync_c:
962 current_branch_only = True
963 elif not self.manifest._loaded:
964 # Manifest cannot check defaults until it syncs.
965 current_branch_only = False
966 elif self.manifest.default.sync_c:
967 current_branch_only = True
968
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700969 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
970 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700971 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800972
973 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800974 self._InitMRef()
975 else:
976 self._InitMirrorHead()
977 try:
978 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
979 except OSError:
980 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800982
983 def PostRepoUpgrade(self):
984 self._InitHooks()
985
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900987 for copyfile in self.copyfiles:
988 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989
David Pursehouse8a68ff92012-09-24 12:15:13 +0900990 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700991 if self.revisionId:
992 return self.revisionId
993
994 rem = self.GetRemote(self.remote.name)
995 rev = rem.ToLocal(self.revisionExpr)
996
David Pursehouse8a68ff92012-09-24 12:15:13 +0900997 if all_refs is not None and rev in all_refs:
998 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700999
1000 try:
1001 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1002 except GitError:
1003 raise ManifestInvalidRevisionError(
1004 'revision %s in %s not found' % (self.revisionExpr,
1005 self.name))
1006
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001007 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008 """Perform only the local IO portion of the sync process.
1009 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001010 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001011 all_refs = self.bare_ref.all
1012 self.CleanPublishedCache(all_refs)
1013 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001014
David Pursehouse1d947b32012-10-25 12:23:11 +09001015 def _doff():
1016 self._FastForward(revid)
1017 self._CopyFiles()
1018
Skyler Kaufman835cd682011-03-08 12:14:41 -08001019 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001020 head = self.work_git.GetHead()
1021 if head.startswith(R_HEADS):
1022 branch = head[len(R_HEADS):]
1023 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001024 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001025 except KeyError:
1026 head = None
1027 else:
1028 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001029
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001030 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031 # Currently on a detached HEAD. The user is assumed to
1032 # not have any local modifications worth worrying about.
1033 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001034 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001035 syncbuf.fail(self, _PriorSyncFailedError())
1036 return
1037
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001038 if head == revid:
1039 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001040 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001041 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001042 if not syncbuf.detach_head:
1043 return
1044 else:
1045 lost = self._revlist(not_rev(revid), HEAD)
1046 if lost:
1047 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001048
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001049 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001050 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001051 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001052 syncbuf.fail(self, e)
1053 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001055 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001057 if head == revid:
1058 # No changes; don't do anything further.
1059 #
1060 return
1061
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001064 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001066 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001068 syncbuf.info(self,
1069 "leaving %s; does not track upstream",
1070 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001072 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001073 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001074 syncbuf.fail(self, e)
1075 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001077 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001079 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001080 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001082 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083 if not_merged:
1084 if upstream_gain:
1085 # The user has published this branch and some of those
1086 # commits are not yet merged upstream. We do not want
1087 # to rewrite the published commits so we punt.
1088 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001089 syncbuf.fail(self,
1090 "branch %s is published (but not merged) and is now %d commits behind"
1091 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001092 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001093 elif pub == head:
1094 # All published commits are merged, and thus we are a
1095 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001096 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001097 syncbuf.later1(self, _doff)
1098 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001099
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001100 # Examine the local commits not in the remote. Find the
1101 # last one attributed to this user, if any.
1102 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001103 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001104 last_mine = None
1105 cnt_mine = 0
1106 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001107 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001108 if committer_email == self.UserEmail:
1109 last_mine = commit_id
1110 cnt_mine += 1
1111
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001112 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001113 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001114
1115 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001116 syncbuf.fail(self, _DirtyError())
1117 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001118
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001119 # If the upstream switched on us, warn the user.
1120 #
1121 if branch.merge != self.revisionExpr:
1122 if branch.merge and self.revisionExpr:
1123 syncbuf.info(self,
1124 'manifest switched %s...%s',
1125 branch.merge,
1126 self.revisionExpr)
1127 elif branch.merge:
1128 syncbuf.info(self,
1129 'manifest no longer tracks %s',
1130 branch.merge)
1131
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001132 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001133 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001134 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001136 syncbuf.info(self,
1137 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001138 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001139
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001140 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001141 if not ID_RE.match(self.revisionExpr):
1142 # in case of manifest sync the revisionExpr might be a SHA1
1143 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001144 branch.Save()
1145
Mike Pontillod3153822012-02-28 11:53:24 -08001146 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001147 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001148 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001149 self._CopyFiles()
1150 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001151 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001152 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001153 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001154 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001155 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001156 syncbuf.fail(self, e)
1157 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001158 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001159 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001161 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162 # dest should already be an absolute path, but src is project relative
1163 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001164 abssrc = os.path.join(self.worktree, src)
1165 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001166
James W. Mills24c13082012-04-12 15:04:13 -05001167 def AddAnnotation(self, name, value, keep):
1168 self.annotations.append(_Annotation(name, value, keep))
1169
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001170 def DownloadPatchSet(self, change_id, patch_id):
1171 """Download a single patch set of a single change to FETCH_HEAD.
1172 """
1173 remote = self.GetRemote(self.remote.name)
1174
1175 cmd = ['fetch', remote.name]
1176 cmd.append('refs/changes/%2.2d/%d/%d' \
1177 % (change_id % 100, change_id, patch_id))
1178 cmd.extend(map(lambda x: str(x), remote.fetch))
1179 if GitCommand(self, cmd, bare=True).Wait() != 0:
1180 return None
1181 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001182 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001183 change_id,
1184 patch_id,
1185 self.bare_git.rev_parse('FETCH_HEAD'))
1186
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187
1188## Branch Management ##
1189
1190 def StartBranch(self, name):
1191 """Create a new branch off the manifest's revision.
1192 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001193 head = self.work_git.GetHead()
1194 if head == (R_HEADS + name):
1195 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196
David Pursehouse8a68ff92012-09-24 12:15:13 +09001197 all_refs = self.bare_ref.all
1198 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001199 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001200 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001201 capture_stdout = True,
1202 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001203
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001204 branch = self.GetBranch(name)
1205 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001206 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001207 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001208
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001209 if head.startswith(R_HEADS):
1210 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001211 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001212 except KeyError:
1213 head = None
1214
1215 if revid and head and revid == head:
1216 ref = os.path.join(self.gitdir, R_HEADS + name)
1217 try:
1218 os.makedirs(os.path.dirname(ref))
1219 except OSError:
1220 pass
1221 _lwrite(ref, '%s\n' % revid)
1222 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1223 'ref: %s%s\n' % (R_HEADS, name))
1224 branch.Save()
1225 return True
1226
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001227 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001228 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001229 capture_stdout = True,
1230 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001231 branch.Save()
1232 return True
1233 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001234
Wink Saville02d79452009-04-10 13:01:24 -07001235 def CheckoutBranch(self, name):
1236 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001237
1238 Args:
1239 name: The name of the branch to checkout.
1240
1241 Returns:
1242 True if the checkout succeeded; False if it didn't; None if the branch
1243 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001244 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001245 rev = R_HEADS + name
1246 head = self.work_git.GetHead()
1247 if head == rev:
1248 # Already on the branch
1249 #
1250 return True
Wink Saville02d79452009-04-10 13:01:24 -07001251
David Pursehouse8a68ff92012-09-24 12:15:13 +09001252 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001253 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001254 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001255 except KeyError:
1256 # Branch does not exist in this project
1257 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001258 return None
Wink Saville02d79452009-04-10 13:01:24 -07001259
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001260 if head.startswith(R_HEADS):
1261 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001262 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001263 except KeyError:
1264 head = None
1265
1266 if head == revid:
1267 # Same revision; just update HEAD to point to the new
1268 # target branch, but otherwise take no other action.
1269 #
1270 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1271 'ref: %s%s\n' % (R_HEADS, name))
1272 return True
1273
1274 return GitCommand(self,
1275 ['checkout', name, '--'],
1276 capture_stdout = True,
1277 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001278
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001279 def AbandonBranch(self, name):
1280 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001281
1282 Args:
1283 name: The name of the branch to abandon.
1284
1285 Returns:
1286 True if the abandon succeeded; False if it didn't; None if the branch
1287 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001288 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001289 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001290 all_refs = self.bare_ref.all
1291 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001292 # Doesn't exist
1293 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001294
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001295 head = self.work_git.GetHead()
1296 if head == rev:
1297 # We can't destroy the branch while we are sitting
1298 # on it. Switch to a detached HEAD.
1299 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001300 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001301
David Pursehouse8a68ff92012-09-24 12:15:13 +09001302 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001303 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001304 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1305 '%s\n' % revid)
1306 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001307 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001308
1309 return GitCommand(self,
1310 ['branch', '-D', name],
1311 capture_stdout = True,
1312 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001313
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001314 def PruneHeads(self):
1315 """Prune any topic branches already merged into upstream.
1316 """
1317 cb = self.CurrentBranch
1318 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001319 left = self._allrefs
1320 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 if name.startswith(R_HEADS):
1322 name = name[len(R_HEADS):]
1323 if cb is None or name != cb:
1324 kill.append(name)
1325
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001326 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327 if cb is not None \
1328 and not self._revlist(HEAD + '...' + rev) \
1329 and not self.IsDirty(consider_untracked = False):
1330 self.work_git.DetachHead(HEAD)
1331 kill.append(cb)
1332
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001333 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001334 old = self.bare_git.GetHead()
1335 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001336 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1337
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001338 try:
1339 self.bare_git.DetachHead(rev)
1340
1341 b = ['branch', '-d']
1342 b.extend(kill)
1343 b = GitCommand(self, b, bare=True,
1344 capture_stdout=True,
1345 capture_stderr=True)
1346 b.Wait()
1347 finally:
1348 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001349 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001351 for branch in kill:
1352 if (R_HEADS + branch) not in left:
1353 self.CleanPublishedCache()
1354 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001355
1356 if cb and cb not in kill:
1357 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001358 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359
1360 kept = []
1361 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001362 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363 branch = self.GetBranch(branch)
1364 base = branch.LocalMerge
1365 if not base:
1366 base = rev
1367 kept.append(ReviewableBranch(self, branch, base))
1368 return kept
1369
1370
1371## Direct Git Commands ##
1372
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001373 def _RemoteFetch(self, name=None,
1374 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001375 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001376 quiet=False,
1377 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001378
1379 is_sha1 = False
1380 tag_name = None
1381
Brian Harring14a66742012-09-28 20:21:57 -07001382 def CheckForSha1():
1383 try:
1384 # if revision (sha or tag) is not present then following function
1385 # throws an error.
1386 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1387 return True
1388 except GitError:
1389 # There is no such persistent revision. We have to fetch it.
1390 return False
1391
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001392 if current_branch_only:
1393 if ID_RE.match(self.revisionExpr) is not None:
1394 is_sha1 = True
1395 elif self.revisionExpr.startswith(R_TAGS):
1396 # this is a tag and its sha1 value should never change
1397 tag_name = self.revisionExpr[len(R_TAGS):]
1398
1399 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001400 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001401 return True
Brian Harring14a66742012-09-28 20:21:57 -07001402 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1403 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001404
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001405 if not name:
1406 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001407
1408 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001409 remote = self.GetRemote(name)
1410 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001411 ssh_proxy = True
1412
Shawn O. Pearce88443382010-10-08 10:02:09 +02001413 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001414 if alt_dir and 'objects' == os.path.basename(alt_dir):
1415 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001416 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1417 remote = self.GetRemote(name)
1418
David Pursehouse8a68ff92012-09-24 12:15:13 +09001419 all_refs = self.bare_ref.all
1420 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001421 tmp = set()
1422
David Pursehouse8a68ff92012-09-24 12:15:13 +09001423 for r, ref_id in GitRefs(ref_dir).all.iteritems():
1424 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001425 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001426 all_refs[r] = ref_id
1427 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001428 continue
1429
David Pursehouse8a68ff92012-09-24 12:15:13 +09001430 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001431 continue
1432
David Pursehouse8a68ff92012-09-24 12:15:13 +09001433 r = 'refs/_alt/%s' % ref_id
1434 all_refs[r] = ref_id
1435 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001436 tmp.add(r)
1437
David Pursehouse8a68ff92012-09-24 12:15:13 +09001438 ref_names = list(all_refs.keys())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001439 ref_names.sort()
1440
1441 tmp_packed = ''
1442 old_packed = ''
1443
1444 for r in ref_names:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001445 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001446 tmp_packed += line
1447 if r not in tmp:
1448 old_packed += line
1449
1450 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001451 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001452 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001453
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001454 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001455
1456 # The --depth option only affects the initial fetch; after that we'll do
1457 # full fetches of changes.
1458 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1459 if depth and initial:
1460 cmd.append('--depth=%s' % depth)
1461
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001462 if quiet:
1463 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001464 if not self.worktree:
1465 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001466 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001467
Brian Harring14a66742012-09-28 20:21:57 -07001468 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001469 # Fetch whole repo
1470 cmd.append('--tags')
1471 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1472 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001473 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001474 cmd.append(tag_name)
1475 else:
1476 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001477 if is_sha1:
1478 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001479 if branch.startswith(R_HEADS):
1480 branch = branch[len(R_HEADS):]
1481 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001482
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001483 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001484 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001485 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1486 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001487 ok = True
1488 break
Brian Harring14a66742012-09-28 20:21:57 -07001489 elif current_branch_only and is_sha1 and ret == 128:
1490 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1491 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1492 # abort the optimization attempt and do a full sync.
1493 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001494 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001495
1496 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001497 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001498 if old_packed != '':
1499 _lwrite(packed_refs, old_packed)
1500 else:
1501 os.remove(packed_refs)
1502 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001503
1504 if is_sha1 and current_branch_only and self.upstream:
1505 # We just synced the upstream given branch; verify we
1506 # got what we wanted, else trigger a second run of all
1507 # refs.
1508 if not CheckForSha1():
1509 return self._RemoteFetch(name=name, current_branch_only=False,
1510 initial=False, quiet=quiet, alt_dir=alt_dir)
1511
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001512 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001513
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001514 def _ApplyCloneBundle(self, initial=False, quiet=False):
1515 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1516 return False
1517
1518 remote = self.GetRemote(self.remote.name)
1519 bundle_url = remote.url + '/clone.bundle'
1520 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001521 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1522 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001523 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1524 return False
1525
1526 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1527 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1528
1529 exist_dst = os.path.exists(bundle_dst)
1530 exist_tmp = os.path.exists(bundle_tmp)
1531
1532 if not initial and not exist_dst and not exist_tmp:
1533 return False
1534
1535 if not exist_dst:
1536 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1537 if not exist_dst:
1538 return False
1539
1540 cmd = ['fetch']
1541 if quiet:
1542 cmd.append('--quiet')
1543 if not self.worktree:
1544 cmd.append('--update-head-ok')
1545 cmd.append(bundle_dst)
1546 for f in remote.fetch:
1547 cmd.append(str(f))
1548 cmd.append('refs/tags/*:refs/tags/*')
1549
1550 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001551 if os.path.exists(bundle_dst):
1552 os.remove(bundle_dst)
1553 if os.path.exists(bundle_tmp):
1554 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001555 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001556
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001557 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001558 if os.path.exists(dstPath):
1559 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001560
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001561 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001562 if quiet:
1563 cmd += ['--silent']
1564 if os.path.exists(tmpPath):
1565 size = os.stat(tmpPath).st_size
1566 if size >= 1024:
1567 cmd += ['--continue-at', '%d' % (size,)]
1568 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001569 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001570 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1571 cmd += ['--proxy', os.environ['http_proxy']]
1572 cmd += [srcUrl]
1573
1574 if IsTrace():
1575 Trace('%s', ' '.join(cmd))
1576 try:
1577 proc = subprocess.Popen(cmd)
1578 except OSError:
1579 return False
1580
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001581 curlret = proc.wait()
1582
1583 if curlret == 22:
1584 # From curl man page:
1585 # 22: HTTP page not retrieved. The requested url was not found or
1586 # returned another error with the HTTP error code being 400 or above.
1587 # This return code only appears if -f, --fail is used.
1588 if not quiet:
1589 print >> sys.stderr, "Server does not provide clone.bundle; ignoring."
1590 return False
1591
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001592 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001593 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001594 os.rename(tmpPath, dstPath)
1595 return True
1596 else:
1597 os.remove(tmpPath)
1598 return False
1599 else:
1600 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001601
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001602 def _Checkout(self, rev, quiet=False):
1603 cmd = ['checkout']
1604 if quiet:
1605 cmd.append('-q')
1606 cmd.append(rev)
1607 cmd.append('--')
1608 if GitCommand(self, cmd).Wait() != 0:
1609 if self._allrefs:
1610 raise GitError('%s checkout %s ' % (self.name, rev))
1611
Pierre Tardye5a21222011-03-24 16:28:18 +01001612 def _CherryPick(self, rev, quiet=False):
1613 cmd = ['cherry-pick']
1614 cmd.append(rev)
1615 cmd.append('--')
1616 if GitCommand(self, cmd).Wait() != 0:
1617 if self._allrefs:
1618 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1619
Erwan Mahea94f1622011-08-19 13:56:09 +02001620 def _Revert(self, rev, quiet=False):
1621 cmd = ['revert']
1622 cmd.append('--no-edit')
1623 cmd.append(rev)
1624 cmd.append('--')
1625 if GitCommand(self, cmd).Wait() != 0:
1626 if self._allrefs:
1627 raise GitError('%s revert %s ' % (self.name, rev))
1628
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001629 def _ResetHard(self, rev, quiet=True):
1630 cmd = ['reset', '--hard']
1631 if quiet:
1632 cmd.append('-q')
1633 cmd.append(rev)
1634 if GitCommand(self, cmd).Wait() != 0:
1635 raise GitError('%s reset --hard %s ' % (self.name, rev))
1636
1637 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001638 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001639 if onto is not None:
1640 cmd.extend(['--onto', onto])
1641 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001642 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001643 raise GitError('%s rebase %s ' % (self.name, upstream))
1644
Pierre Tardy3d125942012-05-04 12:18:12 +02001645 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001646 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001647 if ffonly:
1648 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001649 if GitCommand(self, cmd).Wait() != 0:
1650 raise GitError('%s merge %s ' % (self.name, head))
1651
1652 def _InitGitDir(self):
1653 if not os.path.exists(self.gitdir):
1654 os.makedirs(self.gitdir)
1655 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001656
Shawn O. Pearce88443382010-10-08 10:02:09 +02001657 mp = self.manifest.manifestProject
1658 ref_dir = mp.config.GetString('repo.reference')
1659
1660 if ref_dir:
1661 mirror_git = os.path.join(ref_dir, self.name + '.git')
1662 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1663 self.relpath + '.git')
1664
1665 if os.path.exists(mirror_git):
1666 ref_dir = mirror_git
1667
1668 elif os.path.exists(repo_git):
1669 ref_dir = repo_git
1670
1671 else:
1672 ref_dir = None
1673
1674 if ref_dir:
1675 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1676 os.path.join(ref_dir, 'objects') + '\n')
1677
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001678 if self.manifest.IsMirror:
1679 self.config.SetString('core.bare', 'true')
1680 else:
1681 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001682
1683 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001684 try:
1685 to_rm = os.listdir(hooks)
1686 except OSError:
1687 to_rm = []
1688 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001689 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001690 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691
1692 m = self.manifest.manifestProject.config
1693 for key in ['user.name', 'user.email']:
1694 if m.Has(key, include_defaults = False):
1695 self.config.SetString(key, m.GetString(key))
1696
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001697 def _InitHooks(self):
1698 hooks = self._gitdir_path('hooks')
1699 if not os.path.exists(hooks):
1700 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001701 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001702 name = os.path.basename(stock_hook)
1703
Victor Boivie65e0f352011-04-18 11:23:29 +02001704 if name in ('commit-msg',) and not self.remote.review \
1705 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001706 # Don't install a Gerrit Code Review hook if this
1707 # project does not appear to use it for reviews.
1708 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001709 # Since the manifest project is one of those, but also
1710 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001711 continue
1712
1713 dst = os.path.join(hooks, name)
1714 if os.path.islink(dst):
1715 continue
1716 if os.path.exists(dst):
1717 if filecmp.cmp(stock_hook, dst, shallow=False):
1718 os.remove(dst)
1719 else:
1720 _error("%s: Not replacing %s hook", self.relpath, name)
1721 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001722 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001723 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001724 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001725 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001726 raise GitError('filesystem must support symlinks')
1727 else:
1728 raise
1729
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001730 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001731 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001732 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001733 remote.url = self.remote.url
1734 remote.review = self.remote.review
1735 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001736
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001737 if self.worktree:
1738 remote.ResetFetch(mirror=False)
1739 else:
1740 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001741 remote.Save()
1742
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001743 def _InitMRef(self):
1744 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001745 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001746
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001747 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001748 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001749
1750 def _InitAnyMRef(self, ref):
1751 cur = self.bare_ref.symref(ref)
1752
1753 if self.revisionId:
1754 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1755 msg = 'manifest set to %s' % self.revisionId
1756 dst = self.revisionId + '^0'
1757 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1758 else:
1759 remote = self.GetRemote(self.remote.name)
1760 dst = remote.ToLocal(self.revisionExpr)
1761 if cur != dst:
1762 msg = 'manifest set to %s' % self.revisionExpr
1763 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001764
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001765 def _InitWorkTree(self):
1766 dotgit = os.path.join(self.worktree, '.git')
1767 if not os.path.exists(dotgit):
1768 os.makedirs(dotgit)
1769
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001770 for name in ['config',
1771 'description',
1772 'hooks',
1773 'info',
1774 'logs',
1775 'objects',
1776 'packed-refs',
1777 'refs',
1778 'rr-cache',
1779 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001780 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001781 src = os.path.join(self.gitdir, name)
1782 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001783 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001784 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001785 else:
1786 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001787 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001788 if e.errno == errno.EPERM:
1789 raise GitError('filesystem must support symlinks')
1790 else:
1791 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001792
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001793 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001794
1795 cmd = ['read-tree', '--reset', '-u']
1796 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001797 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001798 if GitCommand(self, cmd).Wait() != 0:
1799 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001800
1801 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1802 if not os.path.exists(rr_cache):
1803 os.makedirs(rr_cache)
1804
Shawn O. Pearce93609662009-04-21 10:50:33 -07001805 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001806
1807 def _gitdir_path(self, path):
1808 return os.path.join(self.gitdir, path)
1809
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001810 def _revlist(self, *args, **kw):
1811 a = []
1812 a.extend(args)
1813 a.append('--')
1814 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001815
1816 @property
1817 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001818 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001819
1820 class _GitGetByExec(object):
1821 def __init__(self, project, bare):
1822 self._project = project
1823 self._bare = bare
1824
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001825 def LsOthers(self):
1826 p = GitCommand(self._project,
1827 ['ls-files',
1828 '-z',
1829 '--others',
1830 '--exclude-standard'],
1831 bare = False,
1832 capture_stdout = True,
1833 capture_stderr = True)
1834 if p.Wait() == 0:
1835 out = p.stdout
1836 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09001837 return out[:-1].split('\0') # pylint: disable=W1401
1838 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001839 return []
1840
1841 def DiffZ(self, name, *args):
1842 cmd = [name]
1843 cmd.append('-z')
1844 cmd.extend(args)
1845 p = GitCommand(self._project,
1846 cmd,
1847 bare = False,
1848 capture_stdout = True,
1849 capture_stderr = True)
1850 try:
1851 out = p.process.stdout.read()
1852 r = {}
1853 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09001854 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001855 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001856 try:
1857 info = out.next()
1858 path = out.next()
1859 except StopIteration:
1860 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001861
1862 class _Info(object):
1863 def __init__(self, path, omode, nmode, oid, nid, state):
1864 self.path = path
1865 self.src_path = None
1866 self.old_mode = omode
1867 self.new_mode = nmode
1868 self.old_id = oid
1869 self.new_id = nid
1870
1871 if len(state) == 1:
1872 self.status = state
1873 self.level = None
1874 else:
1875 self.status = state[:1]
1876 self.level = state[1:]
1877 while self.level.startswith('0'):
1878 self.level = self.level[1:]
1879
1880 info = info[1:].split(' ')
1881 info =_Info(path, *info)
1882 if info.status in ('R', 'C'):
1883 info.src_path = info.path
1884 info.path = out.next()
1885 r[info.path] = info
1886 return r
1887 finally:
1888 p.Wait()
1889
1890 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001891 if self._bare:
1892 path = os.path.join(self._project.gitdir, HEAD)
1893 else:
1894 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001895 fd = open(path, 'rb')
1896 try:
1897 line = fd.read()
1898 finally:
1899 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001900 if line.startswith('ref: '):
1901 return line[5:-1]
1902 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001903
1904 def SetHead(self, ref, message=None):
1905 cmdv = []
1906 if message is not None:
1907 cmdv.extend(['-m', message])
1908 cmdv.append(HEAD)
1909 cmdv.append(ref)
1910 self.symbolic_ref(*cmdv)
1911
1912 def DetachHead(self, new, message=None):
1913 cmdv = ['--no-deref']
1914 if message is not None:
1915 cmdv.extend(['-m', message])
1916 cmdv.append(HEAD)
1917 cmdv.append(new)
1918 self.update_ref(*cmdv)
1919
1920 def UpdateRef(self, name, new, old=None,
1921 message=None,
1922 detach=False):
1923 cmdv = []
1924 if message is not None:
1925 cmdv.extend(['-m', message])
1926 if detach:
1927 cmdv.append('--no-deref')
1928 cmdv.append(name)
1929 cmdv.append(new)
1930 if old is not None:
1931 cmdv.append(old)
1932 self.update_ref(*cmdv)
1933
1934 def DeleteRef(self, name, old=None):
1935 if not old:
1936 old = self.rev_parse(name)
1937 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001938 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001939
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001940 def rev_list(self, *args, **kw):
1941 if 'format' in kw:
1942 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1943 else:
1944 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001945 cmdv.extend(args)
1946 p = GitCommand(self._project,
1947 cmdv,
1948 bare = self._bare,
1949 capture_stdout = True,
1950 capture_stderr = True)
1951 r = []
1952 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001953 if line[-1] == '\n':
1954 line = line[:-1]
1955 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001956 if p.Wait() != 0:
1957 raise GitError('%s rev-list %s: %s' % (
1958 self._project.name,
1959 str(args),
1960 p.stderr))
1961 return r
1962
1963 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001964 """Allow arbitrary git commands using pythonic syntax.
1965
1966 This allows you to do things like:
1967 git_obj.rev_parse('HEAD')
1968
1969 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1970 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07001971 Any other positional arguments will be passed to the git command, and the
1972 following keyword arguments are supported:
1973 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08001974
1975 Args:
1976 name: The name of the git command to call. Any '_' characters will
1977 be replaced with '-'.
1978
1979 Returns:
1980 A callable object that will try to call git with the named command.
1981 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001982 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07001983 def runner(*args, **kwargs):
1984 cmdv = []
1985 config = kwargs.pop('config', None)
1986 for k in kwargs:
1987 raise TypeError('%s() got an unexpected keyword argument %r'
1988 % (name, k))
1989 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07001990 if not git_require((1, 7, 2)):
1991 raise ValueError('cannot set config on command line for %s()'
1992 % name)
Dave Borowitz091f8932012-10-23 17:01:04 -07001993 for k, v in config.iteritems():
1994 cmdv.append('-c')
1995 cmdv.append('%s=%s' % (k, v))
1996 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001997 cmdv.extend(args)
1998 p = GitCommand(self._project,
1999 cmdv,
2000 bare = self._bare,
2001 capture_stdout = True,
2002 capture_stderr = True)
2003 if p.Wait() != 0:
2004 raise GitError('%s %s: %s' % (
2005 self._project.name,
2006 name,
2007 p.stderr))
2008 r = p.stdout
2009 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2010 return r[:-1]
2011 return r
2012 return runner
2013
2014
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002015class _PriorSyncFailedError(Exception):
2016 def __str__(self):
2017 return 'prior sync failed; rebase still in progress'
2018
2019class _DirtyError(Exception):
2020 def __str__(self):
2021 return 'contains uncommitted changes'
2022
2023class _InfoMessage(object):
2024 def __init__(self, project, text):
2025 self.project = project
2026 self.text = text
2027
2028 def Print(self, syncbuf):
2029 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2030 syncbuf.out.nl()
2031
2032class _Failure(object):
2033 def __init__(self, project, why):
2034 self.project = project
2035 self.why = why
2036
2037 def Print(self, syncbuf):
2038 syncbuf.out.fail('error: %s/: %s',
2039 self.project.relpath,
2040 str(self.why))
2041 syncbuf.out.nl()
2042
2043class _Later(object):
2044 def __init__(self, project, action):
2045 self.project = project
2046 self.action = action
2047
2048 def Run(self, syncbuf):
2049 out = syncbuf.out
2050 out.project('project %s/', self.project.relpath)
2051 out.nl()
2052 try:
2053 self.action()
2054 out.nl()
2055 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002056 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002057 out.nl()
2058 return False
2059
2060class _SyncColoring(Coloring):
2061 def __init__(self, config):
2062 Coloring.__init__(self, config, 'reposync')
2063 self.project = self.printer('header', attr = 'bold')
2064 self.info = self.printer('info')
2065 self.fail = self.printer('fail', fg='red')
2066
2067class SyncBuffer(object):
2068 def __init__(self, config, detach_head=False):
2069 self._messages = []
2070 self._failures = []
2071 self._later_queue1 = []
2072 self._later_queue2 = []
2073
2074 self.out = _SyncColoring(config)
2075 self.out.redirect(sys.stderr)
2076
2077 self.detach_head = detach_head
2078 self.clean = True
2079
2080 def info(self, project, fmt, *args):
2081 self._messages.append(_InfoMessage(project, fmt % args))
2082
2083 def fail(self, project, err=None):
2084 self._failures.append(_Failure(project, err))
2085 self.clean = False
2086
2087 def later1(self, project, what):
2088 self._later_queue1.append(_Later(project, what))
2089
2090 def later2(self, project, what):
2091 self._later_queue2.append(_Later(project, what))
2092
2093 def Finish(self):
2094 self._PrintMessages()
2095 self._RunLater()
2096 self._PrintMessages()
2097 return self.clean
2098
2099 def _RunLater(self):
2100 for q in ['_later_queue1', '_later_queue2']:
2101 if not self._RunQueue(q):
2102 return
2103
2104 def _RunQueue(self, queue):
2105 for m in getattr(self, queue):
2106 if not m.Run(self):
2107 self.clean = False
2108 return False
2109 setattr(self, queue, [])
2110 return True
2111
2112 def _PrintMessages(self):
2113 for m in self._messages:
2114 m.Print(self)
2115 for m in self._failures:
2116 m.Print(self)
2117
2118 self._messages = []
2119 self._failures = []
2120
2121
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002122class MetaProject(Project):
2123 """A special project housed under .repo.
2124 """
2125 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002126 Project.__init__(self,
2127 manifest = manifest,
2128 name = name,
2129 gitdir = gitdir,
2130 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002131 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002132 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002133 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002134 revisionId = None,
2135 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002136
2137 def PreSync(self):
2138 if self.Exists:
2139 cb = self.CurrentBranch
2140 if cb:
2141 base = self.GetBranch(cb).merge
2142 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002143 self.revisionExpr = base
2144 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002145
Florian Vallee5d016502012-06-07 17:19:26 +02002146 def MetaBranchSwitch(self, target):
2147 """ Prepare MetaProject for manifest branch switch
2148 """
2149
2150 # detach and delete manifest branch, allowing a new
2151 # branch to take over
2152 syncbuf = SyncBuffer(self.config, detach_head = True)
2153 self.Sync_LocalHalf(syncbuf)
2154 syncbuf.Finish()
2155
2156 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002157 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002158 capture_stdout = True,
2159 capture_stderr = True).Wait() == 0
2160
2161
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002162 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002163 def LastFetch(self):
2164 try:
2165 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2166 return os.path.getmtime(fh)
2167 except OSError:
2168 return 0
2169
2170 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002171 def HasChanges(self):
2172 """Has the remote received new commits not yet checked out?
2173 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002174 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002175 return False
2176
David Pursehouse8a68ff92012-09-24 12:15:13 +09002177 all_refs = self.bare_ref.all
2178 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002179 head = self.work_git.GetHead()
2180 if head.startswith(R_HEADS):
2181 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002182 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002183 except KeyError:
2184 head = None
2185
2186 if revid == head:
2187 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002188 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002189 return True
2190 return False