blob: 08b27710adae3534b5de5f64ba80ad7eb7be869a [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
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Doug Anderson37282b42011-03-04 11:54:18 -080016import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080017import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import filecmp
19import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070026import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070027
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070029from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070030from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090031from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080032from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080033from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070034from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070035
Shawn O. Pearced237b692009-04-17 18:49:50 -070036from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070038def _lwrite(path, content):
39 lock = '%s.lock' % path
40
41 fd = open(lock, 'wb')
42 try:
43 fd.write(content)
44 finally:
45 fd.close()
46
47 try:
48 os.rename(lock, path)
49 except OSError:
50 os.remove(lock)
51 raise
52
Shawn O. Pearce48244782009-04-16 08:25:57 -070053def _error(fmt, *args):
54 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070055 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070056
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070057def not_rev(r):
58 return '^' + r
59
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080060def sq(r):
61 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080062
Doug Anderson8ced8642011-01-10 14:16:30 -080063_project_hook_list = None
64def _ProjectHooks():
65 """List the hooks present in the 'hooks' directory.
66
67 These hooks are project hooks and are copied to the '.git/hooks' directory
68 of all subprojects.
69
70 This function caches the list of hooks (based on the contents of the
71 'repo/hooks' directory) on the first call.
72
73 Returns:
74 A list of absolute paths to all of the files in the hooks directory.
75 """
76 global _project_hook_list
77 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080078 d = os.path.abspath(os.path.dirname(__file__))
79 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080080 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
81 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080082
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080083
Shawn O. Pearce632768b2008-10-23 11:58:52 -070084class DownloadedChange(object):
85 _commit_cache = None
86
87 def __init__(self, project, base, change_id, ps_id, commit):
88 self.project = project
89 self.base = base
90 self.change_id = change_id
91 self.ps_id = ps_id
92 self.commit = commit
93
94 @property
95 def commits(self):
96 if self._commit_cache is None:
97 self._commit_cache = self.project.bare_git.rev_list(
98 '--abbrev=8',
99 '--abbrev-commit',
100 '--pretty=oneline',
101 '--reverse',
102 '--date-order',
103 not_rev(self.base),
104 self.commit,
105 '--')
106 return self._commit_cache
107
108
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700109class ReviewableBranch(object):
110 _commit_cache = None
111
112 def __init__(self, project, branch, base):
113 self.project = project
114 self.branch = branch
115 self.base = base
116
117 @property
118 def name(self):
119 return self.branch.name
120
121 @property
122 def commits(self):
123 if self._commit_cache is None:
124 self._commit_cache = self.project.bare_git.rev_list(
125 '--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 R_HEADS + self.name,
132 '--')
133 return self._commit_cache
134
135 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800136 def unabbrev_commits(self):
137 r = dict()
138 for commit in self.project.bare_git.rev_list(
139 not_rev(self.base),
140 R_HEADS + self.name,
141 '--'):
142 r[commit[0:8]] = commit
143 return r
144
145 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700146 def date(self):
147 return self.project.bare_git.log(
148 '--pretty=format:%cd',
149 '-n', '1',
150 R_HEADS + self.name,
151 '--')
152
Brian Harring435370c2012-07-28 15:37:04 -0700153 def UploadForReview(self, people, auto_topic=False, draft=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800154 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700155 people,
Brian Harring435370c2012-07-28 15:37:04 -0700156 auto_topic=auto_topic,
157 draft=draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700159 def GetPublishedRefs(self):
160 refs = {}
161 output = self.project.bare_git.ls_remote(
162 self.branch.remote.SshReviewUrl(self.project.UserEmail),
163 'refs/changes/*')
164 for line in output.split('\n'):
165 try:
166 (sha, ref) = line.split()
167 refs[sha] = ref
168 except ValueError:
169 pass
170
171 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172
173class StatusColoring(Coloring):
174 def __init__(self, config):
175 Coloring.__init__(self, config, 'status')
176 self.project = self.printer('header', attr = 'bold')
177 self.branch = self.printer('header', attr = 'bold')
178 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700179 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700180
181 self.added = self.printer('added', fg = 'green')
182 self.changed = self.printer('changed', fg = 'red')
183 self.untracked = self.printer('untracked', fg = 'red')
184
185
186class DiffColoring(Coloring):
187 def __init__(self, config):
188 Coloring.__init__(self, config, 'diff')
189 self.project = self.printer('header', attr = 'bold')
190
James W. Mills24c13082012-04-12 15:04:13 -0500191class _Annotation:
192 def __init__(self, name, value, keep):
193 self.name = name
194 self.value = value
195 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700196
197class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800198 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199 self.src = src
200 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800201 self.abs_src = abssrc
202 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203
204 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800205 src = self.abs_src
206 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207 # copy file if it does not exist or is out of date
208 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
209 try:
210 # remove existing file first, since it might be read-only
211 if os.path.exists(dest):
212 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400213 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200214 dest_dir = os.path.dirname(dest)
215 if not os.path.isdir(dest_dir):
216 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 shutil.copy(src, dest)
218 # make the file read-only
219 mode = os.stat(dest)[stat.ST_MODE]
220 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
221 os.chmod(dest, mode)
222 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700223 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700225class RemoteSpec(object):
226 def __init__(self,
227 name,
228 url = None,
229 review = None):
230 self.name = name
231 self.url = url
232 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233
Doug Anderson37282b42011-03-04 11:54:18 -0800234class RepoHook(object):
235 """A RepoHook contains information about a script to run as a hook.
236
237 Hooks are used to run a python script before running an upload (for instance,
238 to run presubmit checks). Eventually, we may have hooks for other actions.
239
240 This shouldn't be confused with files in the 'repo/hooks' directory. Those
241 files are copied into each '.git/hooks' folder for each project. Repo-level
242 hooks are associated instead with repo actions.
243
244 Hooks are always python. When a hook is run, we will load the hook into the
245 interpreter and execute its main() function.
246 """
247 def __init__(self,
248 hook_type,
249 hooks_project,
250 topdir,
251 abort_if_user_denies=False):
252 """RepoHook constructor.
253
254 Params:
255 hook_type: A string representing the type of hook. This is also used
256 to figure out the name of the file containing the hook. For
257 example: 'pre-upload'.
258 hooks_project: The project containing the repo hooks. If you have a
259 manifest, this is manifest.repo_hooks_project. OK if this is None,
260 which will make the hook a no-op.
261 topdir: Repo's top directory (the one containing the .repo directory).
262 Scripts will run with CWD as this directory. If you have a manifest,
263 this is manifest.topdir
264 abort_if_user_denies: If True, we'll throw a HookError() if the user
265 doesn't allow us to run the hook.
266 """
267 self._hook_type = hook_type
268 self._hooks_project = hooks_project
269 self._topdir = topdir
270 self._abort_if_user_denies = abort_if_user_denies
271
272 # Store the full path to the script for convenience.
273 if self._hooks_project:
274 self._script_fullpath = os.path.join(self._hooks_project.worktree,
275 self._hook_type + '.py')
276 else:
277 self._script_fullpath = None
278
279 def _GetHash(self):
280 """Return a hash of the contents of the hooks directory.
281
282 We'll just use git to do this. This hash has the property that if anything
283 changes in the directory we will return a different has.
284
285 SECURITY CONSIDERATION:
286 This hash only represents the contents of files in the hook directory, not
287 any other files imported or called by hooks. Changes to imported files
288 can change the script behavior without affecting the hash.
289
290 Returns:
291 A string representing the hash. This will always be ASCII so that it can
292 be printed to the user easily.
293 """
294 assert self._hooks_project, "Must have hooks to calculate their hash."
295
296 # We will use the work_git object rather than just calling GetRevisionId().
297 # That gives us a hash of the latest checked in version of the files that
298 # the user will actually be executing. Specifically, GetRevisionId()
299 # doesn't appear to change even if a user checks out a different version
300 # of the hooks repo (via git checkout) nor if a user commits their own revs.
301 #
302 # NOTE: Local (non-committed) changes will not be factored into this hash.
303 # I think this is OK, since we're really only worried about warning the user
304 # about upstream changes.
305 return self._hooks_project.work_git.rev_parse('HEAD')
306
307 def _GetMustVerb(self):
308 """Return 'must' if the hook is required; 'should' if not."""
309 if self._abort_if_user_denies:
310 return 'must'
311 else:
312 return 'should'
313
314 def _CheckForHookApproval(self):
315 """Check to see whether this hook has been approved.
316
317 We'll look at the hash of all of the hooks. If this matches the hash that
318 the user last approved, we're done. If it doesn't, we'll ask the user
319 about approval.
320
321 Note that we ask permission for each individual hook even though we use
322 the hash of all hooks when detecting changes. We'd like the user to be
323 able to approve / deny each hook individually. We only use the hash of all
324 hooks because there is no other easy way to detect changes to local imports.
325
326 Returns:
327 True if this hook is approved to run; False otherwise.
328
329 Raises:
330 HookError: Raised if the user doesn't approve and abort_if_user_denies
331 was passed to the consturctor.
332 """
Doug Anderson37282b42011-03-04 11:54:18 -0800333 hooks_config = self._hooks_project.config
334 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
335
336 # Get the last hash that the user approved for this hook; may be None.
337 old_hash = hooks_config.GetString(git_approval_key)
338
339 # Get the current hash so we can tell if scripts changed since approval.
340 new_hash = self._GetHash()
341
342 if old_hash is not None:
343 # User previously approved hook and asked not to be prompted again.
344 if new_hash == old_hash:
345 # Approval matched. We're done.
346 return True
347 else:
348 # Give the user a reason why we're prompting, since they last told
349 # us to "never ask again".
350 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
351 self._hook_type)
352 else:
353 prompt = ''
354
355 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
356 if sys.stdout.isatty():
357 prompt += ('Repo %s run the script:\n'
358 ' %s\n'
359 '\n'
360 'Do you want to allow this script to run '
361 '(yes/yes-never-ask-again/NO)? ') % (
362 self._GetMustVerb(), self._script_fullpath)
363 response = raw_input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900364 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800365
366 # User is doing a one-time approval.
367 if response in ('y', 'yes'):
368 return True
369 elif response == 'yes-never-ask-again':
370 hooks_config.SetString(git_approval_key, new_hash)
371 return True
372
373 # For anything else, we'll assume no approval.
374 if self._abort_if_user_denies:
375 raise HookError('You must allow the %s hook or use --no-verify.' %
376 self._hook_type)
377
378 return False
379
380 def _ExecuteHook(self, **kwargs):
381 """Actually execute the given hook.
382
383 This will run the hook's 'main' function in our python interpreter.
384
385 Args:
386 kwargs: Keyword arguments to pass to the hook. These are often specific
387 to the hook type. For instance, pre-upload hooks will contain
388 a project_list.
389 """
390 # Keep sys.path and CWD stashed away so that we can always restore them
391 # upon function exit.
392 orig_path = os.getcwd()
393 orig_syspath = sys.path
394
395 try:
396 # Always run hooks with CWD as topdir.
397 os.chdir(self._topdir)
398
399 # Put the hook dir as the first item of sys.path so hooks can do
400 # relative imports. We want to replace the repo dir as [0] so
401 # hooks can't import repo files.
402 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
403
404 # Exec, storing global context in the context dict. We catch exceptions
405 # and convert to a HookError w/ just the failing traceback.
406 context = {}
407 try:
408 execfile(self._script_fullpath, context)
409 except Exception:
410 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
411 traceback.format_exc(), self._hook_type))
412
413 # Running the script should have defined a main() function.
414 if 'main' not in context:
415 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
416
417
418 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
419 # We don't actually want hooks to define their main with this argument--
420 # it's there to remind them that their hook should always take **kwargs.
421 # For instance, a pre-upload hook should be defined like:
422 # def main(project_list, **kwargs):
423 #
424 # This allows us to later expand the API without breaking old hooks.
425 kwargs = kwargs.copy()
426 kwargs['hook_should_take_kwargs'] = True
427
428 # Call the main function in the hook. If the hook should cause the
429 # build to fail, it will raise an Exception. We'll catch that convert
430 # to a HookError w/ just the failing traceback.
431 try:
432 context['main'](**kwargs)
433 except Exception:
434 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
435 'above.' % (
436 traceback.format_exc(), self._hook_type))
437 finally:
438 # Restore sys.path and CWD.
439 sys.path = orig_syspath
440 os.chdir(orig_path)
441
442 def Run(self, user_allows_all_hooks, **kwargs):
443 """Run the hook.
444
445 If the hook doesn't exist (because there is no hooks project or because
446 this particular hook is not enabled), this is a no-op.
447
448 Args:
449 user_allows_all_hooks: If True, we will never prompt about running the
450 hook--we'll just assume it's OK to run it.
451 kwargs: Keyword arguments to pass to the hook. These are often specific
452 to the hook type. For instance, pre-upload hooks will contain
453 a project_list.
454
455 Raises:
456 HookError: If there was a problem finding the hook or the user declined
457 to run a required hook (from _CheckForHookApproval).
458 """
459 # No-op if there is no hooks project or if hook is disabled.
460 if ((not self._hooks_project) or
461 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
462 return
463
464 # Bail with a nice error if we can't find the hook.
465 if not os.path.isfile(self._script_fullpath):
466 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
467
468 # Make sure the user is OK with running the hook.
469 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
470 return
471
472 # Run the hook with the same version of python we're using.
473 self._ExecuteHook(**kwargs)
474
475
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700476class Project(object):
477 def __init__(self,
478 manifest,
479 name,
480 remote,
481 gitdir,
482 worktree,
483 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700484 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800485 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700486 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700487 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700488 sync_c = False,
Shawn O. Pearcecd81dd62012-10-26 12:18:00 -0700489 upstream = None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700490 self.manifest = manifest
491 self.name = name
492 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800493 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800494 if worktree:
495 self.worktree = worktree.replace('\\', '/')
496 else:
497 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700498 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700499 self.revisionExpr = revisionExpr
500
501 if revisionId is None \
502 and revisionExpr \
503 and IsId(revisionExpr):
504 self.revisionId = revisionExpr
505 else:
506 self.revisionId = revisionId
507
Mike Pontillod3153822012-02-28 11:53:24 -0800508 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700509 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700510 self.sync_c = sync_c
Brian Harring14a66742012-09-28 20:21:57 -0700511 self.upstream = upstream
Mike Pontillod3153822012-02-28 11:53:24 -0800512
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700513 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700514 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500515 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700516 self.config = GitConfig.ForRepository(
517 gitdir = self.gitdir,
518 defaults = self.manifest.globalConfig)
519
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800520 if self.worktree:
521 self.work_git = self._GitGetByExec(self, bare=False)
522 else:
523 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700524 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700525 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700526
Doug Anderson37282b42011-03-04 11:54:18 -0800527 # This will be filled in if a project is later identified to be the
528 # project containing repo hooks.
529 self.enabled_repo_hooks = []
530
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700531 @property
532 def Exists(self):
533 return os.path.isdir(self.gitdir)
534
535 @property
536 def CurrentBranch(self):
537 """Obtain the name of the currently checked out branch.
538 The branch name omits the 'refs/heads/' prefix.
539 None is returned if the project is on a detached HEAD.
540 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700541 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700542 if b.startswith(R_HEADS):
543 return b[len(R_HEADS):]
544 return None
545
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700546 def IsRebaseInProgress(self):
547 w = self.worktree
548 g = os.path.join(w, '.git')
549 return os.path.exists(os.path.join(g, 'rebase-apply')) \
550 or os.path.exists(os.path.join(g, 'rebase-merge')) \
551 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200552
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700553 def IsDirty(self, consider_untracked=True):
554 """Is the working directory modified in some way?
555 """
556 self.work_git.update_index('-q',
557 '--unmerged',
558 '--ignore-missing',
559 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900560 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700561 return True
562 if self.work_git.DiffZ('diff-files'):
563 return True
564 if consider_untracked and self.work_git.LsOthers():
565 return True
566 return False
567
568 _userident_name = None
569 _userident_email = None
570
571 @property
572 def UserName(self):
573 """Obtain the user's personal name.
574 """
575 if self._userident_name is None:
576 self._LoadUserIdentity()
577 return self._userident_name
578
579 @property
580 def UserEmail(self):
581 """Obtain the user's email address. This is very likely
582 to be their Gerrit login.
583 """
584 if self._userident_email is None:
585 self._LoadUserIdentity()
586 return self._userident_email
587
588 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900589 u = self.bare_git.var('GIT_COMMITTER_IDENT')
590 m = re.compile("^(.*) <([^>]*)> ").match(u)
591 if m:
592 self._userident_name = m.group(1)
593 self._userident_email = m.group(2)
594 else:
595 self._userident_name = ''
596 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700597
598 def GetRemote(self, name):
599 """Get the configuration for a single remote.
600 """
601 return self.config.GetRemote(name)
602
603 def GetBranch(self, name):
604 """Get the configuration for a single branch.
605 """
606 return self.config.GetBranch(name)
607
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700608 def GetBranches(self):
609 """Get all existing local branches.
610 """
611 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900612 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700613 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700614
David Pursehouse8a68ff92012-09-24 12:15:13 +0900615 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700616 if name.startswith(R_HEADS):
617 name = name[len(R_HEADS):]
618 b = self.GetBranch(name)
619 b.current = name == current
620 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900621 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700622 heads[name] = b
623
David Pursehouse8a68ff92012-09-24 12:15:13 +0900624 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700625 if name.startswith(R_PUB):
626 name = name[len(R_PUB):]
627 b = heads.get(name)
628 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900629 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700630
631 return heads
632
Colin Cross5acde752012-03-28 20:15:45 -0700633 def MatchesGroups(self, manifest_groups):
634 """Returns true if the manifest groups specified at init should cause
635 this project to be synced.
636 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700637 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700638
639 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700640 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700641 manifest_groups: "-group1,group2"
642 the project will be matched.
Colin Cross5acde752012-03-28 20:15:45 -0700643 """
Conley Owensbb1b5f52012-08-13 13:11:18 -0700644 expanded_manifest_groups = manifest_groups or ['all', '-notdefault']
645 expanded_project_groups = ['all'] + (self.groups or [])
646
Conley Owens971de8e2012-04-16 10:36:08 -0700647 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700648 for group in expanded_manifest_groups:
649 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700650 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700651 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700652 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700653
Conley Owens971de8e2012-04-16 10:36:08 -0700654 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700655
656## Status Display ##
657
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500658 def HasChanges(self):
659 """Returns true if there are uncommitted changes.
660 """
661 self.work_git.update_index('-q',
662 '--unmerged',
663 '--ignore-missing',
664 '--refresh')
665 if self.IsRebaseInProgress():
666 return True
667
668 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
669 return True
670
671 if self.work_git.DiffZ('diff-files'):
672 return True
673
674 if self.work_git.LsOthers():
675 return True
676
677 return False
678
Terence Haddock4655e812011-03-31 12:33:34 +0200679 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700680 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200681
682 Args:
683 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700684 """
685 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200686 if output_redir == None:
687 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700688 print(file=output_redir)
689 print('project %s/' % self.relpath, file=output_redir)
690 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700691 return
692
693 self.work_git.update_index('-q',
694 '--unmerged',
695 '--ignore-missing',
696 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700697 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700698 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
699 df = self.work_git.DiffZ('diff-files')
700 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100701 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700702 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700703
704 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200705 if not output_redir == None:
706 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700707 out.project('project %-40s', self.relpath + '/')
708
709 branch = self.CurrentBranch
710 if branch is None:
711 out.nobranch('(*** NO BRANCH ***)')
712 else:
713 out.branch('branch %s', branch)
714 out.nl()
715
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700716 if rb:
717 out.important('prior sync failed; rebase still in progress')
718 out.nl()
719
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720 paths = list()
721 paths.extend(di.keys())
722 paths.extend(df.keys())
723 paths.extend(do)
724
725 paths = list(set(paths))
726 paths.sort()
727
728 for p in paths:
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900729 try:
730 i = di[p]
731 except KeyError:
732 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900734 try:
735 f = df[p]
736 except KeyError:
737 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200738
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900739 if i:
740 i_status = i.status.upper()
741 else:
742 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900744 if f:
745 f_status = f.status.lower()
746 else:
747 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748
749 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800750 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751 i.src_path, p, i.level)
752 else:
753 line = ' %s%s\t%s' % (i_status, f_status, p)
754
755 if i and not f:
756 out.added('%s', line)
757 elif (i and f) or (not i and f):
758 out.changed('%s', line)
759 elif not i and not f:
760 out.untracked('%s', line)
761 else:
762 out.write('%s', line)
763 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200764
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700765 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700766
pelyad67872d2012-03-28 14:49:58 +0300767 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700768 """Prints the status of the repository to stdout.
769 """
770 out = DiffColoring(self.config)
771 cmd = ['diff']
772 if out.is_on:
773 cmd.append('--color')
774 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300775 if absolute_paths:
776 cmd.append('--src-prefix=a/%s/' % self.relpath)
777 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700778 cmd.append('--')
779 p = GitCommand(self,
780 cmd,
781 capture_stdout = True,
782 capture_stderr = True)
783 has_diff = False
784 for line in p.process.stdout:
785 if not has_diff:
786 out.nl()
787 out.project('project %s/' % self.relpath)
788 out.nl()
789 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700790 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791 p.Wait()
792
793
794## Publish / Upload ##
795
David Pursehouse8a68ff92012-09-24 12:15:13 +0900796 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700797 """Was the branch published (uploaded) for code review?
798 If so, returns the SHA-1 hash of the last published
799 state for the branch.
800 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700801 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900802 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700803 try:
804 return self.bare_git.rev_parse(key)
805 except GitError:
806 return None
807 else:
808 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900809 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700810 except KeyError:
811 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812
David Pursehouse8a68ff92012-09-24 12:15:13 +0900813 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814 """Prunes any stale published refs.
815 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900816 if all_refs is None:
817 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700818 heads = set()
819 canrm = {}
David Pursehouse8a68ff92012-09-24 12:15:13 +0900820 for name, ref_id in all_refs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700821 if name.startswith(R_HEADS):
822 heads.add(name)
823 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900824 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700825
David Pursehouse8a68ff92012-09-24 12:15:13 +0900826 for name, ref_id in canrm.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700827 n = name[len(R_PUB):]
828 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900829 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700831 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 """List any branches which can be uploaded for review.
833 """
834 heads = {}
835 pubed = {}
836
David Pursehouse8a68ff92012-09-24 12:15:13 +0900837 for name, ref_id in self._allrefs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900839 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700840 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900841 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700842
843 ready = []
David Pursehouse8a68ff92012-09-24 12:15:13 +0900844 for branch, ref_id in heads.iteritems():
845 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700847 if selected_branch and branch != selected_branch:
848 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800850 rb = self.GetUploadableBranch(branch)
851 if rb:
852 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853 return ready
854
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800855 def GetUploadableBranch(self, branch_name):
856 """Get a single uploadable branch, or None.
857 """
858 branch = self.GetBranch(branch_name)
859 base = branch.LocalMerge
860 if branch.LocalMerge:
861 rb = ReviewableBranch(self, branch, base)
862 if rb.commits:
863 return rb
864 return None
865
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700866 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700867 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700868 auto_topic=False,
869 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700870 """Uploads the named branch for code review.
871 """
872 if branch is None:
873 branch = self.CurrentBranch
874 if branch is None:
875 raise GitError('not currently on a branch')
876
877 branch = self.GetBranch(branch)
878 if not branch.LocalMerge:
879 raise GitError('branch %s does not track a remote' % branch.name)
880 if not branch.remote.review:
881 raise GitError('remote %s has no review url' % branch.remote.name)
882
883 dest_branch = branch.merge
884 if not dest_branch.startswith(R_HEADS):
885 dest_branch = R_HEADS + dest_branch
886
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800887 if not branch.remote.projectname:
888 branch.remote.projectname = self.name
889 branch.remote.Save()
890
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800891 url = branch.remote.ReviewUrl(self.UserEmail)
892 if url is None:
893 raise UploadError('review not configured')
894 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800895
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800896 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800897 rp = ['gerrit receive-pack']
898 for e in people[0]:
899 rp.append('--reviewer=%s' % sq(e))
900 for e in people[1]:
901 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800902 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700903
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800904 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800905
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800906 if dest_branch.startswith(R_HEADS):
907 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700908
909 upload_type = 'for'
910 if draft:
911 upload_type = 'drafts'
912
913 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
914 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800915 if auto_topic:
916 ref_spec = ref_spec + '/' + branch.name
917 cmd.append(ref_spec)
918
919 if GitCommand(self, cmd, bare = True).Wait() != 0:
920 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921
922 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
923 self.bare_git.UpdateRef(R_PUB + branch.name,
924 R_HEADS + branch.name,
925 message = msg)
926
927
928## Sync ##
929
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700930 def Sync_NetworkHalf(self,
931 quiet=False,
932 is_new=None,
933 current_branch_only=False,
934 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700935 """Perform only the network IO portion of the sync process.
936 Local working directory/branch state is not affected.
937 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700938 if is_new is None:
939 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200940 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941 self._InitGitDir()
942 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700943
944 if is_new:
945 alt = os.path.join(self.gitdir, 'objects/info/alternates')
946 try:
947 fd = open(alt, 'rb')
948 try:
949 alt_dir = fd.readline().rstrip()
950 finally:
951 fd.close()
952 except IOError:
953 alt_dir = None
954 else:
955 alt_dir = None
956
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700957 if clone_bundle \
958 and alt_dir is None \
959 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700960 is_new = False
961
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700962 if not current_branch_only:
963 if self.sync_c:
964 current_branch_only = True
965 elif not self.manifest._loaded:
966 # Manifest cannot check defaults until it syncs.
967 current_branch_only = False
968 elif self.manifest.default.sync_c:
969 current_branch_only = True
970
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700971 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
972 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800974
975 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800976 self._InitMRef()
977 else:
978 self._InitMirrorHead()
979 try:
980 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
981 except OSError:
982 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800984
985 def PostRepoUpgrade(self):
986 self._InitHooks()
987
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700988 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900989 for copyfile in self.copyfiles:
990 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991
David Pursehouse8a68ff92012-09-24 12:15:13 +0900992 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700993 if self.revisionId:
994 return self.revisionId
995
996 rem = self.GetRemote(self.remote.name)
997 rev = rem.ToLocal(self.revisionExpr)
998
David Pursehouse8a68ff92012-09-24 12:15:13 +0900999 if all_refs is not None and rev in all_refs:
1000 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001001
1002 try:
1003 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1004 except GitError:
1005 raise ManifestInvalidRevisionError(
1006 'revision %s in %s not found' % (self.revisionExpr,
1007 self.name))
1008
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001009 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001010 """Perform only the local IO portion of the sync process.
1011 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001012 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001013 all_refs = self.bare_ref.all
1014 self.CleanPublishedCache(all_refs)
1015 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001016
David Pursehouse1d947b32012-10-25 12:23:11 +09001017 def _doff():
1018 self._FastForward(revid)
1019 self._CopyFiles()
1020
Skyler Kaufman835cd682011-03-08 12:14:41 -08001021 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001022 head = self.work_git.GetHead()
1023 if head.startswith(R_HEADS):
1024 branch = head[len(R_HEADS):]
1025 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001026 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001027 except KeyError:
1028 head = None
1029 else:
1030 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001032 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001033 # Currently on a detached HEAD. The user is assumed to
1034 # not have any local modifications worth worrying about.
1035 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001036 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001037 syncbuf.fail(self, _PriorSyncFailedError())
1038 return
1039
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001040 if head == revid:
1041 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001042 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001043 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001044 if not syncbuf.detach_head:
1045 return
1046 else:
1047 lost = self._revlist(not_rev(revid), HEAD)
1048 if lost:
1049 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001050
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001052 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001053 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001054 syncbuf.fail(self, e)
1055 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001057 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001059 if head == revid:
1060 # No changes; don't do anything further.
1061 #
1062 return
1063
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001066 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001068 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001070 syncbuf.info(self,
1071 "leaving %s; does not track upstream",
1072 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001073 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001074 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001075 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001076 syncbuf.fail(self, e)
1077 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001079 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001081 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001082 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001084 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085 if not_merged:
1086 if upstream_gain:
1087 # The user has published this branch and some of those
1088 # commits are not yet merged upstream. We do not want
1089 # to rewrite the published commits so we punt.
1090 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001091 syncbuf.fail(self,
1092 "branch %s is published (but not merged) and is now %d commits behind"
1093 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001094 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001095 elif pub == head:
1096 # All published commits are merged, and thus we are a
1097 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001098 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001099 syncbuf.later1(self, _doff)
1100 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001102 # Examine the local commits not in the remote. Find the
1103 # last one attributed to this user, if any.
1104 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001105 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001106 last_mine = None
1107 cnt_mine = 0
1108 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001109 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001110 if committer_email == self.UserEmail:
1111 last_mine = commit_id
1112 cnt_mine += 1
1113
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001114 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001115 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001116
1117 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001118 syncbuf.fail(self, _DirtyError())
1119 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001120
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001121 # If the upstream switched on us, warn the user.
1122 #
1123 if branch.merge != self.revisionExpr:
1124 if branch.merge and self.revisionExpr:
1125 syncbuf.info(self,
1126 'manifest switched %s...%s',
1127 branch.merge,
1128 self.revisionExpr)
1129 elif branch.merge:
1130 syncbuf.info(self,
1131 'manifest no longer tracks %s',
1132 branch.merge)
1133
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001134 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001136 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001137 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001138 syncbuf.info(self,
1139 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001140 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001141
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001142 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001143 if not ID_RE.match(self.revisionExpr):
1144 # in case of manifest sync the revisionExpr might be a SHA1
1145 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146 branch.Save()
1147
Mike Pontillod3153822012-02-28 11:53:24 -08001148 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001149 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001150 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001151 self._CopyFiles()
1152 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001153 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001154 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001155 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001156 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001157 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001158 syncbuf.fail(self, e)
1159 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001161 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))
David Pursehouse7e6dd2d2012-10-25 12:40:51 +09001180 cmd.extend(map(str, remote.fetch))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001181 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():
David Pursehousec1b86a22012-11-14 11:36:51 +09001385 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
Brian Harring14a66742012-09-28 20:21:57 -07001393
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:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001591 print("Server does not provide clone.bundle; ignoring.",
1592 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001593 return False
1594
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001595 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001596 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001597 os.rename(tmpPath, dstPath)
1598 return True
1599 else:
1600 os.remove(tmpPath)
1601 return False
1602 else:
1603 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001604
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001605 def _Checkout(self, rev, quiet=False):
1606 cmd = ['checkout']
1607 if quiet:
1608 cmd.append('-q')
1609 cmd.append(rev)
1610 cmd.append('--')
1611 if GitCommand(self, cmd).Wait() != 0:
1612 if self._allrefs:
1613 raise GitError('%s checkout %s ' % (self.name, rev))
1614
Pierre Tardye5a21222011-03-24 16:28:18 +01001615 def _CherryPick(self, rev, quiet=False):
1616 cmd = ['cherry-pick']
1617 cmd.append(rev)
1618 cmd.append('--')
1619 if GitCommand(self, cmd).Wait() != 0:
1620 if self._allrefs:
1621 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1622
Erwan Mahea94f1622011-08-19 13:56:09 +02001623 def _Revert(self, rev, quiet=False):
1624 cmd = ['revert']
1625 cmd.append('--no-edit')
1626 cmd.append(rev)
1627 cmd.append('--')
1628 if GitCommand(self, cmd).Wait() != 0:
1629 if self._allrefs:
1630 raise GitError('%s revert %s ' % (self.name, rev))
1631
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001632 def _ResetHard(self, rev, quiet=True):
1633 cmd = ['reset', '--hard']
1634 if quiet:
1635 cmd.append('-q')
1636 cmd.append(rev)
1637 if GitCommand(self, cmd).Wait() != 0:
1638 raise GitError('%s reset --hard %s ' % (self.name, rev))
1639
1640 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001641 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001642 if onto is not None:
1643 cmd.extend(['--onto', onto])
1644 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001645 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001646 raise GitError('%s rebase %s ' % (self.name, upstream))
1647
Pierre Tardy3d125942012-05-04 12:18:12 +02001648 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001649 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001650 if ffonly:
1651 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001652 if GitCommand(self, cmd).Wait() != 0:
1653 raise GitError('%s merge %s ' % (self.name, head))
1654
1655 def _InitGitDir(self):
1656 if not os.path.exists(self.gitdir):
1657 os.makedirs(self.gitdir)
1658 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001659
Shawn O. Pearce88443382010-10-08 10:02:09 +02001660 mp = self.manifest.manifestProject
1661 ref_dir = mp.config.GetString('repo.reference')
1662
1663 if ref_dir:
1664 mirror_git = os.path.join(ref_dir, self.name + '.git')
1665 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1666 self.relpath + '.git')
1667
1668 if os.path.exists(mirror_git):
1669 ref_dir = mirror_git
1670
1671 elif os.path.exists(repo_git):
1672 ref_dir = repo_git
1673
1674 else:
1675 ref_dir = None
1676
1677 if ref_dir:
1678 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1679 os.path.join(ref_dir, 'objects') + '\n')
1680
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001681 if self.manifest.IsMirror:
1682 self.config.SetString('core.bare', 'true')
1683 else:
1684 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685
1686 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001687 try:
1688 to_rm = os.listdir(hooks)
1689 except OSError:
1690 to_rm = []
1691 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001692 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001693 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001694
1695 m = self.manifest.manifestProject.config
1696 for key in ['user.name', 'user.email']:
1697 if m.Has(key, include_defaults = False):
1698 self.config.SetString(key, m.GetString(key))
1699
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001700 def _InitHooks(self):
1701 hooks = self._gitdir_path('hooks')
1702 if not os.path.exists(hooks):
1703 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001704 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001705 name = os.path.basename(stock_hook)
1706
Victor Boivie65e0f352011-04-18 11:23:29 +02001707 if name in ('commit-msg',) and not self.remote.review \
1708 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001709 # Don't install a Gerrit Code Review hook if this
1710 # project does not appear to use it for reviews.
1711 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001712 # Since the manifest project is one of those, but also
1713 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001714 continue
1715
1716 dst = os.path.join(hooks, name)
1717 if os.path.islink(dst):
1718 continue
1719 if os.path.exists(dst):
1720 if filecmp.cmp(stock_hook, dst, shallow=False):
1721 os.remove(dst)
1722 else:
1723 _error("%s: Not replacing %s hook", self.relpath, name)
1724 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001725 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001726 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001727 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001728 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001729 raise GitError('filesystem must support symlinks')
1730 else:
1731 raise
1732
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001733 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001734 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001735 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001736 remote.url = self.remote.url
1737 remote.review = self.remote.review
1738 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001739
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001740 if self.worktree:
1741 remote.ResetFetch(mirror=False)
1742 else:
1743 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001744 remote.Save()
1745
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001746 def _InitMRef(self):
1747 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001748 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001749
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001750 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001751 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001752
1753 def _InitAnyMRef(self, ref):
1754 cur = self.bare_ref.symref(ref)
1755
1756 if self.revisionId:
1757 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1758 msg = 'manifest set to %s' % self.revisionId
1759 dst = self.revisionId + '^0'
1760 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1761 else:
1762 remote = self.GetRemote(self.remote.name)
1763 dst = remote.ToLocal(self.revisionExpr)
1764 if cur != dst:
1765 msg = 'manifest set to %s' % self.revisionExpr
1766 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001767
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768 def _InitWorkTree(self):
1769 dotgit = os.path.join(self.worktree, '.git')
1770 if not os.path.exists(dotgit):
1771 os.makedirs(dotgit)
1772
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001773 for name in ['config',
1774 'description',
1775 'hooks',
1776 'info',
1777 'logs',
1778 'objects',
1779 'packed-refs',
1780 'refs',
1781 'rr-cache',
1782 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001783 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001784 src = os.path.join(self.gitdir, name)
1785 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001786 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001787 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001788 else:
1789 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001790 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001791 if e.errno == errno.EPERM:
1792 raise GitError('filesystem must support symlinks')
1793 else:
1794 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001795
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001796 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001797
1798 cmd = ['read-tree', '--reset', '-u']
1799 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001800 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001801 if GitCommand(self, cmd).Wait() != 0:
1802 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001803
1804 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1805 if not os.path.exists(rr_cache):
1806 os.makedirs(rr_cache)
1807
Shawn O. Pearce93609662009-04-21 10:50:33 -07001808 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001809
1810 def _gitdir_path(self, path):
1811 return os.path.join(self.gitdir, path)
1812
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001813 def _revlist(self, *args, **kw):
1814 a = []
1815 a.extend(args)
1816 a.append('--')
1817 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001818
1819 @property
1820 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001821 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001822
1823 class _GitGetByExec(object):
1824 def __init__(self, project, bare):
1825 self._project = project
1826 self._bare = bare
1827
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001828 def LsOthers(self):
1829 p = GitCommand(self._project,
1830 ['ls-files',
1831 '-z',
1832 '--others',
1833 '--exclude-standard'],
1834 bare = False,
1835 capture_stdout = True,
1836 capture_stderr = True)
1837 if p.Wait() == 0:
1838 out = p.stdout
1839 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09001840 return out[:-1].split('\0') # pylint: disable=W1401
1841 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001842 return []
1843
1844 def DiffZ(self, name, *args):
1845 cmd = [name]
1846 cmd.append('-z')
1847 cmd.extend(args)
1848 p = GitCommand(self._project,
1849 cmd,
1850 bare = False,
1851 capture_stdout = True,
1852 capture_stderr = True)
1853 try:
1854 out = p.process.stdout.read()
1855 r = {}
1856 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09001857 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001858 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001859 try:
1860 info = out.next()
1861 path = out.next()
1862 except StopIteration:
1863 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001864
1865 class _Info(object):
1866 def __init__(self, path, omode, nmode, oid, nid, state):
1867 self.path = path
1868 self.src_path = None
1869 self.old_mode = omode
1870 self.new_mode = nmode
1871 self.old_id = oid
1872 self.new_id = nid
1873
1874 if len(state) == 1:
1875 self.status = state
1876 self.level = None
1877 else:
1878 self.status = state[:1]
1879 self.level = state[1:]
1880 while self.level.startswith('0'):
1881 self.level = self.level[1:]
1882
1883 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09001884 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001885 if info.status in ('R', 'C'):
1886 info.src_path = info.path
1887 info.path = out.next()
1888 r[info.path] = info
1889 return r
1890 finally:
1891 p.Wait()
1892
1893 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001894 if self._bare:
1895 path = os.path.join(self._project.gitdir, HEAD)
1896 else:
1897 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08001898 try:
1899 fd = open(path, 'rb')
1900 except IOError:
1901 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001902 try:
1903 line = fd.read()
1904 finally:
1905 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001906 if line.startswith('ref: '):
1907 return line[5:-1]
1908 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001909
1910 def SetHead(self, ref, message=None):
1911 cmdv = []
1912 if message is not None:
1913 cmdv.extend(['-m', message])
1914 cmdv.append(HEAD)
1915 cmdv.append(ref)
1916 self.symbolic_ref(*cmdv)
1917
1918 def DetachHead(self, new, message=None):
1919 cmdv = ['--no-deref']
1920 if message is not None:
1921 cmdv.extend(['-m', message])
1922 cmdv.append(HEAD)
1923 cmdv.append(new)
1924 self.update_ref(*cmdv)
1925
1926 def UpdateRef(self, name, new, old=None,
1927 message=None,
1928 detach=False):
1929 cmdv = []
1930 if message is not None:
1931 cmdv.extend(['-m', message])
1932 if detach:
1933 cmdv.append('--no-deref')
1934 cmdv.append(name)
1935 cmdv.append(new)
1936 if old is not None:
1937 cmdv.append(old)
1938 self.update_ref(*cmdv)
1939
1940 def DeleteRef(self, name, old=None):
1941 if not old:
1942 old = self.rev_parse(name)
1943 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001944 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001945
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001946 def rev_list(self, *args, **kw):
1947 if 'format' in kw:
1948 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1949 else:
1950 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001951 cmdv.extend(args)
1952 p = GitCommand(self._project,
1953 cmdv,
1954 bare = self._bare,
1955 capture_stdout = True,
1956 capture_stderr = True)
1957 r = []
1958 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001959 if line[-1] == '\n':
1960 line = line[:-1]
1961 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001962 if p.Wait() != 0:
1963 raise GitError('%s rev-list %s: %s' % (
1964 self._project.name,
1965 str(args),
1966 p.stderr))
1967 return r
1968
1969 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08001970 """Allow arbitrary git commands using pythonic syntax.
1971
1972 This allows you to do things like:
1973 git_obj.rev_parse('HEAD')
1974
1975 Since we don't have a 'rev_parse' method defined, the __getattr__ will
1976 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07001977 Any other positional arguments will be passed to the git command, and the
1978 following keyword arguments are supported:
1979 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08001980
1981 Args:
1982 name: The name of the git command to call. Any '_' characters will
1983 be replaced with '-'.
1984
1985 Returns:
1986 A callable object that will try to call git with the named command.
1987 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001988 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07001989 def runner(*args, **kwargs):
1990 cmdv = []
1991 config = kwargs.pop('config', None)
1992 for k in kwargs:
1993 raise TypeError('%s() got an unexpected keyword argument %r'
1994 % (name, k))
1995 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07001996 if not git_require((1, 7, 2)):
1997 raise ValueError('cannot set config on command line for %s()'
1998 % name)
Dave Borowitz091f8932012-10-23 17:01:04 -07001999 for k, v in config.iteritems():
2000 cmdv.append('-c')
2001 cmdv.append('%s=%s' % (k, v))
2002 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002003 cmdv.extend(args)
2004 p = GitCommand(self._project,
2005 cmdv,
2006 bare = self._bare,
2007 capture_stdout = True,
2008 capture_stderr = True)
2009 if p.Wait() != 0:
2010 raise GitError('%s %s: %s' % (
2011 self._project.name,
2012 name,
2013 p.stderr))
2014 r = p.stdout
2015 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2016 return r[:-1]
2017 return r
2018 return runner
2019
2020
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002021class _PriorSyncFailedError(Exception):
2022 def __str__(self):
2023 return 'prior sync failed; rebase still in progress'
2024
2025class _DirtyError(Exception):
2026 def __str__(self):
2027 return 'contains uncommitted changes'
2028
2029class _InfoMessage(object):
2030 def __init__(self, project, text):
2031 self.project = project
2032 self.text = text
2033
2034 def Print(self, syncbuf):
2035 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2036 syncbuf.out.nl()
2037
2038class _Failure(object):
2039 def __init__(self, project, why):
2040 self.project = project
2041 self.why = why
2042
2043 def Print(self, syncbuf):
2044 syncbuf.out.fail('error: %s/: %s',
2045 self.project.relpath,
2046 str(self.why))
2047 syncbuf.out.nl()
2048
2049class _Later(object):
2050 def __init__(self, project, action):
2051 self.project = project
2052 self.action = action
2053
2054 def Run(self, syncbuf):
2055 out = syncbuf.out
2056 out.project('project %s/', self.project.relpath)
2057 out.nl()
2058 try:
2059 self.action()
2060 out.nl()
2061 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002062 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002063 out.nl()
2064 return False
2065
2066class _SyncColoring(Coloring):
2067 def __init__(self, config):
2068 Coloring.__init__(self, config, 'reposync')
2069 self.project = self.printer('header', attr = 'bold')
2070 self.info = self.printer('info')
2071 self.fail = self.printer('fail', fg='red')
2072
2073class SyncBuffer(object):
2074 def __init__(self, config, detach_head=False):
2075 self._messages = []
2076 self._failures = []
2077 self._later_queue1 = []
2078 self._later_queue2 = []
2079
2080 self.out = _SyncColoring(config)
2081 self.out.redirect(sys.stderr)
2082
2083 self.detach_head = detach_head
2084 self.clean = True
2085
2086 def info(self, project, fmt, *args):
2087 self._messages.append(_InfoMessage(project, fmt % args))
2088
2089 def fail(self, project, err=None):
2090 self._failures.append(_Failure(project, err))
2091 self.clean = False
2092
2093 def later1(self, project, what):
2094 self._later_queue1.append(_Later(project, what))
2095
2096 def later2(self, project, what):
2097 self._later_queue2.append(_Later(project, what))
2098
2099 def Finish(self):
2100 self._PrintMessages()
2101 self._RunLater()
2102 self._PrintMessages()
2103 return self.clean
2104
2105 def _RunLater(self):
2106 for q in ['_later_queue1', '_later_queue2']:
2107 if not self._RunQueue(q):
2108 return
2109
2110 def _RunQueue(self, queue):
2111 for m in getattr(self, queue):
2112 if not m.Run(self):
2113 self.clean = False
2114 return False
2115 setattr(self, queue, [])
2116 return True
2117
2118 def _PrintMessages(self):
2119 for m in self._messages:
2120 m.Print(self)
2121 for m in self._failures:
2122 m.Print(self)
2123
2124 self._messages = []
2125 self._failures = []
2126
2127
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002128class MetaProject(Project):
2129 """A special project housed under .repo.
2130 """
2131 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002132 Project.__init__(self,
2133 manifest = manifest,
2134 name = name,
2135 gitdir = gitdir,
2136 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002137 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002138 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002139 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002140 revisionId = None,
2141 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002142
2143 def PreSync(self):
2144 if self.Exists:
2145 cb = self.CurrentBranch
2146 if cb:
2147 base = self.GetBranch(cb).merge
2148 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002149 self.revisionExpr = base
2150 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002151
Florian Vallee5d016502012-06-07 17:19:26 +02002152 def MetaBranchSwitch(self, target):
2153 """ Prepare MetaProject for manifest branch switch
2154 """
2155
2156 # detach and delete manifest branch, allowing a new
2157 # branch to take over
2158 syncbuf = SyncBuffer(self.config, detach_head = True)
2159 self.Sync_LocalHalf(syncbuf)
2160 syncbuf.Finish()
2161
2162 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002163 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002164 capture_stdout = True,
2165 capture_stderr = True).Wait() == 0
2166
2167
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002168 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002169 def LastFetch(self):
2170 try:
2171 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2172 return os.path.getmtime(fh)
2173 except OSError:
2174 return 0
2175
2176 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002177 def HasChanges(self):
2178 """Has the remote received new commits not yet checked out?
2179 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002180 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002181 return False
2182
David Pursehouse8a68ff92012-09-24 12:15:13 +09002183 all_refs = self.bare_ref.all
2184 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002185 head = self.work_git.GetHead()
2186 if head.startswith(R_HEADS):
2187 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002188 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002189 except KeyError:
2190 head = None
2191
2192 if revid == head:
2193 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002194 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002195 return True
2196 return False