blob: 901a2831902a16a602eb64a50587fc59cf69f5ab [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
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080026import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070027import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070028
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070029from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070030from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070031from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090032from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080033from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080034from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070035from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036
Shawn O. Pearced237b692009-04-17 18:49:50 -070037from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070038
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070039def _lwrite(path, content):
40 lock = '%s.lock' % path
41
42 fd = open(lock, 'wb')
43 try:
44 fd.write(content)
45 finally:
46 fd.close()
47
48 try:
49 os.rename(lock, path)
50 except OSError:
51 os.remove(lock)
52 raise
53
Shawn O. Pearce48244782009-04-16 08:25:57 -070054def _error(fmt, *args):
55 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070056 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070057
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070058def not_rev(r):
59 return '^' + r
60
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080061def sq(r):
62 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080063
Doug Anderson8ced8642011-01-10 14:16:30 -080064_project_hook_list = None
65def _ProjectHooks():
66 """List the hooks present in the 'hooks' directory.
67
68 These hooks are project hooks and are copied to the '.git/hooks' directory
69 of all subprojects.
70
71 This function caches the list of hooks (based on the contents of the
72 'repo/hooks' directory) on the first call.
73
74 Returns:
75 A list of absolute paths to all of the files in the hooks directory.
76 """
77 global _project_hook_list
78 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080079 d = os.path.abspath(os.path.dirname(__file__))
80 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080081 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
82 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080083
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080084
Shawn O. Pearce632768b2008-10-23 11:58:52 -070085class DownloadedChange(object):
86 _commit_cache = None
87
88 def __init__(self, project, base, change_id, ps_id, commit):
89 self.project = project
90 self.base = base
91 self.change_id = change_id
92 self.ps_id = ps_id
93 self.commit = commit
94
95 @property
96 def commits(self):
97 if self._commit_cache is None:
98 self._commit_cache = self.project.bare_git.rev_list(
99 '--abbrev=8',
100 '--abbrev-commit',
101 '--pretty=oneline',
102 '--reverse',
103 '--date-order',
104 not_rev(self.base),
105 self.commit,
106 '--')
107 return self._commit_cache
108
109
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700110class ReviewableBranch(object):
111 _commit_cache = None
112
113 def __init__(self, project, branch, base):
114 self.project = project
115 self.branch = branch
116 self.base = base
117
118 @property
119 def name(self):
120 return self.branch.name
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
125 self._commit_cache = self.project.bare_git.rev_list(
126 '--abbrev=8',
127 '--abbrev-commit',
128 '--pretty=oneline',
129 '--reverse',
130 '--date-order',
131 not_rev(self.base),
132 R_HEADS + self.name,
133 '--')
134 return self._commit_cache
135
136 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800137 def unabbrev_commits(self):
138 r = dict()
139 for commit in self.project.bare_git.rev_list(
140 not_rev(self.base),
141 R_HEADS + self.name,
142 '--'):
143 r[commit[0:8]] = commit
144 return r
145
146 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700147 def date(self):
148 return self.project.bare_git.log(
149 '--pretty=format:%cd',
150 '-n', '1',
151 R_HEADS + self.name,
152 '--')
153
Brian Harring435370c2012-07-28 15:37:04 -0700154 def UploadForReview(self, people, auto_topic=False, draft=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800155 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700156 people,
Brian Harring435370c2012-07-28 15:37:04 -0700157 auto_topic=auto_topic,
158 draft=draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700160 def GetPublishedRefs(self):
161 refs = {}
162 output = self.project.bare_git.ls_remote(
163 self.branch.remote.SshReviewUrl(self.project.UserEmail),
164 'refs/changes/*')
165 for line in output.split('\n'):
166 try:
167 (sha, ref) = line.split()
168 refs[sha] = ref
169 except ValueError:
170 pass
171
172 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700173
174class StatusColoring(Coloring):
175 def __init__(self, config):
176 Coloring.__init__(self, config, 'status')
177 self.project = self.printer('header', attr = 'bold')
178 self.branch = self.printer('header', attr = 'bold')
179 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700180 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
182 self.added = self.printer('added', fg = 'green')
183 self.changed = self.printer('changed', fg = 'red')
184 self.untracked = self.printer('untracked', fg = 'red')
185
186
187class DiffColoring(Coloring):
188 def __init__(self, config):
189 Coloring.__init__(self, config, 'diff')
190 self.project = self.printer('header', attr = 'bold')
191
James W. Mills24c13082012-04-12 15:04:13 -0500192class _Annotation:
193 def __init__(self, name, value, keep):
194 self.name = name
195 self.value = value
196 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197
198class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800199 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700200 self.src = src
201 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800202 self.abs_src = abssrc
203 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204
205 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800206 src = self.abs_src
207 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 # copy file if it does not exist or is out of date
209 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
210 try:
211 # remove existing file first, since it might be read-only
212 if os.path.exists(dest):
213 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400214 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200215 dest_dir = os.path.dirname(dest)
216 if not os.path.isdir(dest_dir):
217 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218 shutil.copy(src, dest)
219 # make the file read-only
220 mode = os.stat(dest)[stat.ST_MODE]
221 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
222 os.chmod(dest, mode)
223 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700224 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700226class RemoteSpec(object):
227 def __init__(self,
228 name,
229 url = None,
230 review = None):
231 self.name = name
232 self.url = url
233 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234
Doug Anderson37282b42011-03-04 11:54:18 -0800235class RepoHook(object):
236 """A RepoHook contains information about a script to run as a hook.
237
238 Hooks are used to run a python script before running an upload (for instance,
239 to run presubmit checks). Eventually, we may have hooks for other actions.
240
241 This shouldn't be confused with files in the 'repo/hooks' directory. Those
242 files are copied into each '.git/hooks' folder for each project. Repo-level
243 hooks are associated instead with repo actions.
244
245 Hooks are always python. When a hook is run, we will load the hook into the
246 interpreter and execute its main() function.
247 """
248 def __init__(self,
249 hook_type,
250 hooks_project,
251 topdir,
252 abort_if_user_denies=False):
253 """RepoHook constructor.
254
255 Params:
256 hook_type: A string representing the type of hook. This is also used
257 to figure out the name of the file containing the hook. For
258 example: 'pre-upload'.
259 hooks_project: The project containing the repo hooks. If you have a
260 manifest, this is manifest.repo_hooks_project. OK if this is None,
261 which will make the hook a no-op.
262 topdir: Repo's top directory (the one containing the .repo directory).
263 Scripts will run with CWD as this directory. If you have a manifest,
264 this is manifest.topdir
265 abort_if_user_denies: If True, we'll throw a HookError() if the user
266 doesn't allow us to run the hook.
267 """
268 self._hook_type = hook_type
269 self._hooks_project = hooks_project
270 self._topdir = topdir
271 self._abort_if_user_denies = abort_if_user_denies
272
273 # Store the full path to the script for convenience.
274 if self._hooks_project:
275 self._script_fullpath = os.path.join(self._hooks_project.worktree,
276 self._hook_type + '.py')
277 else:
278 self._script_fullpath = None
279
280 def _GetHash(self):
281 """Return a hash of the contents of the hooks directory.
282
283 We'll just use git to do this. This hash has the property that if anything
284 changes in the directory we will return a different has.
285
286 SECURITY CONSIDERATION:
287 This hash only represents the contents of files in the hook directory, not
288 any other files imported or called by hooks. Changes to imported files
289 can change the script behavior without affecting the hash.
290
291 Returns:
292 A string representing the hash. This will always be ASCII so that it can
293 be printed to the user easily.
294 """
295 assert self._hooks_project, "Must have hooks to calculate their hash."
296
297 # We will use the work_git object rather than just calling GetRevisionId().
298 # That gives us a hash of the latest checked in version of the files that
299 # the user will actually be executing. Specifically, GetRevisionId()
300 # doesn't appear to change even if a user checks out a different version
301 # of the hooks repo (via git checkout) nor if a user commits their own revs.
302 #
303 # NOTE: Local (non-committed) changes will not be factored into this hash.
304 # I think this is OK, since we're really only worried about warning the user
305 # about upstream changes.
306 return self._hooks_project.work_git.rev_parse('HEAD')
307
308 def _GetMustVerb(self):
309 """Return 'must' if the hook is required; 'should' if not."""
310 if self._abort_if_user_denies:
311 return 'must'
312 else:
313 return 'should'
314
315 def _CheckForHookApproval(self):
316 """Check to see whether this hook has been approved.
317
318 We'll look at the hash of all of the hooks. If this matches the hash that
319 the user last approved, we're done. If it doesn't, we'll ask the user
320 about approval.
321
322 Note that we ask permission for each individual hook even though we use
323 the hash of all hooks when detecting changes. We'd like the user to be
324 able to approve / deny each hook individually. We only use the hash of all
325 hooks because there is no other easy way to detect changes to local imports.
326
327 Returns:
328 True if this hook is approved to run; False otherwise.
329
330 Raises:
331 HookError: Raised if the user doesn't approve and abort_if_user_denies
332 was passed to the consturctor.
333 """
Doug Anderson37282b42011-03-04 11:54:18 -0800334 hooks_config = self._hooks_project.config
335 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
336
337 # Get the last hash that the user approved for this hook; may be None.
338 old_hash = hooks_config.GetString(git_approval_key)
339
340 # Get the current hash so we can tell if scripts changed since approval.
341 new_hash = self._GetHash()
342
343 if old_hash is not None:
344 # User previously approved hook and asked not to be prompted again.
345 if new_hash == old_hash:
346 # Approval matched. We're done.
347 return True
348 else:
349 # Give the user a reason why we're prompting, since they last told
350 # us to "never ask again".
351 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
352 self._hook_type)
353 else:
354 prompt = ''
355
356 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
357 if sys.stdout.isatty():
358 prompt += ('Repo %s run the script:\n'
359 ' %s\n'
360 '\n'
361 'Do you want to allow this script to run '
362 '(yes/yes-never-ask-again/NO)? ') % (
363 self._GetMustVerb(), self._script_fullpath)
364 response = raw_input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900365 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800366
367 # User is doing a one-time approval.
368 if response in ('y', 'yes'):
369 return True
370 elif response == 'yes-never-ask-again':
371 hooks_config.SetString(git_approval_key, new_hash)
372 return True
373
374 # For anything else, we'll assume no approval.
375 if self._abort_if_user_denies:
376 raise HookError('You must allow the %s hook or use --no-verify.' %
377 self._hook_type)
378
379 return False
380
381 def _ExecuteHook(self, **kwargs):
382 """Actually execute the given hook.
383
384 This will run the hook's 'main' function in our python interpreter.
385
386 Args:
387 kwargs: Keyword arguments to pass to the hook. These are often specific
388 to the hook type. For instance, pre-upload hooks will contain
389 a project_list.
390 """
391 # Keep sys.path and CWD stashed away so that we can always restore them
392 # upon function exit.
393 orig_path = os.getcwd()
394 orig_syspath = sys.path
395
396 try:
397 # Always run hooks with CWD as topdir.
398 os.chdir(self._topdir)
399
400 # Put the hook dir as the first item of sys.path so hooks can do
401 # relative imports. We want to replace the repo dir as [0] so
402 # hooks can't import repo files.
403 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
404
405 # Exec, storing global context in the context dict. We catch exceptions
406 # and convert to a HookError w/ just the failing traceback.
407 context = {}
408 try:
409 execfile(self._script_fullpath, context)
410 except Exception:
411 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
412 traceback.format_exc(), self._hook_type))
413
414 # Running the script should have defined a main() function.
415 if 'main' not in context:
416 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
417
418
419 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
420 # We don't actually want hooks to define their main with this argument--
421 # it's there to remind them that their hook should always take **kwargs.
422 # For instance, a pre-upload hook should be defined like:
423 # def main(project_list, **kwargs):
424 #
425 # This allows us to later expand the API without breaking old hooks.
426 kwargs = kwargs.copy()
427 kwargs['hook_should_take_kwargs'] = True
428
429 # Call the main function in the hook. If the hook should cause the
430 # build to fail, it will raise an Exception. We'll catch that convert
431 # to a HookError w/ just the failing traceback.
432 try:
433 context['main'](**kwargs)
434 except Exception:
435 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
436 'above.' % (
437 traceback.format_exc(), self._hook_type))
438 finally:
439 # Restore sys.path and CWD.
440 sys.path = orig_syspath
441 os.chdir(orig_path)
442
443 def Run(self, user_allows_all_hooks, **kwargs):
444 """Run the hook.
445
446 If the hook doesn't exist (because there is no hooks project or because
447 this particular hook is not enabled), this is a no-op.
448
449 Args:
450 user_allows_all_hooks: If True, we will never prompt about running the
451 hook--we'll just assume it's OK to run it.
452 kwargs: Keyword arguments to pass to the hook. These are often specific
453 to the hook type. For instance, pre-upload hooks will contain
454 a project_list.
455
456 Raises:
457 HookError: If there was a problem finding the hook or the user declined
458 to run a required hook (from _CheckForHookApproval).
459 """
460 # No-op if there is no hooks project or if hook is disabled.
461 if ((not self._hooks_project) or
462 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
463 return
464
465 # Bail with a nice error if we can't find the hook.
466 if not os.path.isfile(self._script_fullpath):
467 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
468
469 # Make sure the user is OK with running the hook.
470 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
471 return
472
473 # Run the hook with the same version of python we're using.
474 self._ExecuteHook(**kwargs)
475
476
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700477class Project(object):
478 def __init__(self,
479 manifest,
480 name,
481 remote,
482 gitdir,
483 worktree,
484 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700485 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800486 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700487 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700488 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700489 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800490 sync_s = False,
491 upstream = None,
492 parent = None,
493 is_derived = False):
494 """Init a Project object.
495
496 Args:
497 manifest: The XmlManifest object.
498 name: The `name` attribute of manifest.xml's project element.
499 remote: RemoteSpec object specifying its remote's properties.
500 gitdir: Absolute path of git directory.
501 worktree: Absolute path of git working tree.
502 relpath: Relative path of git working tree to repo's top directory.
503 revisionExpr: The `revision` attribute of manifest.xml's project element.
504 revisionId: git commit id for checking out.
505 rebase: The `rebase` attribute of manifest.xml's project element.
506 groups: The `groups` attribute of manifest.xml's project element.
507 sync_c: The `sync-c` attribute of manifest.xml's project element.
508 sync_s: The `sync-s` attribute of manifest.xml's project element.
509 upstream: The `upstream` attribute of manifest.xml's project element.
510 parent: The parent Project object.
511 is_derived: False if the project was explicitly defined in the manifest;
512 True if the project is a discovered submodule.
513 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700514 self.manifest = manifest
515 self.name = name
516 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800517 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800518 if worktree:
519 self.worktree = worktree.replace('\\', '/')
520 else:
521 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700522 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700523 self.revisionExpr = revisionExpr
524
525 if revisionId is None \
526 and revisionExpr \
527 and IsId(revisionExpr):
528 self.revisionId = revisionExpr
529 else:
530 self.revisionId = revisionId
531
Mike Pontillod3153822012-02-28 11:53:24 -0800532 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700533 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700534 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800535 self.sync_s = sync_s
Brian Harring14a66742012-09-28 20:21:57 -0700536 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800537 self.parent = parent
538 self.is_derived = is_derived
539 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800540
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700541 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700542 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500543 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700544 self.config = GitConfig.ForRepository(
545 gitdir = self.gitdir,
546 defaults = self.manifest.globalConfig)
547
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800548 if self.worktree:
549 self.work_git = self._GitGetByExec(self, bare=False)
550 else:
551 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700553 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700554
Doug Anderson37282b42011-03-04 11:54:18 -0800555 # This will be filled in if a project is later identified to be the
556 # project containing repo hooks.
557 self.enabled_repo_hooks = []
558
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700559 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800560 def Derived(self):
561 return self.is_derived
562
563 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700564 def Exists(self):
565 return os.path.isdir(self.gitdir)
566
567 @property
568 def CurrentBranch(self):
569 """Obtain the name of the currently checked out branch.
570 The branch name omits the 'refs/heads/' prefix.
571 None is returned if the project is on a detached HEAD.
572 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700573 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700574 if b.startswith(R_HEADS):
575 return b[len(R_HEADS):]
576 return None
577
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700578 def IsRebaseInProgress(self):
579 w = self.worktree
580 g = os.path.join(w, '.git')
581 return os.path.exists(os.path.join(g, 'rebase-apply')) \
582 or os.path.exists(os.path.join(g, 'rebase-merge')) \
583 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200584
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700585 def IsDirty(self, consider_untracked=True):
586 """Is the working directory modified in some way?
587 """
588 self.work_git.update_index('-q',
589 '--unmerged',
590 '--ignore-missing',
591 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900592 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700593 return True
594 if self.work_git.DiffZ('diff-files'):
595 return True
596 if consider_untracked and self.work_git.LsOthers():
597 return True
598 return False
599
600 _userident_name = None
601 _userident_email = None
602
603 @property
604 def UserName(self):
605 """Obtain the user's personal name.
606 """
607 if self._userident_name is None:
608 self._LoadUserIdentity()
609 return self._userident_name
610
611 @property
612 def UserEmail(self):
613 """Obtain the user's email address. This is very likely
614 to be their Gerrit login.
615 """
616 if self._userident_email is None:
617 self._LoadUserIdentity()
618 return self._userident_email
619
620 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900621 u = self.bare_git.var('GIT_COMMITTER_IDENT')
622 m = re.compile("^(.*) <([^>]*)> ").match(u)
623 if m:
624 self._userident_name = m.group(1)
625 self._userident_email = m.group(2)
626 else:
627 self._userident_name = ''
628 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700629
630 def GetRemote(self, name):
631 """Get the configuration for a single remote.
632 """
633 return self.config.GetRemote(name)
634
635 def GetBranch(self, name):
636 """Get the configuration for a single branch.
637 """
638 return self.config.GetBranch(name)
639
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700640 def GetBranches(self):
641 """Get all existing local branches.
642 """
643 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900644 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700645 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700646
David Pursehouse8a68ff92012-09-24 12:15:13 +0900647 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700648 if name.startswith(R_HEADS):
649 name = name[len(R_HEADS):]
650 b = self.GetBranch(name)
651 b.current = name == current
652 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900653 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700654 heads[name] = b
655
David Pursehouse8a68ff92012-09-24 12:15:13 +0900656 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700657 if name.startswith(R_PUB):
658 name = name[len(R_PUB):]
659 b = heads.get(name)
660 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900661 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700662
663 return heads
664
Colin Cross5acde752012-03-28 20:15:45 -0700665 def MatchesGroups(self, manifest_groups):
666 """Returns true if the manifest groups specified at init should cause
667 this project to be synced.
668 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700669 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700670
671 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700672 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700673 manifest_groups: "-group1,group2"
674 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500675
676 The special manifest group "default" will match any project that
677 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700678 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500679 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700680 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500681 if not 'notdefault' in expanded_project_groups:
682 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700683
Conley Owens971de8e2012-04-16 10:36:08 -0700684 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700685 for group in expanded_manifest_groups:
686 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700687 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700688 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700689 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700690
Conley Owens971de8e2012-04-16 10:36:08 -0700691 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700692
693## Status Display ##
694
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500695 def HasChanges(self):
696 """Returns true if there are uncommitted changes.
697 """
698 self.work_git.update_index('-q',
699 '--unmerged',
700 '--ignore-missing',
701 '--refresh')
702 if self.IsRebaseInProgress():
703 return True
704
705 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
706 return True
707
708 if self.work_git.DiffZ('diff-files'):
709 return True
710
711 if self.work_git.LsOthers():
712 return True
713
714 return False
715
Terence Haddock4655e812011-03-31 12:33:34 +0200716 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200718
719 Args:
720 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700721 """
722 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200723 if output_redir == None:
724 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700725 print(file=output_redir)
726 print('project %s/' % self.relpath, file=output_redir)
727 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728 return
729
730 self.work_git.update_index('-q',
731 '--unmerged',
732 '--ignore-missing',
733 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700734 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
736 df = self.work_git.DiffZ('diff-files')
737 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100738 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700739 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700740
741 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200742 if not output_redir == None:
743 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744 out.project('project %-40s', self.relpath + '/')
745
746 branch = self.CurrentBranch
747 if branch is None:
748 out.nobranch('(*** NO BRANCH ***)')
749 else:
750 out.branch('branch %s', branch)
751 out.nl()
752
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700753 if rb:
754 out.important('prior sync failed; rebase still in progress')
755 out.nl()
756
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 paths = list()
758 paths.extend(di.keys())
759 paths.extend(df.keys())
760 paths.extend(do)
761
762 paths = list(set(paths))
763 paths.sort()
764
765 for p in paths:
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900766 try:
767 i = di[p]
768 except KeyError:
769 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900771 try:
772 f = df[p]
773 except KeyError:
774 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200775
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900776 if i:
777 i_status = i.status.upper()
778 else:
779 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900781 if f:
782 f_status = f.status.lower()
783 else:
784 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700785
786 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800787 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788 i.src_path, p, i.level)
789 else:
790 line = ' %s%s\t%s' % (i_status, f_status, p)
791
792 if i and not f:
793 out.added('%s', line)
794 elif (i and f) or (not i and f):
795 out.changed('%s', line)
796 elif not i and not f:
797 out.untracked('%s', line)
798 else:
799 out.write('%s', line)
800 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200801
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700802 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803
pelyad67872d2012-03-28 14:49:58 +0300804 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700805 """Prints the status of the repository to stdout.
806 """
807 out = DiffColoring(self.config)
808 cmd = ['diff']
809 if out.is_on:
810 cmd.append('--color')
811 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300812 if absolute_paths:
813 cmd.append('--src-prefix=a/%s/' % self.relpath)
814 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700815 cmd.append('--')
816 p = GitCommand(self,
817 cmd,
818 capture_stdout = True,
819 capture_stderr = True)
820 has_diff = False
821 for line in p.process.stdout:
822 if not has_diff:
823 out.nl()
824 out.project('project %s/' % self.relpath)
825 out.nl()
826 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700827 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828 p.Wait()
829
830
831## Publish / Upload ##
832
David Pursehouse8a68ff92012-09-24 12:15:13 +0900833 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834 """Was the branch published (uploaded) for code review?
835 If so, returns the SHA-1 hash of the last published
836 state for the branch.
837 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700838 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900839 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700840 try:
841 return self.bare_git.rev_parse(key)
842 except GitError:
843 return None
844 else:
845 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900846 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700847 except KeyError:
848 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849
David Pursehouse8a68ff92012-09-24 12:15:13 +0900850 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700851 """Prunes any stale published refs.
852 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900853 if all_refs is None:
854 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700855 heads = set()
856 canrm = {}
David Pursehouse8a68ff92012-09-24 12:15:13 +0900857 for name, ref_id in all_refs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858 if name.startswith(R_HEADS):
859 heads.add(name)
860 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900861 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862
David Pursehouse8a68ff92012-09-24 12:15:13 +0900863 for name, ref_id in canrm.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700864 n = name[len(R_PUB):]
865 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900866 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700868 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 """List any branches which can be uploaded for review.
870 """
871 heads = {}
872 pubed = {}
873
David Pursehouse8a68ff92012-09-24 12:15:13 +0900874 for name, ref_id in self._allrefs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700875 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900876 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900878 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879
880 ready = []
David Pursehouse8a68ff92012-09-24 12:15:13 +0900881 for branch, ref_id in heads.iteritems():
882 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700883 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700884 if selected_branch and branch != selected_branch:
885 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800887 rb = self.GetUploadableBranch(branch)
888 if rb:
889 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890 return ready
891
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800892 def GetUploadableBranch(self, branch_name):
893 """Get a single uploadable branch, or None.
894 """
895 branch = self.GetBranch(branch_name)
896 base = branch.LocalMerge
897 if branch.LocalMerge:
898 rb = ReviewableBranch(self, branch, base)
899 if rb.commits:
900 return rb
901 return None
902
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700903 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700904 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700905 auto_topic=False,
906 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700907 """Uploads the named branch for code review.
908 """
909 if branch is None:
910 branch = self.CurrentBranch
911 if branch is None:
912 raise GitError('not currently on a branch')
913
914 branch = self.GetBranch(branch)
915 if not branch.LocalMerge:
916 raise GitError('branch %s does not track a remote' % branch.name)
917 if not branch.remote.review:
918 raise GitError('remote %s has no review url' % branch.remote.name)
919
920 dest_branch = branch.merge
921 if not dest_branch.startswith(R_HEADS):
922 dest_branch = R_HEADS + dest_branch
923
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800924 if not branch.remote.projectname:
925 branch.remote.projectname = self.name
926 branch.remote.Save()
927
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800928 url = branch.remote.ReviewUrl(self.UserEmail)
929 if url is None:
930 raise UploadError('review not configured')
931 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800932
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800933 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800934 rp = ['gerrit receive-pack']
935 for e in people[0]:
936 rp.append('--reviewer=%s' % sq(e))
937 for e in people[1]:
938 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800939 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700940
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800941 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800942
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800943 if dest_branch.startswith(R_HEADS):
944 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700945
946 upload_type = 'for'
947 if draft:
948 upload_type = 'drafts'
949
950 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
951 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800952 if auto_topic:
953 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800954 if not url.startswith('ssh://'):
955 rp = ['r=%s' % p for p in people[0]] + \
956 ['cc=%s' % p for p in people[1]]
957 if rp:
958 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800959 cmd.append(ref_spec)
960
961 if GitCommand(self, cmd, bare = True).Wait() != 0:
962 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963
964 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
965 self.bare_git.UpdateRef(R_PUB + branch.name,
966 R_HEADS + branch.name,
967 message = msg)
968
969
970## Sync ##
971
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700972 def Sync_NetworkHalf(self,
973 quiet=False,
974 is_new=None,
975 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700976 clone_bundle=True,
977 no_tags=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978 """Perform only the network IO portion of the sync process.
979 Local working directory/branch state is not affected.
980 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700981 if is_new is None:
982 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200983 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984 self._InitGitDir()
985 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700986
987 if is_new:
988 alt = os.path.join(self.gitdir, 'objects/info/alternates')
989 try:
990 fd = open(alt, 'rb')
991 try:
992 alt_dir = fd.readline().rstrip()
993 finally:
994 fd.close()
995 except IOError:
996 alt_dir = None
997 else:
998 alt_dir = None
999
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001000 if clone_bundle \
1001 and alt_dir is None \
1002 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001003 is_new = False
1004
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001005 if not current_branch_only:
1006 if self.sync_c:
1007 current_branch_only = True
1008 elif not self.manifest._loaded:
1009 # Manifest cannot check defaults until it syncs.
1010 current_branch_only = False
1011 elif self.manifest.default.sync_c:
1012 current_branch_only = True
1013
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001014 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001015 current_branch_only=current_branch_only,
1016 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001018
1019 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001020 self._InitMRef()
1021 else:
1022 self._InitMirrorHead()
1023 try:
1024 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1025 except OSError:
1026 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001027 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001028
1029 def PostRepoUpgrade(self):
1030 self._InitHooks()
1031
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001032 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001033 for copyfile in self.copyfiles:
1034 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001035
David Pursehouse8a68ff92012-09-24 12:15:13 +09001036 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001037 if self.revisionId:
1038 return self.revisionId
1039
1040 rem = self.GetRemote(self.remote.name)
1041 rev = rem.ToLocal(self.revisionExpr)
1042
David Pursehouse8a68ff92012-09-24 12:15:13 +09001043 if all_refs is not None and rev in all_refs:
1044 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001045
1046 try:
1047 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1048 except GitError:
1049 raise ManifestInvalidRevisionError(
1050 'revision %s in %s not found' % (self.revisionExpr,
1051 self.name))
1052
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001053 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054 """Perform only the local IO portion of the sync process.
1055 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001057 all_refs = self.bare_ref.all
1058 self.CleanPublishedCache(all_refs)
1059 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001060
David Pursehouse1d947b32012-10-25 12:23:11 +09001061 def _doff():
1062 self._FastForward(revid)
1063 self._CopyFiles()
1064
Skyler Kaufman835cd682011-03-08 12:14:41 -08001065 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001066 head = self.work_git.GetHead()
1067 if head.startswith(R_HEADS):
1068 branch = head[len(R_HEADS):]
1069 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001070 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001071 except KeyError:
1072 head = None
1073 else:
1074 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001076 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 # Currently on a detached HEAD. The user is assumed to
1078 # not have any local modifications worth worrying about.
1079 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001080 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001081 syncbuf.fail(self, _PriorSyncFailedError())
1082 return
1083
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001084 if head == revid:
1085 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001086 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001087 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001088 if not syncbuf.detach_head:
1089 return
1090 else:
1091 lost = self._revlist(not_rev(revid), HEAD)
1092 if lost:
1093 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001094
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001095 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001096 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001097 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001098 syncbuf.fail(self, e)
1099 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001101 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001103 if head == revid:
1104 # No changes; don't do anything further.
1105 #
1106 return
1107
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001109
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001110 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001112 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001113 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001114 syncbuf.info(self,
1115 "leaving %s; does not track upstream",
1116 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001118 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001119 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001120 syncbuf.fail(self, e)
1121 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001122 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001123 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001124
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001125 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001126 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001127 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001128 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001129 if not_merged:
1130 if upstream_gain:
1131 # The user has published this branch and some of those
1132 # commits are not yet merged upstream. We do not want
1133 # to rewrite the published commits so we punt.
1134 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001135 syncbuf.fail(self,
1136 "branch %s is published (but not merged) and is now %d commits behind"
1137 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001138 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001139 elif pub == head:
1140 # All published commits are merged, and thus we are a
1141 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001142 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001143 syncbuf.later1(self, _doff)
1144 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001145
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001146 # Examine the local commits not in the remote. Find the
1147 # last one attributed to this user, if any.
1148 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001149 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001150 last_mine = None
1151 cnt_mine = 0
1152 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001153 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001154 if committer_email == self.UserEmail:
1155 last_mine = commit_id
1156 cnt_mine += 1
1157
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001158 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001159 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160
1161 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001162 syncbuf.fail(self, _DirtyError())
1163 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001164
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001165 # If the upstream switched on us, warn the user.
1166 #
1167 if branch.merge != self.revisionExpr:
1168 if branch.merge and self.revisionExpr:
1169 syncbuf.info(self,
1170 'manifest switched %s...%s',
1171 branch.merge,
1172 self.revisionExpr)
1173 elif branch.merge:
1174 syncbuf.info(self,
1175 'manifest no longer tracks %s',
1176 branch.merge)
1177
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001178 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001180 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001181 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001182 syncbuf.info(self,
1183 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001184 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001185
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001186 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001187 if not ID_RE.match(self.revisionExpr):
1188 # in case of manifest sync the revisionExpr might be a SHA1
1189 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190 branch.Save()
1191
Mike Pontillod3153822012-02-28 11:53:24 -08001192 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001193 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001194 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001195 self._CopyFiles()
1196 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001197 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001198 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001199 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001200 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001201 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001202 syncbuf.fail(self, e)
1203 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001205 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001206
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001207 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001208 # dest should already be an absolute path, but src is project relative
1209 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001210 abssrc = os.path.join(self.worktree, src)
1211 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001212
James W. Mills24c13082012-04-12 15:04:13 -05001213 def AddAnnotation(self, name, value, keep):
1214 self.annotations.append(_Annotation(name, value, keep))
1215
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001216 def DownloadPatchSet(self, change_id, patch_id):
1217 """Download a single patch set of a single change to FETCH_HEAD.
1218 """
1219 remote = self.GetRemote(self.remote.name)
1220
1221 cmd = ['fetch', remote.name]
1222 cmd.append('refs/changes/%2.2d/%d/%d' \
1223 % (change_id % 100, change_id, patch_id))
David Pursehouse7e6dd2d2012-10-25 12:40:51 +09001224 cmd.extend(map(str, remote.fetch))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001225 if GitCommand(self, cmd, bare=True).Wait() != 0:
1226 return None
1227 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001228 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001229 change_id,
1230 patch_id,
1231 self.bare_git.rev_parse('FETCH_HEAD'))
1232
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001233
1234## Branch Management ##
1235
1236 def StartBranch(self, name):
1237 """Create a new branch off the manifest's revision.
1238 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001239 head = self.work_git.GetHead()
1240 if head == (R_HEADS + name):
1241 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242
David Pursehouse8a68ff92012-09-24 12:15:13 +09001243 all_refs = self.bare_ref.all
1244 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001245 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001246 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001247 capture_stdout = True,
1248 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001249
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001250 branch = self.GetBranch(name)
1251 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001252 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001253 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001254
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001255 if head.startswith(R_HEADS):
1256 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001257 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001258 except KeyError:
1259 head = None
1260
1261 if revid and head and revid == head:
1262 ref = os.path.join(self.gitdir, R_HEADS + name)
1263 try:
1264 os.makedirs(os.path.dirname(ref))
1265 except OSError:
1266 pass
1267 _lwrite(ref, '%s\n' % revid)
1268 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1269 'ref: %s%s\n' % (R_HEADS, name))
1270 branch.Save()
1271 return True
1272
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001273 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001274 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001275 capture_stdout = True,
1276 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001277 branch.Save()
1278 return True
1279 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280
Wink Saville02d79452009-04-10 13:01:24 -07001281 def CheckoutBranch(self, name):
1282 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001283
1284 Args:
1285 name: The name of the branch to checkout.
1286
1287 Returns:
1288 True if the checkout succeeded; False if it didn't; None if the branch
1289 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001290 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001291 rev = R_HEADS + name
1292 head = self.work_git.GetHead()
1293 if head == rev:
1294 # Already on the branch
1295 #
1296 return True
Wink Saville02d79452009-04-10 13:01:24 -07001297
David Pursehouse8a68ff92012-09-24 12:15:13 +09001298 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001299 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001300 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001301 except KeyError:
1302 # Branch does not exist in this project
1303 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001304 return None
Wink Saville02d79452009-04-10 13:01:24 -07001305
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001306 if head.startswith(R_HEADS):
1307 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001308 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001309 except KeyError:
1310 head = None
1311
1312 if head == revid:
1313 # Same revision; just update HEAD to point to the new
1314 # target branch, but otherwise take no other action.
1315 #
1316 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1317 'ref: %s%s\n' % (R_HEADS, name))
1318 return True
1319
1320 return GitCommand(self,
1321 ['checkout', name, '--'],
1322 capture_stdout = True,
1323 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001324
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001325 def AbandonBranch(self, name):
1326 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001327
1328 Args:
1329 name: The name of the branch to abandon.
1330
1331 Returns:
1332 True if the abandon succeeded; False if it didn't; None if the branch
1333 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001334 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001335 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001336 all_refs = self.bare_ref.all
1337 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001338 # Doesn't exist
1339 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001340
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001341 head = self.work_git.GetHead()
1342 if head == rev:
1343 # We can't destroy the branch while we are sitting
1344 # on it. Switch to a detached HEAD.
1345 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001346 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001347
David Pursehouse8a68ff92012-09-24 12:15:13 +09001348 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001349 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001350 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1351 '%s\n' % revid)
1352 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001353 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001354
1355 return GitCommand(self,
1356 ['branch', '-D', name],
1357 capture_stdout = True,
1358 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001359
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001360 def PruneHeads(self):
1361 """Prune any topic branches already merged into upstream.
1362 """
1363 cb = self.CurrentBranch
1364 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001365 left = self._allrefs
1366 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367 if name.startswith(R_HEADS):
1368 name = name[len(R_HEADS):]
1369 if cb is None or name != cb:
1370 kill.append(name)
1371
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001372 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373 if cb is not None \
1374 and not self._revlist(HEAD + '...' + rev) \
1375 and not self.IsDirty(consider_untracked = False):
1376 self.work_git.DetachHead(HEAD)
1377 kill.append(cb)
1378
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001379 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001380 old = self.bare_git.GetHead()
1381 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001382 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1383
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001384 try:
1385 self.bare_git.DetachHead(rev)
1386
1387 b = ['branch', '-d']
1388 b.extend(kill)
1389 b = GitCommand(self, b, bare=True,
1390 capture_stdout=True,
1391 capture_stderr=True)
1392 b.Wait()
1393 finally:
1394 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001395 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001396
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001397 for branch in kill:
1398 if (R_HEADS + branch) not in left:
1399 self.CleanPublishedCache()
1400 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001401
1402 if cb and cb not in kill:
1403 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001404 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001405
1406 kept = []
1407 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001408 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001409 branch = self.GetBranch(branch)
1410 base = branch.LocalMerge
1411 if not base:
1412 base = rev
1413 kept.append(ReviewableBranch(self, branch, base))
1414 return kept
1415
1416
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001417## Submodule Management ##
1418
1419 def GetRegisteredSubprojects(self):
1420 result = []
1421 def rec(subprojects):
1422 if not subprojects:
1423 return
1424 result.extend(subprojects)
1425 for p in subprojects:
1426 rec(p.subprojects)
1427 rec(self.subprojects)
1428 return result
1429
1430 def _GetSubmodules(self):
1431 # Unfortunately we cannot call `git submodule status --recursive` here
1432 # because the working tree might not exist yet, and it cannot be used
1433 # without a working tree in its current implementation.
1434
1435 def get_submodules(gitdir, rev):
1436 # Parse .gitmodules for submodule sub_paths and sub_urls
1437 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1438 if not sub_paths:
1439 return []
1440 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1441 # revision of submodule repository
1442 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1443 submodules = []
1444 for sub_path, sub_url in zip(sub_paths, sub_urls):
1445 try:
1446 sub_rev = sub_revs[sub_path]
1447 except KeyError:
1448 # Ignore non-exist submodules
1449 continue
1450 submodules.append((sub_rev, sub_path, sub_url))
1451 return submodules
1452
1453 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1454 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1455 def parse_gitmodules(gitdir, rev):
1456 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1457 try:
1458 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1459 bare = True, gitdir = gitdir)
1460 except GitError:
1461 return [], []
1462 if p.Wait() != 0:
1463 return [], []
1464
1465 gitmodules_lines = []
1466 fd, temp_gitmodules_path = tempfile.mkstemp()
1467 try:
1468 os.write(fd, p.stdout)
1469 os.close(fd)
1470 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1471 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1472 bare = True, gitdir = gitdir)
1473 if p.Wait() != 0:
1474 return [], []
1475 gitmodules_lines = p.stdout.split('\n')
1476 except GitError:
1477 return [], []
1478 finally:
1479 os.remove(temp_gitmodules_path)
1480
1481 names = set()
1482 paths = {}
1483 urls = {}
1484 for line in gitmodules_lines:
1485 if not line:
1486 continue
1487 m = re_path.match(line)
1488 if m:
1489 names.add(m.group(1))
1490 paths[m.group(1)] = m.group(2)
1491 continue
1492 m = re_url.match(line)
1493 if m:
1494 names.add(m.group(1))
1495 urls[m.group(1)] = m.group(2)
1496 continue
1497 names = sorted(names)
1498 return ([paths.get(name, '') for name in names],
1499 [urls.get(name, '') for name in names])
1500
1501 def git_ls_tree(gitdir, rev, paths):
1502 cmd = ['ls-tree', rev, '--']
1503 cmd.extend(paths)
1504 try:
1505 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1506 bare = True, gitdir = gitdir)
1507 except GitError:
1508 return []
1509 if p.Wait() != 0:
1510 return []
1511 objects = {}
1512 for line in p.stdout.split('\n'):
1513 if not line.strip():
1514 continue
1515 object_rev, object_path = line.split()[2:4]
1516 objects[object_path] = object_rev
1517 return objects
1518
1519 try:
1520 rev = self.GetRevisionId()
1521 except GitError:
1522 return []
1523 return get_submodules(self.gitdir, rev)
1524
1525 def GetDerivedSubprojects(self):
1526 result = []
1527 if not self.Exists:
1528 # If git repo does not exist yet, querying its submodules will
1529 # mess up its states; so return here.
1530 return result
1531 for rev, path, url in self._GetSubmodules():
1532 name = self.manifest.GetSubprojectName(self, path)
1533 project = self.manifest.projects.get(name)
1534 if project:
1535 result.extend(project.GetDerivedSubprojects())
1536 continue
1537 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1538 remote = RemoteSpec(self.remote.name,
1539 url = url,
1540 review = self.remote.review)
1541 subproject = Project(manifest = self.manifest,
1542 name = name,
1543 remote = remote,
1544 gitdir = gitdir,
1545 worktree = worktree,
1546 relpath = relpath,
1547 revisionExpr = self.revisionExpr,
1548 revisionId = rev,
1549 rebase = self.rebase,
1550 groups = self.groups,
1551 sync_c = self.sync_c,
1552 sync_s = self.sync_s,
1553 parent = self,
1554 is_derived = True)
1555 result.append(subproject)
1556 result.extend(subproject.GetDerivedSubprojects())
1557 return result
1558
1559
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001560## Direct Git Commands ##
1561
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001562 def _RemoteFetch(self, name=None,
1563 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001564 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001565 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001566 alt_dir=None,
1567 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001568
1569 is_sha1 = False
1570 tag_name = None
1571
Brian Harring14a66742012-09-28 20:21:57 -07001572 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001573 try:
1574 # if revision (sha or tag) is not present then following function
1575 # throws an error.
1576 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1577 return True
1578 except GitError:
1579 # There is no such persistent revision. We have to fetch it.
1580 return False
Brian Harring14a66742012-09-28 20:21:57 -07001581
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001582 if current_branch_only:
1583 if ID_RE.match(self.revisionExpr) is not None:
1584 is_sha1 = True
1585 elif self.revisionExpr.startswith(R_TAGS):
1586 # this is a tag and its sha1 value should never change
1587 tag_name = self.revisionExpr[len(R_TAGS):]
1588
1589 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001590 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001591 return True
Brian Harring14a66742012-09-28 20:21:57 -07001592 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1593 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001594
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001595 if not name:
1596 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001597
1598 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001599 remote = self.GetRemote(name)
1600 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001601 ssh_proxy = True
1602
Shawn O. Pearce88443382010-10-08 10:02:09 +02001603 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001604 if alt_dir and 'objects' == os.path.basename(alt_dir):
1605 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001606 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1607 remote = self.GetRemote(name)
1608
David Pursehouse8a68ff92012-09-24 12:15:13 +09001609 all_refs = self.bare_ref.all
1610 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001611 tmp = set()
1612
David Pursehouse8a68ff92012-09-24 12:15:13 +09001613 for r, ref_id in GitRefs(ref_dir).all.iteritems():
1614 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001615 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001616 all_refs[r] = ref_id
1617 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001618 continue
1619
David Pursehouse8a68ff92012-09-24 12:15:13 +09001620 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001621 continue
1622
David Pursehouse8a68ff92012-09-24 12:15:13 +09001623 r = 'refs/_alt/%s' % ref_id
1624 all_refs[r] = ref_id
1625 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001626 tmp.add(r)
1627
David Pursehouse8a68ff92012-09-24 12:15:13 +09001628 ref_names = list(all_refs.keys())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001629 ref_names.sort()
1630
1631 tmp_packed = ''
1632 old_packed = ''
1633
1634 for r in ref_names:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001635 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001636 tmp_packed += line
1637 if r not in tmp:
1638 old_packed += line
1639
1640 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001641 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001642 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001643
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001644 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001645
1646 # The --depth option only affects the initial fetch; after that we'll do
1647 # full fetches of changes.
1648 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1649 if depth and initial:
1650 cmd.append('--depth=%s' % depth)
1651
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001652 if quiet:
1653 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001654 if not self.worktree:
1655 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001656 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001657
Brian Harring14a66742012-09-28 20:21:57 -07001658 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001659 # Fetch whole repo
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001660 if no_tags:
1661 cmd.append('--no-tags')
1662 else:
1663 cmd.append('--tags')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001664 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1665 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001666 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001667 cmd.append(tag_name)
1668 else:
1669 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001670 if is_sha1:
1671 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001672 if branch.startswith(R_HEADS):
1673 branch = branch[len(R_HEADS):]
1674 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001675
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001676 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001677 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001678 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1679 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001680 ok = True
1681 break
Brian Harring14a66742012-09-28 20:21:57 -07001682 elif current_branch_only and is_sha1 and ret == 128:
1683 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1684 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1685 # abort the optimization attempt and do a full sync.
1686 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001687 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001688
1689 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001690 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001691 if old_packed != '':
1692 _lwrite(packed_refs, old_packed)
1693 else:
1694 os.remove(packed_refs)
1695 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001696
1697 if is_sha1 and current_branch_only and self.upstream:
1698 # We just synced the upstream given branch; verify we
1699 # got what we wanted, else trigger a second run of all
1700 # refs.
1701 if not CheckForSha1():
1702 return self._RemoteFetch(name=name, current_branch_only=False,
1703 initial=False, quiet=quiet, alt_dir=alt_dir)
1704
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001705 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001706
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001707 def _ApplyCloneBundle(self, initial=False, quiet=False):
1708 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1709 return False
1710
1711 remote = self.GetRemote(self.remote.name)
1712 bundle_url = remote.url + '/clone.bundle'
1713 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001714 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1715 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001716 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1717 return False
1718
1719 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1720 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1721
1722 exist_dst = os.path.exists(bundle_dst)
1723 exist_tmp = os.path.exists(bundle_tmp)
1724
1725 if not initial and not exist_dst and not exist_tmp:
1726 return False
1727
1728 if not exist_dst:
1729 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1730 if not exist_dst:
1731 return False
1732
1733 cmd = ['fetch']
1734 if quiet:
1735 cmd.append('--quiet')
1736 if not self.worktree:
1737 cmd.append('--update-head-ok')
1738 cmd.append(bundle_dst)
1739 for f in remote.fetch:
1740 cmd.append(str(f))
1741 cmd.append('refs/tags/*:refs/tags/*')
1742
1743 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001744 if os.path.exists(bundle_dst):
1745 os.remove(bundle_dst)
1746 if os.path.exists(bundle_tmp):
1747 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001748 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001749
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001750 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001751 if os.path.exists(dstPath):
1752 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001753
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001754 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001755 if quiet:
1756 cmd += ['--silent']
1757 if os.path.exists(tmpPath):
1758 size = os.stat(tmpPath).st_size
1759 if size >= 1024:
1760 cmd += ['--continue-at', '%d' % (size,)]
1761 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001762 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001763 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1764 cmd += ['--proxy', os.environ['http_proxy']]
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001765 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
1766 if cookiefile:
1767 cmd += ['--cookie', cookiefile]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001768 cmd += [srcUrl]
1769
1770 if IsTrace():
1771 Trace('%s', ' '.join(cmd))
1772 try:
1773 proc = subprocess.Popen(cmd)
1774 except OSError:
1775 return False
1776
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001777 curlret = proc.wait()
1778
1779 if curlret == 22:
1780 # From curl man page:
1781 # 22: HTTP page not retrieved. The requested url was not found or
1782 # returned another error with the HTTP error code being 400 or above.
1783 # This return code only appears if -f, --fail is used.
1784 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001785 print("Server does not provide clone.bundle; ignoring.",
1786 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001787 return False
1788
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001789 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001790 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001791 os.rename(tmpPath, dstPath)
1792 return True
1793 else:
1794 os.remove(tmpPath)
1795 return False
1796 else:
1797 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001798
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001799 def _Checkout(self, rev, quiet=False):
1800 cmd = ['checkout']
1801 if quiet:
1802 cmd.append('-q')
1803 cmd.append(rev)
1804 cmd.append('--')
1805 if GitCommand(self, cmd).Wait() != 0:
1806 if self._allrefs:
1807 raise GitError('%s checkout %s ' % (self.name, rev))
1808
Pierre Tardye5a21222011-03-24 16:28:18 +01001809 def _CherryPick(self, rev, quiet=False):
1810 cmd = ['cherry-pick']
1811 cmd.append(rev)
1812 cmd.append('--')
1813 if GitCommand(self, cmd).Wait() != 0:
1814 if self._allrefs:
1815 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1816
Erwan Mahea94f1622011-08-19 13:56:09 +02001817 def _Revert(self, rev, quiet=False):
1818 cmd = ['revert']
1819 cmd.append('--no-edit')
1820 cmd.append(rev)
1821 cmd.append('--')
1822 if GitCommand(self, cmd).Wait() != 0:
1823 if self._allrefs:
1824 raise GitError('%s revert %s ' % (self.name, rev))
1825
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001826 def _ResetHard(self, rev, quiet=True):
1827 cmd = ['reset', '--hard']
1828 if quiet:
1829 cmd.append('-q')
1830 cmd.append(rev)
1831 if GitCommand(self, cmd).Wait() != 0:
1832 raise GitError('%s reset --hard %s ' % (self.name, rev))
1833
1834 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001835 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001836 if onto is not None:
1837 cmd.extend(['--onto', onto])
1838 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001839 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001840 raise GitError('%s rebase %s ' % (self.name, upstream))
1841
Pierre Tardy3d125942012-05-04 12:18:12 +02001842 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001843 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001844 if ffonly:
1845 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001846 if GitCommand(self, cmd).Wait() != 0:
1847 raise GitError('%s merge %s ' % (self.name, head))
1848
1849 def _InitGitDir(self):
1850 if not os.path.exists(self.gitdir):
1851 os.makedirs(self.gitdir)
1852 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001853
Shawn O. Pearce88443382010-10-08 10:02:09 +02001854 mp = self.manifest.manifestProject
1855 ref_dir = mp.config.GetString('repo.reference')
1856
1857 if ref_dir:
1858 mirror_git = os.path.join(ref_dir, self.name + '.git')
1859 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1860 self.relpath + '.git')
1861
1862 if os.path.exists(mirror_git):
1863 ref_dir = mirror_git
1864
1865 elif os.path.exists(repo_git):
1866 ref_dir = repo_git
1867
1868 else:
1869 ref_dir = None
1870
1871 if ref_dir:
1872 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1873 os.path.join(ref_dir, 'objects') + '\n')
1874
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001875 if self.manifest.IsMirror:
1876 self.config.SetString('core.bare', 'true')
1877 else:
1878 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001879
1880 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001881 try:
1882 to_rm = os.listdir(hooks)
1883 except OSError:
1884 to_rm = []
1885 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001886 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001887 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001888
1889 m = self.manifest.manifestProject.config
1890 for key in ['user.name', 'user.email']:
1891 if m.Has(key, include_defaults = False):
1892 self.config.SetString(key, m.GetString(key))
1893
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001894 def _InitHooks(self):
1895 hooks = self._gitdir_path('hooks')
1896 if not os.path.exists(hooks):
1897 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001898 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001899 name = os.path.basename(stock_hook)
1900
Victor Boivie65e0f352011-04-18 11:23:29 +02001901 if name in ('commit-msg',) and not self.remote.review \
1902 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001903 # Don't install a Gerrit Code Review hook if this
1904 # project does not appear to use it for reviews.
1905 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001906 # Since the manifest project is one of those, but also
1907 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001908 continue
1909
1910 dst = os.path.join(hooks, name)
1911 if os.path.islink(dst):
1912 continue
1913 if os.path.exists(dst):
1914 if filecmp.cmp(stock_hook, dst, shallow=False):
1915 os.remove(dst)
1916 else:
1917 _error("%s: Not replacing %s hook", self.relpath, name)
1918 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001919 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001920 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001921 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001922 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001923 raise GitError('filesystem must support symlinks')
1924 else:
1925 raise
1926
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001927 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001928 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001929 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001930 remote.url = self.remote.url
1931 remote.review = self.remote.review
1932 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001933
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001934 if self.worktree:
1935 remote.ResetFetch(mirror=False)
1936 else:
1937 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001938 remote.Save()
1939
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001940 def _InitMRef(self):
1941 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001942 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001943
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001944 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001945 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001946
1947 def _InitAnyMRef(self, ref):
1948 cur = self.bare_ref.symref(ref)
1949
1950 if self.revisionId:
1951 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1952 msg = 'manifest set to %s' % self.revisionId
1953 dst = self.revisionId + '^0'
1954 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1955 else:
1956 remote = self.GetRemote(self.remote.name)
1957 dst = remote.ToLocal(self.revisionExpr)
1958 if cur != dst:
1959 msg = 'manifest set to %s' % self.revisionExpr
1960 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001961
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001962 def _InitWorkTree(self):
1963 dotgit = os.path.join(self.worktree, '.git')
1964 if not os.path.exists(dotgit):
1965 os.makedirs(dotgit)
1966
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001967 for name in ['config',
1968 'description',
1969 'hooks',
1970 'info',
1971 'logs',
1972 'objects',
1973 'packed-refs',
1974 'refs',
1975 'rr-cache',
1976 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001977 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001978 src = os.path.join(self.gitdir, name)
1979 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001980 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001981 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001982 else:
1983 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001984 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001985 if e.errno == errno.EPERM:
1986 raise GitError('filesystem must support symlinks')
1987 else:
1988 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001989
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001990 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001991
1992 cmd = ['read-tree', '--reset', '-u']
1993 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001994 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001995 if GitCommand(self, cmd).Wait() != 0:
1996 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001997
1998 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1999 if not os.path.exists(rr_cache):
2000 os.makedirs(rr_cache)
2001
Shawn O. Pearce93609662009-04-21 10:50:33 -07002002 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002003
2004 def _gitdir_path(self, path):
2005 return os.path.join(self.gitdir, path)
2006
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002007 def _revlist(self, *args, **kw):
2008 a = []
2009 a.extend(args)
2010 a.append('--')
2011 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002012
2013 @property
2014 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002015 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002016
2017 class _GitGetByExec(object):
2018 def __init__(self, project, bare):
2019 self._project = project
2020 self._bare = bare
2021
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002022 def LsOthers(self):
2023 p = GitCommand(self._project,
2024 ['ls-files',
2025 '-z',
2026 '--others',
2027 '--exclude-standard'],
2028 bare = False,
2029 capture_stdout = True,
2030 capture_stderr = True)
2031 if p.Wait() == 0:
2032 out = p.stdout
2033 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002034 return out[:-1].split('\0') # pylint: disable=W1401
2035 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002036 return []
2037
2038 def DiffZ(self, name, *args):
2039 cmd = [name]
2040 cmd.append('-z')
2041 cmd.extend(args)
2042 p = GitCommand(self._project,
2043 cmd,
2044 bare = False,
2045 capture_stdout = True,
2046 capture_stderr = True)
2047 try:
2048 out = p.process.stdout.read()
2049 r = {}
2050 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002051 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002052 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002053 try:
2054 info = out.next()
2055 path = out.next()
2056 except StopIteration:
2057 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002058
2059 class _Info(object):
2060 def __init__(self, path, omode, nmode, oid, nid, state):
2061 self.path = path
2062 self.src_path = None
2063 self.old_mode = omode
2064 self.new_mode = nmode
2065 self.old_id = oid
2066 self.new_id = nid
2067
2068 if len(state) == 1:
2069 self.status = state
2070 self.level = None
2071 else:
2072 self.status = state[:1]
2073 self.level = state[1:]
2074 while self.level.startswith('0'):
2075 self.level = self.level[1:]
2076
2077 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002078 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002079 if info.status in ('R', 'C'):
2080 info.src_path = info.path
2081 info.path = out.next()
2082 r[info.path] = info
2083 return r
2084 finally:
2085 p.Wait()
2086
2087 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002088 if self._bare:
2089 path = os.path.join(self._project.gitdir, HEAD)
2090 else:
2091 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002092 try:
2093 fd = open(path, 'rb')
2094 except IOError:
2095 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002096 try:
2097 line = fd.read()
2098 finally:
2099 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002100 if line.startswith('ref: '):
2101 return line[5:-1]
2102 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002103
2104 def SetHead(self, ref, message=None):
2105 cmdv = []
2106 if message is not None:
2107 cmdv.extend(['-m', message])
2108 cmdv.append(HEAD)
2109 cmdv.append(ref)
2110 self.symbolic_ref(*cmdv)
2111
2112 def DetachHead(self, new, message=None):
2113 cmdv = ['--no-deref']
2114 if message is not None:
2115 cmdv.extend(['-m', message])
2116 cmdv.append(HEAD)
2117 cmdv.append(new)
2118 self.update_ref(*cmdv)
2119
2120 def UpdateRef(self, name, new, old=None,
2121 message=None,
2122 detach=False):
2123 cmdv = []
2124 if message is not None:
2125 cmdv.extend(['-m', message])
2126 if detach:
2127 cmdv.append('--no-deref')
2128 cmdv.append(name)
2129 cmdv.append(new)
2130 if old is not None:
2131 cmdv.append(old)
2132 self.update_ref(*cmdv)
2133
2134 def DeleteRef(self, name, old=None):
2135 if not old:
2136 old = self.rev_parse(name)
2137 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002138 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002139
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002140 def rev_list(self, *args, **kw):
2141 if 'format' in kw:
2142 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2143 else:
2144 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002145 cmdv.extend(args)
2146 p = GitCommand(self._project,
2147 cmdv,
2148 bare = self._bare,
2149 capture_stdout = True,
2150 capture_stderr = True)
2151 r = []
2152 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002153 if line[-1] == '\n':
2154 line = line[:-1]
2155 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002156 if p.Wait() != 0:
2157 raise GitError('%s rev-list %s: %s' % (
2158 self._project.name,
2159 str(args),
2160 p.stderr))
2161 return r
2162
2163 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002164 """Allow arbitrary git commands using pythonic syntax.
2165
2166 This allows you to do things like:
2167 git_obj.rev_parse('HEAD')
2168
2169 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2170 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002171 Any other positional arguments will be passed to the git command, and the
2172 following keyword arguments are supported:
2173 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002174
2175 Args:
2176 name: The name of the git command to call. Any '_' characters will
2177 be replaced with '-'.
2178
2179 Returns:
2180 A callable object that will try to call git with the named command.
2181 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002182 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002183 def runner(*args, **kwargs):
2184 cmdv = []
2185 config = kwargs.pop('config', None)
2186 for k in kwargs:
2187 raise TypeError('%s() got an unexpected keyword argument %r'
2188 % (name, k))
2189 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002190 if not git_require((1, 7, 2)):
2191 raise ValueError('cannot set config on command line for %s()'
2192 % name)
Dave Borowitz091f8932012-10-23 17:01:04 -07002193 for k, v in config.iteritems():
2194 cmdv.append('-c')
2195 cmdv.append('%s=%s' % (k, v))
2196 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002197 cmdv.extend(args)
2198 p = GitCommand(self._project,
2199 cmdv,
2200 bare = self._bare,
2201 capture_stdout = True,
2202 capture_stderr = True)
2203 if p.Wait() != 0:
2204 raise GitError('%s %s: %s' % (
2205 self._project.name,
2206 name,
2207 p.stderr))
2208 r = p.stdout
2209 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2210 return r[:-1]
2211 return r
2212 return runner
2213
2214
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002215class _PriorSyncFailedError(Exception):
2216 def __str__(self):
2217 return 'prior sync failed; rebase still in progress'
2218
2219class _DirtyError(Exception):
2220 def __str__(self):
2221 return 'contains uncommitted changes'
2222
2223class _InfoMessage(object):
2224 def __init__(self, project, text):
2225 self.project = project
2226 self.text = text
2227
2228 def Print(self, syncbuf):
2229 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2230 syncbuf.out.nl()
2231
2232class _Failure(object):
2233 def __init__(self, project, why):
2234 self.project = project
2235 self.why = why
2236
2237 def Print(self, syncbuf):
2238 syncbuf.out.fail('error: %s/: %s',
2239 self.project.relpath,
2240 str(self.why))
2241 syncbuf.out.nl()
2242
2243class _Later(object):
2244 def __init__(self, project, action):
2245 self.project = project
2246 self.action = action
2247
2248 def Run(self, syncbuf):
2249 out = syncbuf.out
2250 out.project('project %s/', self.project.relpath)
2251 out.nl()
2252 try:
2253 self.action()
2254 out.nl()
2255 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002256 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002257 out.nl()
2258 return False
2259
2260class _SyncColoring(Coloring):
2261 def __init__(self, config):
2262 Coloring.__init__(self, config, 'reposync')
2263 self.project = self.printer('header', attr = 'bold')
2264 self.info = self.printer('info')
2265 self.fail = self.printer('fail', fg='red')
2266
2267class SyncBuffer(object):
2268 def __init__(self, config, detach_head=False):
2269 self._messages = []
2270 self._failures = []
2271 self._later_queue1 = []
2272 self._later_queue2 = []
2273
2274 self.out = _SyncColoring(config)
2275 self.out.redirect(sys.stderr)
2276
2277 self.detach_head = detach_head
2278 self.clean = True
2279
2280 def info(self, project, fmt, *args):
2281 self._messages.append(_InfoMessage(project, fmt % args))
2282
2283 def fail(self, project, err=None):
2284 self._failures.append(_Failure(project, err))
2285 self.clean = False
2286
2287 def later1(self, project, what):
2288 self._later_queue1.append(_Later(project, what))
2289
2290 def later2(self, project, what):
2291 self._later_queue2.append(_Later(project, what))
2292
2293 def Finish(self):
2294 self._PrintMessages()
2295 self._RunLater()
2296 self._PrintMessages()
2297 return self.clean
2298
2299 def _RunLater(self):
2300 for q in ['_later_queue1', '_later_queue2']:
2301 if not self._RunQueue(q):
2302 return
2303
2304 def _RunQueue(self, queue):
2305 for m in getattr(self, queue):
2306 if not m.Run(self):
2307 self.clean = False
2308 return False
2309 setattr(self, queue, [])
2310 return True
2311
2312 def _PrintMessages(self):
2313 for m in self._messages:
2314 m.Print(self)
2315 for m in self._failures:
2316 m.Print(self)
2317
2318 self._messages = []
2319 self._failures = []
2320
2321
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002322class MetaProject(Project):
2323 """A special project housed under .repo.
2324 """
2325 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002326 Project.__init__(self,
2327 manifest = manifest,
2328 name = name,
2329 gitdir = gitdir,
2330 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002331 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002332 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002333 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002334 revisionId = None,
2335 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002336
2337 def PreSync(self):
2338 if self.Exists:
2339 cb = self.CurrentBranch
2340 if cb:
2341 base = self.GetBranch(cb).merge
2342 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002343 self.revisionExpr = base
2344 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002345
Florian Vallee5d016502012-06-07 17:19:26 +02002346 def MetaBranchSwitch(self, target):
2347 """ Prepare MetaProject for manifest branch switch
2348 """
2349
2350 # detach and delete manifest branch, allowing a new
2351 # branch to take over
2352 syncbuf = SyncBuffer(self.config, detach_head = True)
2353 self.Sync_LocalHalf(syncbuf)
2354 syncbuf.Finish()
2355
2356 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002357 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002358 capture_stdout = True,
2359 capture_stderr = True).Wait() == 0
2360
2361
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002362 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002363 def LastFetch(self):
2364 try:
2365 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2366 return os.path.getmtime(fh)
2367 except OSError:
2368 return 0
2369
2370 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002371 def HasChanges(self):
2372 """Has the remote received new commits not yet checked out?
2373 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002374 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002375 return False
2376
David Pursehouse8a68ff92012-09-24 12:15:13 +09002377 all_refs = self.bare_ref.all
2378 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002379 head = self.work_git.GetHead()
2380 if head.startswith(R_HEADS):
2381 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002382 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002383 except KeyError:
2384 head = None
2385
2386 if revid == head:
2387 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002388 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002389 return True
2390 return False