blob: 96ab907b362559d71aea1fe05c96cb1384ca4059 [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:
727 try: i = di[p]
728 except KeyError: i = None
729
730 try: f = df[p]
731 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200732
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733 if i: i_status = i.status.upper()
734 else: i_status = '-'
735
736 if f: f_status = f.status.lower()
737 else: f_status = '-'
738
739 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800740 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741 i.src_path, p, i.level)
742 else:
743 line = ' %s%s\t%s' % (i_status, f_status, p)
744
745 if i and not f:
746 out.added('%s', line)
747 elif (i and f) or (not i and f):
748 out.changed('%s', line)
749 elif not i and not f:
750 out.untracked('%s', line)
751 else:
752 out.write('%s', line)
753 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200754
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700755 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700756
pelyad67872d2012-03-28 14:49:58 +0300757 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700758 """Prints the status of the repository to stdout.
759 """
760 out = DiffColoring(self.config)
761 cmd = ['diff']
762 if out.is_on:
763 cmd.append('--color')
764 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300765 if absolute_paths:
766 cmd.append('--src-prefix=a/%s/' % self.relpath)
767 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700768 cmd.append('--')
769 p = GitCommand(self,
770 cmd,
771 capture_stdout = True,
772 capture_stderr = True)
773 has_diff = False
774 for line in p.process.stdout:
775 if not has_diff:
776 out.nl()
777 out.project('project %s/' % self.relpath)
778 out.nl()
779 has_diff = True
780 print line[:-1]
781 p.Wait()
782
783
784## Publish / Upload ##
785
David Pursehouse8a68ff92012-09-24 12:15:13 +0900786 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700787 """Was the branch published (uploaded) for code review?
788 If so, returns the SHA-1 hash of the last published
789 state for the branch.
790 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700791 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900792 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700793 try:
794 return self.bare_git.rev_parse(key)
795 except GitError:
796 return None
797 else:
798 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900799 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700800 except KeyError:
801 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700802
David Pursehouse8a68ff92012-09-24 12:15:13 +0900803 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700804 """Prunes any stale published refs.
805 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900806 if all_refs is None:
807 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700808 heads = set()
809 canrm = {}
David Pursehouse8a68ff92012-09-24 12:15:13 +0900810 for name, ref_id in all_refs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811 if name.startswith(R_HEADS):
812 heads.add(name)
813 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900814 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700815
David Pursehouse8a68ff92012-09-24 12:15:13 +0900816 for name, ref_id in canrm.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700817 n = name[len(R_PUB):]
818 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900819 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700820
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700821 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700822 """List any branches which can be uploaded for review.
823 """
824 heads = {}
825 pubed = {}
826
David Pursehouse8a68ff92012-09-24 12:15:13 +0900827 for name, ref_id in self._allrefs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900829 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900831 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832
833 ready = []
David Pursehouse8a68ff92012-09-24 12:15:13 +0900834 for branch, ref_id in heads.iteritems():
835 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700837 if selected_branch and branch != selected_branch:
838 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800840 rb = self.GetUploadableBranch(branch)
841 if rb:
842 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843 return ready
844
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800845 def GetUploadableBranch(self, branch_name):
846 """Get a single uploadable branch, or None.
847 """
848 branch = self.GetBranch(branch_name)
849 base = branch.LocalMerge
850 if branch.LocalMerge:
851 rb = ReviewableBranch(self, branch, base)
852 if rb.commits:
853 return rb
854 return None
855
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700856 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700857 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700858 auto_topic=False,
859 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700860 """Uploads the named branch for code review.
861 """
862 if branch is None:
863 branch = self.CurrentBranch
864 if branch is None:
865 raise GitError('not currently on a branch')
866
867 branch = self.GetBranch(branch)
868 if not branch.LocalMerge:
869 raise GitError('branch %s does not track a remote' % branch.name)
870 if not branch.remote.review:
871 raise GitError('remote %s has no review url' % branch.remote.name)
872
873 dest_branch = branch.merge
874 if not dest_branch.startswith(R_HEADS):
875 dest_branch = R_HEADS + dest_branch
876
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800877 if not branch.remote.projectname:
878 branch.remote.projectname = self.name
879 branch.remote.Save()
880
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800881 url = branch.remote.ReviewUrl(self.UserEmail)
882 if url is None:
883 raise UploadError('review not configured')
884 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800885
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800886 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800887 rp = ['gerrit receive-pack']
888 for e in people[0]:
889 rp.append('--reviewer=%s' % sq(e))
890 for e in people[1]:
891 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800892 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700893
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800894 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800895
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800896 if dest_branch.startswith(R_HEADS):
897 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700898
899 upload_type = 'for'
900 if draft:
901 upload_type = 'drafts'
902
903 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
904 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800905 if auto_topic:
906 ref_spec = ref_spec + '/' + branch.name
907 cmd.append(ref_spec)
908
909 if GitCommand(self, cmd, bare = True).Wait() != 0:
910 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911
912 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
913 self.bare_git.UpdateRef(R_PUB + branch.name,
914 R_HEADS + branch.name,
915 message = msg)
916
917
918## Sync ##
919
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700920 def Sync_NetworkHalf(self,
921 quiet=False,
922 is_new=None,
923 current_branch_only=False,
924 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925 """Perform only the network IO portion of the sync process.
926 Local working directory/branch state is not affected.
927 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700928 if is_new is None:
929 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200930 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931 self._InitGitDir()
932 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700933
934 if is_new:
935 alt = os.path.join(self.gitdir, 'objects/info/alternates')
936 try:
937 fd = open(alt, 'rb')
938 try:
939 alt_dir = fd.readline().rstrip()
940 finally:
941 fd.close()
942 except IOError:
943 alt_dir = None
944 else:
945 alt_dir = None
946
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700947 if clone_bundle \
948 and alt_dir is None \
949 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700950 is_new = False
951
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700952 if not current_branch_only:
953 if self.sync_c:
954 current_branch_only = True
955 elif not self.manifest._loaded:
956 # Manifest cannot check defaults until it syncs.
957 current_branch_only = False
958 elif self.manifest.default.sync_c:
959 current_branch_only = True
960
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700961 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
962 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800964
965 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800966 self._InitMRef()
967 else:
968 self._InitMirrorHead()
969 try:
970 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
971 except OSError:
972 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800974
975 def PostRepoUpgrade(self):
976 self._InitHooks()
977
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900979 for copyfile in self.copyfiles:
980 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981
David Pursehouse8a68ff92012-09-24 12:15:13 +0900982 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700983 if self.revisionId:
984 return self.revisionId
985
986 rem = self.GetRemote(self.remote.name)
987 rev = rem.ToLocal(self.revisionExpr)
988
David Pursehouse8a68ff92012-09-24 12:15:13 +0900989 if all_refs is not None and rev in all_refs:
990 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700991
992 try:
993 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
994 except GitError:
995 raise ManifestInvalidRevisionError(
996 'revision %s in %s not found' % (self.revisionExpr,
997 self.name))
998
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700999 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000 """Perform only the local IO portion of the sync process.
1001 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001003 all_refs = self.bare_ref.all
1004 self.CleanPublishedCache(all_refs)
1005 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001006
1007 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001008 head = self.work_git.GetHead()
1009 if head.startswith(R_HEADS):
1010 branch = head[len(R_HEADS):]
1011 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001012 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001013 except KeyError:
1014 head = None
1015 else:
1016 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001018 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001019 # Currently on a detached HEAD. The user is assumed to
1020 # not have any local modifications worth worrying about.
1021 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001022 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001023 syncbuf.fail(self, _PriorSyncFailedError())
1024 return
1025
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001026 if head == revid:
1027 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001028 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001029 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001030 if not syncbuf.detach_head:
1031 return
1032 else:
1033 lost = self._revlist(not_rev(revid), HEAD)
1034 if lost:
1035 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001036
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001038 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001039 except GitError, e:
1040 syncbuf.fail(self, e)
1041 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001043 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001044
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001045 if head == revid:
1046 # No changes; don't do anything further.
1047 #
1048 return
1049
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001050 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001052 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001054 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001056 syncbuf.info(self,
1057 "leaving %s; does not track upstream",
1058 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001060 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001061 except GitError, e:
1062 syncbuf.fail(self, e)
1063 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001065 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001067 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001068 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001070 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071 if not_merged:
1072 if upstream_gain:
1073 # The user has published this branch and some of those
1074 # commits are not yet merged upstream. We do not want
1075 # to rewrite the published commits so we punt.
1076 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001077 syncbuf.fail(self,
1078 "branch %s is published (but not merged) and is now %d commits behind"
1079 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001080 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001081 elif pub == head:
1082 # All published commits are merged, and thus we are a
1083 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001084 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001085 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001086 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001087 self._CopyFiles()
1088 syncbuf.later1(self, _doff)
1089 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001091 # Examine the local commits not in the remote. Find the
1092 # last one attributed to this user, if any.
1093 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001094 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001095 last_mine = None
1096 cnt_mine = 0
1097 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001098 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001099 if committer_email == self.UserEmail:
1100 last_mine = commit_id
1101 cnt_mine += 1
1102
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001103 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001104 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001105
1106 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001107 syncbuf.fail(self, _DirtyError())
1108 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001109
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001110 # If the upstream switched on us, warn the user.
1111 #
1112 if branch.merge != self.revisionExpr:
1113 if branch.merge and self.revisionExpr:
1114 syncbuf.info(self,
1115 'manifest switched %s...%s',
1116 branch.merge,
1117 self.revisionExpr)
1118 elif branch.merge:
1119 syncbuf.info(self,
1120 'manifest no longer tracks %s',
1121 branch.merge)
1122
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001123 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001124 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001125 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001127 syncbuf.info(self,
1128 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001129 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001130
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001131 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001132 if not ID_RE.match(self.revisionExpr):
1133 # in case of manifest sync the revisionExpr might be a SHA1
1134 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135 branch.Save()
1136
Mike Pontillod3153822012-02-28 11:53:24 -08001137 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001138 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001139 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001140 self._CopyFiles()
1141 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001142 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001143 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001144 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001145 self._CopyFiles()
1146 except GitError, e:
1147 syncbuf.fail(self, e)
1148 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001150 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001151 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001152 self._CopyFiles()
1153 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001154
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001155 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001156 # dest should already be an absolute path, but src is project relative
1157 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001158 abssrc = os.path.join(self.worktree, src)
1159 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160
James W. Mills24c13082012-04-12 15:04:13 -05001161 def AddAnnotation(self, name, value, keep):
1162 self.annotations.append(_Annotation(name, value, keep))
1163
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001164 def DownloadPatchSet(self, change_id, patch_id):
1165 """Download a single patch set of a single change to FETCH_HEAD.
1166 """
1167 remote = self.GetRemote(self.remote.name)
1168
1169 cmd = ['fetch', remote.name]
1170 cmd.append('refs/changes/%2.2d/%d/%d' \
1171 % (change_id % 100, change_id, patch_id))
1172 cmd.extend(map(lambda x: str(x), remote.fetch))
1173 if GitCommand(self, cmd, bare=True).Wait() != 0:
1174 return None
1175 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001176 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001177 change_id,
1178 patch_id,
1179 self.bare_git.rev_parse('FETCH_HEAD'))
1180
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001181
1182## Branch Management ##
1183
1184 def StartBranch(self, name):
1185 """Create a new branch off the manifest's revision.
1186 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001187 head = self.work_git.GetHead()
1188 if head == (R_HEADS + name):
1189 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190
David Pursehouse8a68ff92012-09-24 12:15:13 +09001191 all_refs = self.bare_ref.all
1192 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001193 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001194 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001195 capture_stdout = True,
1196 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001197
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001198 branch = self.GetBranch(name)
1199 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001200 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001201 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001202
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001203 if head.startswith(R_HEADS):
1204 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001205 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001206 except KeyError:
1207 head = None
1208
1209 if revid and head and revid == head:
1210 ref = os.path.join(self.gitdir, R_HEADS + name)
1211 try:
1212 os.makedirs(os.path.dirname(ref))
1213 except OSError:
1214 pass
1215 _lwrite(ref, '%s\n' % revid)
1216 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1217 'ref: %s%s\n' % (R_HEADS, name))
1218 branch.Save()
1219 return True
1220
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001221 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001222 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001223 capture_stdout = True,
1224 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001225 branch.Save()
1226 return True
1227 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001228
Wink Saville02d79452009-04-10 13:01:24 -07001229 def CheckoutBranch(self, name):
1230 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001231
1232 Args:
1233 name: The name of the branch to checkout.
1234
1235 Returns:
1236 True if the checkout succeeded; False if it didn't; None if the branch
1237 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001238 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001239 rev = R_HEADS + name
1240 head = self.work_git.GetHead()
1241 if head == rev:
1242 # Already on the branch
1243 #
1244 return True
Wink Saville02d79452009-04-10 13:01:24 -07001245
David Pursehouse8a68ff92012-09-24 12:15:13 +09001246 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001247 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001248 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001249 except KeyError:
1250 # Branch does not exist in this project
1251 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001252 return None
Wink Saville02d79452009-04-10 13:01:24 -07001253
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001254 if head.startswith(R_HEADS):
1255 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001256 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001257 except KeyError:
1258 head = None
1259
1260 if head == revid:
1261 # Same revision; just update HEAD to point to the new
1262 # target branch, but otherwise take no other action.
1263 #
1264 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1265 'ref: %s%s\n' % (R_HEADS, name))
1266 return True
1267
1268 return GitCommand(self,
1269 ['checkout', name, '--'],
1270 capture_stdout = True,
1271 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001272
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001273 def AbandonBranch(self, name):
1274 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001275
1276 Args:
1277 name: The name of the branch to abandon.
1278
1279 Returns:
1280 True if the abandon succeeded; False if it didn't; None if the branch
1281 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001282 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001283 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001284 all_refs = self.bare_ref.all
1285 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001286 # Doesn't exist
1287 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001288
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001289 head = self.work_git.GetHead()
1290 if head == rev:
1291 # We can't destroy the branch while we are sitting
1292 # on it. Switch to a detached HEAD.
1293 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001294 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001295
David Pursehouse8a68ff92012-09-24 12:15:13 +09001296 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001297 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001298 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1299 '%s\n' % revid)
1300 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001301 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001302
1303 return GitCommand(self,
1304 ['branch', '-D', name],
1305 capture_stdout = True,
1306 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001307
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308 def PruneHeads(self):
1309 """Prune any topic branches already merged into upstream.
1310 """
1311 cb = self.CurrentBranch
1312 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001313 left = self._allrefs
1314 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315 if name.startswith(R_HEADS):
1316 name = name[len(R_HEADS):]
1317 if cb is None or name != cb:
1318 kill.append(name)
1319
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001320 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 if cb is not None \
1322 and not self._revlist(HEAD + '...' + rev) \
1323 and not self.IsDirty(consider_untracked = False):
1324 self.work_git.DetachHead(HEAD)
1325 kill.append(cb)
1326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001328 old = self.bare_git.GetHead()
1329 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1331
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001332 try:
1333 self.bare_git.DetachHead(rev)
1334
1335 b = ['branch', '-d']
1336 b.extend(kill)
1337 b = GitCommand(self, b, bare=True,
1338 capture_stdout=True,
1339 capture_stderr=True)
1340 b.Wait()
1341 finally:
1342 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001343 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001345 for branch in kill:
1346 if (R_HEADS + branch) not in left:
1347 self.CleanPublishedCache()
1348 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001349
1350 if cb and cb not in kill:
1351 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001352 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353
1354 kept = []
1355 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001356 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357 branch = self.GetBranch(branch)
1358 base = branch.LocalMerge
1359 if not base:
1360 base = rev
1361 kept.append(ReviewableBranch(self, branch, base))
1362 return kept
1363
1364
1365## Direct Git Commands ##
1366
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001367 def _RemoteFetch(self, name=None,
1368 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001369 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001370 quiet=False,
1371 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001372
1373 is_sha1 = False
1374 tag_name = None
1375
Brian Harring14a66742012-09-28 20:21:57 -07001376 def CheckForSha1():
1377 try:
1378 # if revision (sha or tag) is not present then following function
1379 # throws an error.
1380 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1381 return True
1382 except GitError:
1383 # There is no such persistent revision. We have to fetch it.
1384 return False
1385
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001386 if current_branch_only:
1387 if ID_RE.match(self.revisionExpr) is not None:
1388 is_sha1 = True
1389 elif self.revisionExpr.startswith(R_TAGS):
1390 # this is a tag and its sha1 value should never change
1391 tag_name = self.revisionExpr[len(R_TAGS):]
1392
1393 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001394 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001395 return True
Brian Harring14a66742012-09-28 20:21:57 -07001396 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1397 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001398
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399 if not name:
1400 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001401
1402 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001403 remote = self.GetRemote(name)
1404 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001405 ssh_proxy = True
1406
Shawn O. Pearce88443382010-10-08 10:02:09 +02001407 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001408 if alt_dir and 'objects' == os.path.basename(alt_dir):
1409 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001410 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1411 remote = self.GetRemote(name)
1412
David Pursehouse8a68ff92012-09-24 12:15:13 +09001413 all_refs = self.bare_ref.all
1414 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001415 tmp = set()
1416
David Pursehouse8a68ff92012-09-24 12:15:13 +09001417 for r, ref_id in GitRefs(ref_dir).all.iteritems():
1418 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001419 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001420 all_refs[r] = ref_id
1421 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001422 continue
1423
David Pursehouse8a68ff92012-09-24 12:15:13 +09001424 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001425 continue
1426
David Pursehouse8a68ff92012-09-24 12:15:13 +09001427 r = 'refs/_alt/%s' % ref_id
1428 all_refs[r] = ref_id
1429 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001430 tmp.add(r)
1431
David Pursehouse8a68ff92012-09-24 12:15:13 +09001432 ref_names = list(all_refs.keys())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001433 ref_names.sort()
1434
1435 tmp_packed = ''
1436 old_packed = ''
1437
1438 for r in ref_names:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001439 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001440 tmp_packed += line
1441 if r not in tmp:
1442 old_packed += line
1443
1444 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001445 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001446 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001447
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001448 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001449
1450 # The --depth option only affects the initial fetch; after that we'll do
1451 # full fetches of changes.
1452 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1453 if depth and initial:
1454 cmd.append('--depth=%s' % depth)
1455
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001456 if quiet:
1457 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001458 if not self.worktree:
1459 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001460 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001461
Brian Harring14a66742012-09-28 20:21:57 -07001462 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001463 # Fetch whole repo
1464 cmd.append('--tags')
1465 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1466 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001467 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001468 cmd.append(tag_name)
1469 else:
1470 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001471 if is_sha1:
1472 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001473 if branch.startswith(R_HEADS):
1474 branch = branch[len(R_HEADS):]
1475 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001476
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001477 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001478 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001479 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1480 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001481 ok = True
1482 break
Brian Harring14a66742012-09-28 20:21:57 -07001483 elif current_branch_only and is_sha1 and ret == 128:
1484 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1485 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1486 # abort the optimization attempt and do a full sync.
1487 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001488 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001489
1490 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001491 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001492 if old_packed != '':
1493 _lwrite(packed_refs, old_packed)
1494 else:
1495 os.remove(packed_refs)
1496 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001497
1498 if is_sha1 and current_branch_only and self.upstream:
1499 # We just synced the upstream given branch; verify we
1500 # got what we wanted, else trigger a second run of all
1501 # refs.
1502 if not CheckForSha1():
1503 return self._RemoteFetch(name=name, current_branch_only=False,
1504 initial=False, quiet=quiet, alt_dir=alt_dir)
1505
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001506 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001507
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001508 def _ApplyCloneBundle(self, initial=False, quiet=False):
1509 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1510 return False
1511
1512 remote = self.GetRemote(self.remote.name)
1513 bundle_url = remote.url + '/clone.bundle'
1514 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001515 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1516 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001517 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1518 return False
1519
1520 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1521 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1522
1523 exist_dst = os.path.exists(bundle_dst)
1524 exist_tmp = os.path.exists(bundle_tmp)
1525
1526 if not initial and not exist_dst and not exist_tmp:
1527 return False
1528
1529 if not exist_dst:
1530 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1531 if not exist_dst:
1532 return False
1533
1534 cmd = ['fetch']
1535 if quiet:
1536 cmd.append('--quiet')
1537 if not self.worktree:
1538 cmd.append('--update-head-ok')
1539 cmd.append(bundle_dst)
1540 for f in remote.fetch:
1541 cmd.append(str(f))
1542 cmd.append('refs/tags/*:refs/tags/*')
1543
1544 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001545 if os.path.exists(bundle_dst):
1546 os.remove(bundle_dst)
1547 if os.path.exists(bundle_tmp):
1548 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001549 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001550
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001551 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001552 if os.path.exists(dstPath):
1553 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001554
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001555 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001556 if quiet:
1557 cmd += ['--silent']
1558 if os.path.exists(tmpPath):
1559 size = os.stat(tmpPath).st_size
1560 if size >= 1024:
1561 cmd += ['--continue-at', '%d' % (size,)]
1562 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001563 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001564 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1565 cmd += ['--proxy', os.environ['http_proxy']]
1566 cmd += [srcUrl]
1567
1568 if IsTrace():
1569 Trace('%s', ' '.join(cmd))
1570 try:
1571 proc = subprocess.Popen(cmd)
1572 except OSError:
1573 return False
1574
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001575 curlret = proc.wait()
1576
1577 if curlret == 22:
1578 # From curl man page:
1579 # 22: HTTP page not retrieved. The requested url was not found or
1580 # returned another error with the HTTP error code being 400 or above.
1581 # This return code only appears if -f, --fail is used.
1582 if not quiet:
1583 print >> sys.stderr, "Server does not provide clone.bundle; ignoring."
1584 return False
1585
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001586 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001587 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001588 os.rename(tmpPath, dstPath)
1589 return True
1590 else:
1591 os.remove(tmpPath)
1592 return False
1593 else:
1594 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001595
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001596 def _Checkout(self, rev, quiet=False):
1597 cmd = ['checkout']
1598 if quiet:
1599 cmd.append('-q')
1600 cmd.append(rev)
1601 cmd.append('--')
1602 if GitCommand(self, cmd).Wait() != 0:
1603 if self._allrefs:
1604 raise GitError('%s checkout %s ' % (self.name, rev))
1605
Pierre Tardye5a21222011-03-24 16:28:18 +01001606 def _CherryPick(self, rev, quiet=False):
1607 cmd = ['cherry-pick']
1608 cmd.append(rev)
1609 cmd.append('--')
1610 if GitCommand(self, cmd).Wait() != 0:
1611 if self._allrefs:
1612 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1613
Erwan Mahea94f1622011-08-19 13:56:09 +02001614 def _Revert(self, rev, quiet=False):
1615 cmd = ['revert']
1616 cmd.append('--no-edit')
1617 cmd.append(rev)
1618 cmd.append('--')
1619 if GitCommand(self, cmd).Wait() != 0:
1620 if self._allrefs:
1621 raise GitError('%s revert %s ' % (self.name, rev))
1622
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001623 def _ResetHard(self, rev, quiet=True):
1624 cmd = ['reset', '--hard']
1625 if quiet:
1626 cmd.append('-q')
1627 cmd.append(rev)
1628 if GitCommand(self, cmd).Wait() != 0:
1629 raise GitError('%s reset --hard %s ' % (self.name, rev))
1630
1631 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001632 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001633 if onto is not None:
1634 cmd.extend(['--onto', onto])
1635 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001636 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001637 raise GitError('%s rebase %s ' % (self.name, upstream))
1638
Pierre Tardy3d125942012-05-04 12:18:12 +02001639 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001640 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001641 if ffonly:
1642 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001643 if GitCommand(self, cmd).Wait() != 0:
1644 raise GitError('%s merge %s ' % (self.name, head))
1645
1646 def _InitGitDir(self):
1647 if not os.path.exists(self.gitdir):
1648 os.makedirs(self.gitdir)
1649 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001650
Shawn O. Pearce88443382010-10-08 10:02:09 +02001651 mp = self.manifest.manifestProject
1652 ref_dir = mp.config.GetString('repo.reference')
1653
1654 if ref_dir:
1655 mirror_git = os.path.join(ref_dir, self.name + '.git')
1656 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1657 self.relpath + '.git')
1658
1659 if os.path.exists(mirror_git):
1660 ref_dir = mirror_git
1661
1662 elif os.path.exists(repo_git):
1663 ref_dir = repo_git
1664
1665 else:
1666 ref_dir = None
1667
1668 if ref_dir:
1669 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1670 os.path.join(ref_dir, 'objects') + '\n')
1671
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001672 if self.manifest.IsMirror:
1673 self.config.SetString('core.bare', 'true')
1674 else:
1675 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001676
1677 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001678 try:
1679 to_rm = os.listdir(hooks)
1680 except OSError:
1681 to_rm = []
1682 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001683 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001684 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685
1686 m = self.manifest.manifestProject.config
1687 for key in ['user.name', 'user.email']:
1688 if m.Has(key, include_defaults = False):
1689 self.config.SetString(key, m.GetString(key))
1690
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001691 def _InitHooks(self):
1692 hooks = self._gitdir_path('hooks')
1693 if not os.path.exists(hooks):
1694 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001695 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001696 name = os.path.basename(stock_hook)
1697
Victor Boivie65e0f352011-04-18 11:23:29 +02001698 if name in ('commit-msg',) and not self.remote.review \
1699 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001700 # Don't install a Gerrit Code Review hook if this
1701 # project does not appear to use it for reviews.
1702 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001703 # Since the manifest project is one of those, but also
1704 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001705 continue
1706
1707 dst = os.path.join(hooks, name)
1708 if os.path.islink(dst):
1709 continue
1710 if os.path.exists(dst):
1711 if filecmp.cmp(stock_hook, dst, shallow=False):
1712 os.remove(dst)
1713 else:
1714 _error("%s: Not replacing %s hook", self.relpath, name)
1715 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001716 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001717 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001718 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001719 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001720 raise GitError('filesystem must support symlinks')
1721 else:
1722 raise
1723
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001724 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001725 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001726 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001727 remote.url = self.remote.url
1728 remote.review = self.remote.review
1729 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001730
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001731 if self.worktree:
1732 remote.ResetFetch(mirror=False)
1733 else:
1734 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001735 remote.Save()
1736
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001737 def _InitMRef(self):
1738 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001739 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001740
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001741 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001742 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001743
1744 def _InitAnyMRef(self, ref):
1745 cur = self.bare_ref.symref(ref)
1746
1747 if self.revisionId:
1748 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1749 msg = 'manifest set to %s' % self.revisionId
1750 dst = self.revisionId + '^0'
1751 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1752 else:
1753 remote = self.GetRemote(self.remote.name)
1754 dst = remote.ToLocal(self.revisionExpr)
1755 if cur != dst:
1756 msg = 'manifest set to %s' % self.revisionExpr
1757 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001758
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001759 def _InitWorkTree(self):
1760 dotgit = os.path.join(self.worktree, '.git')
1761 if not os.path.exists(dotgit):
1762 os.makedirs(dotgit)
1763
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001764 for name in ['config',
1765 'description',
1766 'hooks',
1767 'info',
1768 'logs',
1769 'objects',
1770 'packed-refs',
1771 'refs',
1772 'rr-cache',
1773 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001774 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001775 src = os.path.join(self.gitdir, name)
1776 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001777 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001778 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001779 else:
1780 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001781 except OSError, e:
1782 if e.errno == errno.EPERM:
1783 raise GitError('filesystem must support symlinks')
1784 else:
1785 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001786
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001787 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001788
1789 cmd = ['read-tree', '--reset', '-u']
1790 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001791 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001792 if GitCommand(self, cmd).Wait() != 0:
1793 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001794
1795 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1796 if not os.path.exists(rr_cache):
1797 os.makedirs(rr_cache)
1798
Shawn O. Pearce93609662009-04-21 10:50:33 -07001799 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001800
1801 def _gitdir_path(self, path):
1802 return os.path.join(self.gitdir, path)
1803
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001804 def _revlist(self, *args, **kw):
1805 a = []
1806 a.extend(args)
1807 a.append('--')
1808 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001809
1810 @property
1811 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001812 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001813
1814 class _GitGetByExec(object):
1815 def __init__(self, project, bare):
1816 self._project = project
1817 self._bare = bare
1818
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001819 def LsOthers(self):
1820 p = GitCommand(self._project,
1821 ['ls-files',
1822 '-z',
1823 '--others',
1824 '--exclude-standard'],
1825 bare = False,
1826 capture_stdout = True,
1827 capture_stderr = True)
1828 if p.Wait() == 0:
1829 out = p.stdout
1830 if out:
1831 return out[:-1].split("\0")
1832 return []
1833
1834 def DiffZ(self, name, *args):
1835 cmd = [name]
1836 cmd.append('-z')
1837 cmd.extend(args)
1838 p = GitCommand(self._project,
1839 cmd,
1840 bare = False,
1841 capture_stdout = True,
1842 capture_stderr = True)
1843 try:
1844 out = p.process.stdout.read()
1845 r = {}
1846 if out:
1847 out = iter(out[:-1].split('\0'))
1848 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001849 try:
1850 info = out.next()
1851 path = out.next()
1852 except StopIteration:
1853 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001854
1855 class _Info(object):
1856 def __init__(self, path, omode, nmode, oid, nid, state):
1857 self.path = path
1858 self.src_path = None
1859 self.old_mode = omode
1860 self.new_mode = nmode
1861 self.old_id = oid
1862 self.new_id = nid
1863
1864 if len(state) == 1:
1865 self.status = state
1866 self.level = None
1867 else:
1868 self.status = state[:1]
1869 self.level = state[1:]
1870 while self.level.startswith('0'):
1871 self.level = self.level[1:]
1872
1873 info = info[1:].split(' ')
1874 info =_Info(path, *info)
1875 if info.status in ('R', 'C'):
1876 info.src_path = info.path
1877 info.path = out.next()
1878 r[info.path] = info
1879 return r
1880 finally:
1881 p.Wait()
1882
1883 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001884 if self._bare:
1885 path = os.path.join(self._project.gitdir, HEAD)
1886 else:
1887 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001888 fd = open(path, 'rb')
1889 try:
1890 line = fd.read()
1891 finally:
1892 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001893 if line.startswith('ref: '):
1894 return line[5:-1]
1895 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001896
1897 def SetHead(self, ref, message=None):
1898 cmdv = []
1899 if message is not None:
1900 cmdv.extend(['-m', message])
1901 cmdv.append(HEAD)
1902 cmdv.append(ref)
1903 self.symbolic_ref(*cmdv)
1904
1905 def DetachHead(self, new, message=None):
1906 cmdv = ['--no-deref']
1907 if message is not None:
1908 cmdv.extend(['-m', message])
1909 cmdv.append(HEAD)
1910 cmdv.append(new)
1911 self.update_ref(*cmdv)
1912
1913 def UpdateRef(self, name, new, old=None,
1914 message=None,
1915 detach=False):
1916 cmdv = []
1917 if message is not None:
1918 cmdv.extend(['-m', message])
1919 if detach:
1920 cmdv.append('--no-deref')
1921 cmdv.append(name)
1922 cmdv.append(new)
1923 if old is not None:
1924 cmdv.append(old)
1925 self.update_ref(*cmdv)
1926
1927 def DeleteRef(self, name, old=None):
1928 if not old:
1929 old = self.rev_parse(name)
1930 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001931 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001932
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001933 def rev_list(self, *args, **kw):
1934 if 'format' in kw:
1935 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1936 else:
1937 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001938 cmdv.extend(args)
1939 p = GitCommand(self._project,
1940 cmdv,
1941 bare = self._bare,
1942 capture_stdout = True,
1943 capture_stderr = True)
1944 r = []
1945 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001946 if line[-1] == '\n':
1947 line = line[:-1]
1948 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001949 if p.Wait() != 0:
1950 raise GitError('%s rev-list %s: %s' % (
1951 self._project.name,
1952 str(args),
1953 p.stderr))
1954 return r
1955
1956 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001957 """Allow arbitrary git commands using pythonic syntax.
1958
1959 This allows you to do things like:
1960 git_obj.rev_parse('HEAD')
1961
1962 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1963 run. We'll replace the '_' with a '-' and try to run a git command.
1964 Any other arguments will be passed to the git command.
1965
1966 Args:
1967 name: The name of the git command to call. Any '_' characters will
1968 be replaced with '-'.
1969
1970 Returns:
1971 A callable object that will try to call git with the named command.
1972 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001973 name = name.replace('_', '-')
1974 def runner(*args):
1975 cmdv = [name]
1976 cmdv.extend(args)
1977 p = GitCommand(self._project,
1978 cmdv,
1979 bare = self._bare,
1980 capture_stdout = True,
1981 capture_stderr = True)
1982 if p.Wait() != 0:
1983 raise GitError('%s %s: %s' % (
1984 self._project.name,
1985 name,
1986 p.stderr))
1987 r = p.stdout
1988 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1989 return r[:-1]
1990 return r
1991 return runner
1992
1993
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001994class _PriorSyncFailedError(Exception):
1995 def __str__(self):
1996 return 'prior sync failed; rebase still in progress'
1997
1998class _DirtyError(Exception):
1999 def __str__(self):
2000 return 'contains uncommitted changes'
2001
2002class _InfoMessage(object):
2003 def __init__(self, project, text):
2004 self.project = project
2005 self.text = text
2006
2007 def Print(self, syncbuf):
2008 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2009 syncbuf.out.nl()
2010
2011class _Failure(object):
2012 def __init__(self, project, why):
2013 self.project = project
2014 self.why = why
2015
2016 def Print(self, syncbuf):
2017 syncbuf.out.fail('error: %s/: %s',
2018 self.project.relpath,
2019 str(self.why))
2020 syncbuf.out.nl()
2021
2022class _Later(object):
2023 def __init__(self, project, action):
2024 self.project = project
2025 self.action = action
2026
2027 def Run(self, syncbuf):
2028 out = syncbuf.out
2029 out.project('project %s/', self.project.relpath)
2030 out.nl()
2031 try:
2032 self.action()
2033 out.nl()
2034 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002035 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002036 out.nl()
2037 return False
2038
2039class _SyncColoring(Coloring):
2040 def __init__(self, config):
2041 Coloring.__init__(self, config, 'reposync')
2042 self.project = self.printer('header', attr = 'bold')
2043 self.info = self.printer('info')
2044 self.fail = self.printer('fail', fg='red')
2045
2046class SyncBuffer(object):
2047 def __init__(self, config, detach_head=False):
2048 self._messages = []
2049 self._failures = []
2050 self._later_queue1 = []
2051 self._later_queue2 = []
2052
2053 self.out = _SyncColoring(config)
2054 self.out.redirect(sys.stderr)
2055
2056 self.detach_head = detach_head
2057 self.clean = True
2058
2059 def info(self, project, fmt, *args):
2060 self._messages.append(_InfoMessage(project, fmt % args))
2061
2062 def fail(self, project, err=None):
2063 self._failures.append(_Failure(project, err))
2064 self.clean = False
2065
2066 def later1(self, project, what):
2067 self._later_queue1.append(_Later(project, what))
2068
2069 def later2(self, project, what):
2070 self._later_queue2.append(_Later(project, what))
2071
2072 def Finish(self):
2073 self._PrintMessages()
2074 self._RunLater()
2075 self._PrintMessages()
2076 return self.clean
2077
2078 def _RunLater(self):
2079 for q in ['_later_queue1', '_later_queue2']:
2080 if not self._RunQueue(q):
2081 return
2082
2083 def _RunQueue(self, queue):
2084 for m in getattr(self, queue):
2085 if not m.Run(self):
2086 self.clean = False
2087 return False
2088 setattr(self, queue, [])
2089 return True
2090
2091 def _PrintMessages(self):
2092 for m in self._messages:
2093 m.Print(self)
2094 for m in self._failures:
2095 m.Print(self)
2096
2097 self._messages = []
2098 self._failures = []
2099
2100
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002101class MetaProject(Project):
2102 """A special project housed under .repo.
2103 """
2104 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002105 Project.__init__(self,
2106 manifest = manifest,
2107 name = name,
2108 gitdir = gitdir,
2109 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002110 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002111 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002112 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002113 revisionId = None,
2114 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002115
2116 def PreSync(self):
2117 if self.Exists:
2118 cb = self.CurrentBranch
2119 if cb:
2120 base = self.GetBranch(cb).merge
2121 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002122 self.revisionExpr = base
2123 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002124
Florian Vallee5d016502012-06-07 17:19:26 +02002125 def MetaBranchSwitch(self, target):
2126 """ Prepare MetaProject for manifest branch switch
2127 """
2128
2129 # detach and delete manifest branch, allowing a new
2130 # branch to take over
2131 syncbuf = SyncBuffer(self.config, detach_head = True)
2132 self.Sync_LocalHalf(syncbuf)
2133 syncbuf.Finish()
2134
2135 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002136 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002137 capture_stdout = True,
2138 capture_stderr = True).Wait() == 0
2139
2140
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002141 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002142 def LastFetch(self):
2143 try:
2144 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2145 return os.path.getmtime(fh)
2146 except OSError:
2147 return 0
2148
2149 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002150 def HasChanges(self):
2151 """Has the remote received new commits not yet checked out?
2152 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002153 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002154 return False
2155
David Pursehouse8a68ff92012-09-24 12:15:13 +09002156 all_refs = self.bare_ref.all
2157 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002158 head = self.work_git.GetHead()
2159 if head.startswith(R_HEADS):
2160 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002161 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002162 except KeyError:
2163 head = None
2164
2165 if revid == head:
2166 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002167 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002168 return True
2169 return False