blob: 099d0b5a79862a67d904e5a5232c5ffe63c1682d [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:
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,
487 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
1015 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001016 head = self.work_git.GetHead()
1017 if head.startswith(R_HEADS):
1018 branch = head[len(R_HEADS):]
1019 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001020 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001021 except KeyError:
1022 head = None
1023 else:
1024 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001025
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001026 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001027 # Currently on a detached HEAD. The user is assumed to
1028 # not have any local modifications worth worrying about.
1029 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001030 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001031 syncbuf.fail(self, _PriorSyncFailedError())
1032 return
1033
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001034 if head == revid:
1035 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001036 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001037 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001038 if not syncbuf.detach_head:
1039 return
1040 else:
1041 lost = self._revlist(not_rev(revid), HEAD)
1042 if lost:
1043 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001044
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001045 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001046 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001047 except GitError, e:
1048 syncbuf.fail(self, e)
1049 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001050 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001051 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001052
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001053 if head == revid:
1054 # No changes; don't do anything further.
1055 #
1056 return
1057
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001060 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001062 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001064 syncbuf.info(self,
1065 "leaving %s; does not track upstream",
1066 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001068 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001069 except GitError, e:
1070 syncbuf.fail(self, e)
1071 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001073 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001075 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001076 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001078 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 if not_merged:
1080 if upstream_gain:
1081 # The user has published this branch and some of those
1082 # commits are not yet merged upstream. We do not want
1083 # to rewrite the published commits so we punt.
1084 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001085 syncbuf.fail(self,
1086 "branch %s is published (but not merged) and is now %d commits behind"
1087 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001088 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001089 elif pub == head:
1090 # All published commits are merged, and thus we are a
1091 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001092 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001093 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001094 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001095 self._CopyFiles()
1096 syncbuf.later1(self, _doff)
1097 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001098
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001099 # Examine the local commits not in the remote. Find the
1100 # last one attributed to this user, if any.
1101 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001102 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001103 last_mine = None
1104 cnt_mine = 0
1105 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001106 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001107 if committer_email == self.UserEmail:
1108 last_mine = commit_id
1109 cnt_mine += 1
1110
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001111 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001112 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001113
1114 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001115 syncbuf.fail(self, _DirtyError())
1116 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001118 # If the upstream switched on us, warn the user.
1119 #
1120 if branch.merge != self.revisionExpr:
1121 if branch.merge and self.revisionExpr:
1122 syncbuf.info(self,
1123 'manifest switched %s...%s',
1124 branch.merge,
1125 self.revisionExpr)
1126 elif branch.merge:
1127 syncbuf.info(self,
1128 'manifest no longer tracks %s',
1129 branch.merge)
1130
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001131 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001132 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001133 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001134 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001135 syncbuf.info(self,
1136 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001137 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001138
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001139 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001140 if not ID_RE.match(self.revisionExpr):
1141 # in case of manifest sync the revisionExpr might be a SHA1
1142 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001143 branch.Save()
1144
Mike Pontillod3153822012-02-28 11:53:24 -08001145 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001146 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001147 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001148 self._CopyFiles()
1149 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001150 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001151 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001152 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001153 self._CopyFiles()
1154 except GitError, e:
1155 syncbuf.fail(self, e)
1156 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001157 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001158 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001159 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001160 self._CopyFiles()
1161 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001163 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001164 # dest should already be an absolute path, but src is project relative
1165 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001166 abssrc = os.path.join(self.worktree, src)
1167 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001168
James W. Mills24c13082012-04-12 15:04:13 -05001169 def AddAnnotation(self, name, value, keep):
1170 self.annotations.append(_Annotation(name, value, keep))
1171
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001172 def DownloadPatchSet(self, change_id, patch_id):
1173 """Download a single patch set of a single change to FETCH_HEAD.
1174 """
1175 remote = self.GetRemote(self.remote.name)
1176
1177 cmd = ['fetch', remote.name]
1178 cmd.append('refs/changes/%2.2d/%d/%d' \
1179 % (change_id % 100, change_id, patch_id))
1180 cmd.extend(map(lambda x: str(x), remote.fetch))
1181 if GitCommand(self, cmd, bare=True).Wait() != 0:
1182 return None
1183 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001184 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001185 change_id,
1186 patch_id,
1187 self.bare_git.rev_parse('FETCH_HEAD'))
1188
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001189
1190## Branch Management ##
1191
1192 def StartBranch(self, name):
1193 """Create a new branch off the manifest's revision.
1194 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001195 head = self.work_git.GetHead()
1196 if head == (R_HEADS + name):
1197 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001198
David Pursehouse8a68ff92012-09-24 12:15:13 +09001199 all_refs = self.bare_ref.all
1200 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001201 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001202 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001203 capture_stdout = True,
1204 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001205
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001206 branch = self.GetBranch(name)
1207 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001208 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001209 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001210
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001211 if head.startswith(R_HEADS):
1212 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001213 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001214 except KeyError:
1215 head = None
1216
1217 if revid and head and revid == head:
1218 ref = os.path.join(self.gitdir, R_HEADS + name)
1219 try:
1220 os.makedirs(os.path.dirname(ref))
1221 except OSError:
1222 pass
1223 _lwrite(ref, '%s\n' % revid)
1224 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1225 'ref: %s%s\n' % (R_HEADS, name))
1226 branch.Save()
1227 return True
1228
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001229 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001230 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001231 capture_stdout = True,
1232 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001233 branch.Save()
1234 return True
1235 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001236
Wink Saville02d79452009-04-10 13:01:24 -07001237 def CheckoutBranch(self, name):
1238 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001239
1240 Args:
1241 name: The name of the branch to checkout.
1242
1243 Returns:
1244 True if the checkout succeeded; False if it didn't; None if the branch
1245 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001246 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001247 rev = R_HEADS + name
1248 head = self.work_git.GetHead()
1249 if head == rev:
1250 # Already on the branch
1251 #
1252 return True
Wink Saville02d79452009-04-10 13:01:24 -07001253
David Pursehouse8a68ff92012-09-24 12:15:13 +09001254 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001255 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001256 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001257 except KeyError:
1258 # Branch does not exist in this project
1259 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001260 return None
Wink Saville02d79452009-04-10 13:01:24 -07001261
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001262 if head.startswith(R_HEADS):
1263 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001264 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001265 except KeyError:
1266 head = None
1267
1268 if head == revid:
1269 # Same revision; just update HEAD to point to the new
1270 # target branch, but otherwise take no other action.
1271 #
1272 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1273 'ref: %s%s\n' % (R_HEADS, name))
1274 return True
1275
1276 return GitCommand(self,
1277 ['checkout', name, '--'],
1278 capture_stdout = True,
1279 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001280
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001281 def AbandonBranch(self, name):
1282 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001283
1284 Args:
1285 name: The name of the branch to abandon.
1286
1287 Returns:
1288 True if the abandon succeeded; False if it didn't; None if the branch
1289 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001290 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001291 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001292 all_refs = self.bare_ref.all
1293 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001294 # Doesn't exist
1295 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001296
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001297 head = self.work_git.GetHead()
1298 if head == rev:
1299 # We can't destroy the branch while we are sitting
1300 # on it. Switch to a detached HEAD.
1301 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001302 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001303
David Pursehouse8a68ff92012-09-24 12:15:13 +09001304 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001305 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001306 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1307 '%s\n' % revid)
1308 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001309 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001310
1311 return GitCommand(self,
1312 ['branch', '-D', name],
1313 capture_stdout = True,
1314 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001315
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316 def PruneHeads(self):
1317 """Prune any topic branches already merged into upstream.
1318 """
1319 cb = self.CurrentBranch
1320 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001321 left = self._allrefs
1322 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323 if name.startswith(R_HEADS):
1324 name = name[len(R_HEADS):]
1325 if cb is None or name != cb:
1326 kill.append(name)
1327
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001328 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329 if cb is not None \
1330 and not self._revlist(HEAD + '...' + rev) \
1331 and not self.IsDirty(consider_untracked = False):
1332 self.work_git.DetachHead(HEAD)
1333 kill.append(cb)
1334
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001335 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001336 old = self.bare_git.GetHead()
1337 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001338 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1339
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001340 try:
1341 self.bare_git.DetachHead(rev)
1342
1343 b = ['branch', '-d']
1344 b.extend(kill)
1345 b = GitCommand(self, b, bare=True,
1346 capture_stdout=True,
1347 capture_stderr=True)
1348 b.Wait()
1349 finally:
1350 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001351 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001352
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001353 for branch in kill:
1354 if (R_HEADS + branch) not in left:
1355 self.CleanPublishedCache()
1356 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357
1358 if cb and cb not in kill:
1359 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001360 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001361
1362 kept = []
1363 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001364 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365 branch = self.GetBranch(branch)
1366 base = branch.LocalMerge
1367 if not base:
1368 base = rev
1369 kept.append(ReviewableBranch(self, branch, base))
1370 return kept
1371
1372
1373## Direct Git Commands ##
1374
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001375 def _RemoteFetch(self, name=None,
1376 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001377 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001378 quiet=False,
1379 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001380
1381 is_sha1 = False
1382 tag_name = None
1383
Brian Harring14a66742012-09-28 20:21:57 -07001384 def CheckForSha1():
1385 try:
1386 # if revision (sha or tag) is not present then following function
1387 # throws an error.
1388 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1389 return True
1390 except GitError:
1391 # There is no such persistent revision. We have to fetch it.
1392 return False
1393
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001394 if current_branch_only:
1395 if ID_RE.match(self.revisionExpr) is not None:
1396 is_sha1 = True
1397 elif self.revisionExpr.startswith(R_TAGS):
1398 # this is a tag and its sha1 value should never change
1399 tag_name = self.revisionExpr[len(R_TAGS):]
1400
1401 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001402 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001403 return True
Brian Harring14a66742012-09-28 20:21:57 -07001404 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1405 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001406
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407 if not name:
1408 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001409
1410 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001411 remote = self.GetRemote(name)
1412 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001413 ssh_proxy = True
1414
Shawn O. Pearce88443382010-10-08 10:02:09 +02001415 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001416 if alt_dir and 'objects' == os.path.basename(alt_dir):
1417 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001418 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1419 remote = self.GetRemote(name)
1420
David Pursehouse8a68ff92012-09-24 12:15:13 +09001421 all_refs = self.bare_ref.all
1422 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001423 tmp = set()
1424
David Pursehouse8a68ff92012-09-24 12:15:13 +09001425 for r, ref_id in GitRefs(ref_dir).all.iteritems():
1426 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001427 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001428 all_refs[r] = ref_id
1429 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001430 continue
1431
David Pursehouse8a68ff92012-09-24 12:15:13 +09001432 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001433 continue
1434
David Pursehouse8a68ff92012-09-24 12:15:13 +09001435 r = 'refs/_alt/%s' % ref_id
1436 all_refs[r] = ref_id
1437 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001438 tmp.add(r)
1439
David Pursehouse8a68ff92012-09-24 12:15:13 +09001440 ref_names = list(all_refs.keys())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001441 ref_names.sort()
1442
1443 tmp_packed = ''
1444 old_packed = ''
1445
1446 for r in ref_names:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001447 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001448 tmp_packed += line
1449 if r not in tmp:
1450 old_packed += line
1451
1452 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001453 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001454 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001455
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001456 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001457
1458 # The --depth option only affects the initial fetch; after that we'll do
1459 # full fetches of changes.
1460 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1461 if depth and initial:
1462 cmd.append('--depth=%s' % depth)
1463
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001464 if quiet:
1465 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001466 if not self.worktree:
1467 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001468 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001469
Brian Harring14a66742012-09-28 20:21:57 -07001470 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001471 # Fetch whole repo
1472 cmd.append('--tags')
1473 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1474 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001475 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001476 cmd.append(tag_name)
1477 else:
1478 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001479 if is_sha1:
1480 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001481 if branch.startswith(R_HEADS):
1482 branch = branch[len(R_HEADS):]
1483 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001484
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001485 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001486 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001487 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1488 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001489 ok = True
1490 break
Brian Harring14a66742012-09-28 20:21:57 -07001491 elif current_branch_only and is_sha1 and ret == 128:
1492 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1493 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1494 # abort the optimization attempt and do a full sync.
1495 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001496 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001497
1498 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001499 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001500 if old_packed != '':
1501 _lwrite(packed_refs, old_packed)
1502 else:
1503 os.remove(packed_refs)
1504 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001505
1506 if is_sha1 and current_branch_only and self.upstream:
1507 # We just synced the upstream given branch; verify we
1508 # got what we wanted, else trigger a second run of all
1509 # refs.
1510 if not CheckForSha1():
1511 return self._RemoteFetch(name=name, current_branch_only=False,
1512 initial=False, quiet=quiet, alt_dir=alt_dir)
1513
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001514 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001515
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001516 def _ApplyCloneBundle(self, initial=False, quiet=False):
1517 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1518 return False
1519
1520 remote = self.GetRemote(self.remote.name)
1521 bundle_url = remote.url + '/clone.bundle'
1522 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001523 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1524 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001525 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1526 return False
1527
1528 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1529 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1530
1531 exist_dst = os.path.exists(bundle_dst)
1532 exist_tmp = os.path.exists(bundle_tmp)
1533
1534 if not initial and not exist_dst and not exist_tmp:
1535 return False
1536
1537 if not exist_dst:
1538 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1539 if not exist_dst:
1540 return False
1541
1542 cmd = ['fetch']
1543 if quiet:
1544 cmd.append('--quiet')
1545 if not self.worktree:
1546 cmd.append('--update-head-ok')
1547 cmd.append(bundle_dst)
1548 for f in remote.fetch:
1549 cmd.append(str(f))
1550 cmd.append('refs/tags/*:refs/tags/*')
1551
1552 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001553 if os.path.exists(bundle_dst):
1554 os.remove(bundle_dst)
1555 if os.path.exists(bundle_tmp):
1556 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001557 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001558
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001559 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001560 if os.path.exists(dstPath):
1561 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001562
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001563 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001564 if quiet:
1565 cmd += ['--silent']
1566 if os.path.exists(tmpPath):
1567 size = os.stat(tmpPath).st_size
1568 if size >= 1024:
1569 cmd += ['--continue-at', '%d' % (size,)]
1570 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001571 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001572 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1573 cmd += ['--proxy', os.environ['http_proxy']]
1574 cmd += [srcUrl]
1575
1576 if IsTrace():
1577 Trace('%s', ' '.join(cmd))
1578 try:
1579 proc = subprocess.Popen(cmd)
1580 except OSError:
1581 return False
1582
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001583 curlret = proc.wait()
1584
1585 if curlret == 22:
1586 # From curl man page:
1587 # 22: HTTP page not retrieved. The requested url was not found or
1588 # returned another error with the HTTP error code being 400 or above.
1589 # This return code only appears if -f, --fail is used.
1590 if not quiet:
1591 print >> sys.stderr, "Server does not provide clone.bundle; ignoring."
1592 return False
1593
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001594 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001595 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001596 os.rename(tmpPath, dstPath)
1597 return True
1598 else:
1599 os.remove(tmpPath)
1600 return False
1601 else:
1602 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001603
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001604 def _Checkout(self, rev, quiet=False):
1605 cmd = ['checkout']
1606 if quiet:
1607 cmd.append('-q')
1608 cmd.append(rev)
1609 cmd.append('--')
1610 if GitCommand(self, cmd).Wait() != 0:
1611 if self._allrefs:
1612 raise GitError('%s checkout %s ' % (self.name, rev))
1613
Pierre Tardye5a21222011-03-24 16:28:18 +01001614 def _CherryPick(self, rev, quiet=False):
1615 cmd = ['cherry-pick']
1616 cmd.append(rev)
1617 cmd.append('--')
1618 if GitCommand(self, cmd).Wait() != 0:
1619 if self._allrefs:
1620 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1621
Erwan Mahea94f1622011-08-19 13:56:09 +02001622 def _Revert(self, rev, quiet=False):
1623 cmd = ['revert']
1624 cmd.append('--no-edit')
1625 cmd.append(rev)
1626 cmd.append('--')
1627 if GitCommand(self, cmd).Wait() != 0:
1628 if self._allrefs:
1629 raise GitError('%s revert %s ' % (self.name, rev))
1630
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001631 def _ResetHard(self, rev, quiet=True):
1632 cmd = ['reset', '--hard']
1633 if quiet:
1634 cmd.append('-q')
1635 cmd.append(rev)
1636 if GitCommand(self, cmd).Wait() != 0:
1637 raise GitError('%s reset --hard %s ' % (self.name, rev))
1638
1639 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001640 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001641 if onto is not None:
1642 cmd.extend(['--onto', onto])
1643 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001644 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001645 raise GitError('%s rebase %s ' % (self.name, upstream))
1646
Pierre Tardy3d125942012-05-04 12:18:12 +02001647 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001648 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001649 if ffonly:
1650 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001651 if GitCommand(self, cmd).Wait() != 0:
1652 raise GitError('%s merge %s ' % (self.name, head))
1653
1654 def _InitGitDir(self):
1655 if not os.path.exists(self.gitdir):
1656 os.makedirs(self.gitdir)
1657 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001658
Shawn O. Pearce88443382010-10-08 10:02:09 +02001659 mp = self.manifest.manifestProject
1660 ref_dir = mp.config.GetString('repo.reference')
1661
1662 if ref_dir:
1663 mirror_git = os.path.join(ref_dir, self.name + '.git')
1664 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1665 self.relpath + '.git')
1666
1667 if os.path.exists(mirror_git):
1668 ref_dir = mirror_git
1669
1670 elif os.path.exists(repo_git):
1671 ref_dir = repo_git
1672
1673 else:
1674 ref_dir = None
1675
1676 if ref_dir:
1677 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1678 os.path.join(ref_dir, 'objects') + '\n')
1679
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001680 if self.manifest.IsMirror:
1681 self.config.SetString('core.bare', 'true')
1682 else:
1683 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001684
1685 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001686 try:
1687 to_rm = os.listdir(hooks)
1688 except OSError:
1689 to_rm = []
1690 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001692 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001693
1694 m = self.manifest.manifestProject.config
1695 for key in ['user.name', 'user.email']:
1696 if m.Has(key, include_defaults = False):
1697 self.config.SetString(key, m.GetString(key))
1698
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001699 def _InitHooks(self):
1700 hooks = self._gitdir_path('hooks')
1701 if not os.path.exists(hooks):
1702 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001703 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001704 name = os.path.basename(stock_hook)
1705
Victor Boivie65e0f352011-04-18 11:23:29 +02001706 if name in ('commit-msg',) and not self.remote.review \
1707 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001708 # Don't install a Gerrit Code Review hook if this
1709 # project does not appear to use it for reviews.
1710 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001711 # Since the manifest project is one of those, but also
1712 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001713 continue
1714
1715 dst = os.path.join(hooks, name)
1716 if os.path.islink(dst):
1717 continue
1718 if os.path.exists(dst):
1719 if filecmp.cmp(stock_hook, dst, shallow=False):
1720 os.remove(dst)
1721 else:
1722 _error("%s: Not replacing %s hook", self.relpath, name)
1723 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001724 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001725 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001726 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001727 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001728 raise GitError('filesystem must support symlinks')
1729 else:
1730 raise
1731
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001732 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001733 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001734 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001735 remote.url = self.remote.url
1736 remote.review = self.remote.review
1737 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001738
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001739 if self.worktree:
1740 remote.ResetFetch(mirror=False)
1741 else:
1742 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001743 remote.Save()
1744
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001745 def _InitMRef(self):
1746 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001747 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001748
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001749 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001750 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001751
1752 def _InitAnyMRef(self, ref):
1753 cur = self.bare_ref.symref(ref)
1754
1755 if self.revisionId:
1756 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1757 msg = 'manifest set to %s' % self.revisionId
1758 dst = self.revisionId + '^0'
1759 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1760 else:
1761 remote = self.GetRemote(self.remote.name)
1762 dst = remote.ToLocal(self.revisionExpr)
1763 if cur != dst:
1764 msg = 'manifest set to %s' % self.revisionExpr
1765 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001766
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001767 def _InitWorkTree(self):
1768 dotgit = os.path.join(self.worktree, '.git')
1769 if not os.path.exists(dotgit):
1770 os.makedirs(dotgit)
1771
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001772 for name in ['config',
1773 'description',
1774 'hooks',
1775 'info',
1776 'logs',
1777 'objects',
1778 'packed-refs',
1779 'refs',
1780 'rr-cache',
1781 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001782 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001783 src = os.path.join(self.gitdir, name)
1784 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001785 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001786 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001787 else:
1788 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001789 except OSError, e:
1790 if e.errno == errno.EPERM:
1791 raise GitError('filesystem must support symlinks')
1792 else:
1793 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001794
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001795 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001796
1797 cmd = ['read-tree', '--reset', '-u']
1798 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001799 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001800 if GitCommand(self, cmd).Wait() != 0:
1801 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001802
1803 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1804 if not os.path.exists(rr_cache):
1805 os.makedirs(rr_cache)
1806
Shawn O. Pearce93609662009-04-21 10:50:33 -07001807 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001808
1809 def _gitdir_path(self, path):
1810 return os.path.join(self.gitdir, path)
1811
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001812 def _revlist(self, *args, **kw):
1813 a = []
1814 a.extend(args)
1815 a.append('--')
1816 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001817
1818 @property
1819 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001820 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001821
1822 class _GitGetByExec(object):
1823 def __init__(self, project, bare):
1824 self._project = project
1825 self._bare = bare
1826
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001827 def LsOthers(self):
1828 p = GitCommand(self._project,
1829 ['ls-files',
1830 '-z',
1831 '--others',
1832 '--exclude-standard'],
1833 bare = False,
1834 capture_stdout = True,
1835 capture_stderr = True)
1836 if p.Wait() == 0:
1837 out = p.stdout
1838 if out:
1839 return out[:-1].split("\0")
1840 return []
1841
1842 def DiffZ(self, name, *args):
1843 cmd = [name]
1844 cmd.append('-z')
1845 cmd.extend(args)
1846 p = GitCommand(self._project,
1847 cmd,
1848 bare = False,
1849 capture_stdout = True,
1850 capture_stderr = True)
1851 try:
1852 out = p.process.stdout.read()
1853 r = {}
1854 if out:
1855 out = iter(out[:-1].split('\0'))
1856 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001857 try:
1858 info = out.next()
1859 path = out.next()
1860 except StopIteration:
1861 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001862
1863 class _Info(object):
1864 def __init__(self, path, omode, nmode, oid, nid, state):
1865 self.path = path
1866 self.src_path = None
1867 self.old_mode = omode
1868 self.new_mode = nmode
1869 self.old_id = oid
1870 self.new_id = nid
1871
1872 if len(state) == 1:
1873 self.status = state
1874 self.level = None
1875 else:
1876 self.status = state[:1]
1877 self.level = state[1:]
1878 while self.level.startswith('0'):
1879 self.level = self.level[1:]
1880
1881 info = info[1:].split(' ')
1882 info =_Info(path, *info)
1883 if info.status in ('R', 'C'):
1884 info.src_path = info.path
1885 info.path = out.next()
1886 r[info.path] = info
1887 return r
1888 finally:
1889 p.Wait()
1890
1891 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001892 if self._bare:
1893 path = os.path.join(self._project.gitdir, HEAD)
1894 else:
1895 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001896 fd = open(path, 'rb')
1897 try:
1898 line = fd.read()
1899 finally:
1900 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001901 if line.startswith('ref: '):
1902 return line[5:-1]
1903 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001904
1905 def SetHead(self, ref, message=None):
1906 cmdv = []
1907 if message is not None:
1908 cmdv.extend(['-m', message])
1909 cmdv.append(HEAD)
1910 cmdv.append(ref)
1911 self.symbolic_ref(*cmdv)
1912
1913 def DetachHead(self, new, message=None):
1914 cmdv = ['--no-deref']
1915 if message is not None:
1916 cmdv.extend(['-m', message])
1917 cmdv.append(HEAD)
1918 cmdv.append(new)
1919 self.update_ref(*cmdv)
1920
1921 def UpdateRef(self, name, new, old=None,
1922 message=None,
1923 detach=False):
1924 cmdv = []
1925 if message is not None:
1926 cmdv.extend(['-m', message])
1927 if detach:
1928 cmdv.append('--no-deref')
1929 cmdv.append(name)
1930 cmdv.append(new)
1931 if old is not None:
1932 cmdv.append(old)
1933 self.update_ref(*cmdv)
1934
1935 def DeleteRef(self, name, old=None):
1936 if not old:
1937 old = self.rev_parse(name)
1938 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001939 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001940
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001941 def rev_list(self, *args, **kw):
1942 if 'format' in kw:
1943 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1944 else:
1945 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001946 cmdv.extend(args)
1947 p = GitCommand(self._project,
1948 cmdv,
1949 bare = self._bare,
1950 capture_stdout = True,
1951 capture_stderr = True)
1952 r = []
1953 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001954 if line[-1] == '\n':
1955 line = line[:-1]
1956 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001957 if p.Wait() != 0:
1958 raise GitError('%s rev-list %s: %s' % (
1959 self._project.name,
1960 str(args),
1961 p.stderr))
1962 return r
1963
1964 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001965 """Allow arbitrary git commands using pythonic syntax.
1966
1967 This allows you to do things like:
1968 git_obj.rev_parse('HEAD')
1969
1970 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1971 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07001972 Any other positional arguments will be passed to the git command, and the
1973 following keyword arguments are supported:
1974 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08001975
1976 Args:
1977 name: The name of the git command to call. Any '_' characters will
1978 be replaced with '-'.
1979
1980 Returns:
1981 A callable object that will try to call git with the named command.
1982 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001983 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07001984 def runner(*args, **kwargs):
1985 cmdv = []
1986 config = kwargs.pop('config', None)
1987 for k in kwargs:
1988 raise TypeError('%s() got an unexpected keyword argument %r'
1989 % (name, k))
1990 if config is not None:
1991 for k, v in config.iteritems():
1992 cmdv.append('-c')
1993 cmdv.append('%s=%s' % (k, v))
1994 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001995 cmdv.extend(args)
1996 p = GitCommand(self._project,
1997 cmdv,
1998 bare = self._bare,
1999 capture_stdout = True,
2000 capture_stderr = True)
2001 if p.Wait() != 0:
2002 raise GitError('%s %s: %s' % (
2003 self._project.name,
2004 name,
2005 p.stderr))
2006 r = p.stdout
2007 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2008 return r[:-1]
2009 return r
2010 return runner
2011
2012
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002013class _PriorSyncFailedError(Exception):
2014 def __str__(self):
2015 return 'prior sync failed; rebase still in progress'
2016
2017class _DirtyError(Exception):
2018 def __str__(self):
2019 return 'contains uncommitted changes'
2020
2021class _InfoMessage(object):
2022 def __init__(self, project, text):
2023 self.project = project
2024 self.text = text
2025
2026 def Print(self, syncbuf):
2027 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2028 syncbuf.out.nl()
2029
2030class _Failure(object):
2031 def __init__(self, project, why):
2032 self.project = project
2033 self.why = why
2034
2035 def Print(self, syncbuf):
2036 syncbuf.out.fail('error: %s/: %s',
2037 self.project.relpath,
2038 str(self.why))
2039 syncbuf.out.nl()
2040
2041class _Later(object):
2042 def __init__(self, project, action):
2043 self.project = project
2044 self.action = action
2045
2046 def Run(self, syncbuf):
2047 out = syncbuf.out
2048 out.project('project %s/', self.project.relpath)
2049 out.nl()
2050 try:
2051 self.action()
2052 out.nl()
2053 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002054 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002055 out.nl()
2056 return False
2057
2058class _SyncColoring(Coloring):
2059 def __init__(self, config):
2060 Coloring.__init__(self, config, 'reposync')
2061 self.project = self.printer('header', attr = 'bold')
2062 self.info = self.printer('info')
2063 self.fail = self.printer('fail', fg='red')
2064
2065class SyncBuffer(object):
2066 def __init__(self, config, detach_head=False):
2067 self._messages = []
2068 self._failures = []
2069 self._later_queue1 = []
2070 self._later_queue2 = []
2071
2072 self.out = _SyncColoring(config)
2073 self.out.redirect(sys.stderr)
2074
2075 self.detach_head = detach_head
2076 self.clean = True
2077
2078 def info(self, project, fmt, *args):
2079 self._messages.append(_InfoMessage(project, fmt % args))
2080
2081 def fail(self, project, err=None):
2082 self._failures.append(_Failure(project, err))
2083 self.clean = False
2084
2085 def later1(self, project, what):
2086 self._later_queue1.append(_Later(project, what))
2087
2088 def later2(self, project, what):
2089 self._later_queue2.append(_Later(project, what))
2090
2091 def Finish(self):
2092 self._PrintMessages()
2093 self._RunLater()
2094 self._PrintMessages()
2095 return self.clean
2096
2097 def _RunLater(self):
2098 for q in ['_later_queue1', '_later_queue2']:
2099 if not self._RunQueue(q):
2100 return
2101
2102 def _RunQueue(self, queue):
2103 for m in getattr(self, queue):
2104 if not m.Run(self):
2105 self.clean = False
2106 return False
2107 setattr(self, queue, [])
2108 return True
2109
2110 def _PrintMessages(self):
2111 for m in self._messages:
2112 m.Print(self)
2113 for m in self._failures:
2114 m.Print(self)
2115
2116 self._messages = []
2117 self._failures = []
2118
2119
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002120class MetaProject(Project):
2121 """A special project housed under .repo.
2122 """
2123 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002124 Project.__init__(self,
2125 manifest = manifest,
2126 name = name,
2127 gitdir = gitdir,
2128 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002129 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002130 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002131 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002132 revisionId = None,
2133 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134
2135 def PreSync(self):
2136 if self.Exists:
2137 cb = self.CurrentBranch
2138 if cb:
2139 base = self.GetBranch(cb).merge
2140 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002141 self.revisionExpr = base
2142 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002143
Florian Vallee5d016502012-06-07 17:19:26 +02002144 def MetaBranchSwitch(self, target):
2145 """ Prepare MetaProject for manifest branch switch
2146 """
2147
2148 # detach and delete manifest branch, allowing a new
2149 # branch to take over
2150 syncbuf = SyncBuffer(self.config, detach_head = True)
2151 self.Sync_LocalHalf(syncbuf)
2152 syncbuf.Finish()
2153
2154 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002155 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002156 capture_stdout = True,
2157 capture_stderr = True).Wait() == 0
2158
2159
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002160 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002161 def LastFetch(self):
2162 try:
2163 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2164 return os.path.getmtime(fh)
2165 except OSError:
2166 return 0
2167
2168 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002169 def HasChanges(self):
2170 """Has the remote received new commits not yet checked out?
2171 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002172 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002173 return False
2174
David Pursehouse8a68ff92012-09-24 12:15:13 +09002175 all_refs = self.bare_ref.all
2176 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002177 head = self.work_git.GetHead()
2178 if head.startswith(R_HEADS):
2179 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002180 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002181 except KeyError:
2182 head = None
2183
2184 if revid == head:
2185 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002186 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002187 return True
2188 return False