blob: 792ad4c43639ed60c819f4aa20500ccc866eca2c [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.
Colin Cross5acde752012-03-28 20:15:45 -0700675 """
Conley Owensbb1b5f52012-08-13 13:11:18 -0700676 expanded_manifest_groups = manifest_groups or ['all', '-notdefault']
677 expanded_project_groups = ['all'] + (self.groups or [])
678
Conley Owens971de8e2012-04-16 10:36:08 -0700679 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700680 for group in expanded_manifest_groups:
681 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700682 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700683 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700684 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700685
Conley Owens971de8e2012-04-16 10:36:08 -0700686 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700687
688## Status Display ##
689
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500690 def HasChanges(self):
691 """Returns true if there are uncommitted changes.
692 """
693 self.work_git.update_index('-q',
694 '--unmerged',
695 '--ignore-missing',
696 '--refresh')
697 if self.IsRebaseInProgress():
698 return True
699
700 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
701 return True
702
703 if self.work_git.DiffZ('diff-files'):
704 return True
705
706 if self.work_git.LsOthers():
707 return True
708
709 return False
710
Terence Haddock4655e812011-03-31 12:33:34 +0200711 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200713
714 Args:
715 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700716 """
717 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200718 if output_redir == None:
719 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700720 print(file=output_redir)
721 print('project %s/' % self.relpath, file=output_redir)
722 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700723 return
724
725 self.work_git.update_index('-q',
726 '--unmerged',
727 '--ignore-missing',
728 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700729 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700730 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
731 df = self.work_git.DiffZ('diff-files')
732 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100733 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700734 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735
736 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200737 if not output_redir == None:
738 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700739 out.project('project %-40s', self.relpath + '/')
740
741 branch = self.CurrentBranch
742 if branch is None:
743 out.nobranch('(*** NO BRANCH ***)')
744 else:
745 out.branch('branch %s', branch)
746 out.nl()
747
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700748 if rb:
749 out.important('prior sync failed; rebase still in progress')
750 out.nl()
751
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700752 paths = list()
753 paths.extend(di.keys())
754 paths.extend(df.keys())
755 paths.extend(do)
756
757 paths = list(set(paths))
758 paths.sort()
759
760 for p in paths:
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900761 try:
762 i = di[p]
763 except KeyError:
764 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700765
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900766 try:
767 f = df[p]
768 except KeyError:
769 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200770
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900771 if i:
772 i_status = i.status.upper()
773 else:
774 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900776 if f:
777 f_status = f.status.lower()
778 else:
779 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780
781 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800782 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700783 i.src_path, p, i.level)
784 else:
785 line = ' %s%s\t%s' % (i_status, f_status, p)
786
787 if i and not f:
788 out.added('%s', line)
789 elif (i and f) or (not i and f):
790 out.changed('%s', line)
791 elif not i and not f:
792 out.untracked('%s', line)
793 else:
794 out.write('%s', line)
795 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200796
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700797 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700798
pelyad67872d2012-03-28 14:49:58 +0300799 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700800 """Prints the status of the repository to stdout.
801 """
802 out = DiffColoring(self.config)
803 cmd = ['diff']
804 if out.is_on:
805 cmd.append('--color')
806 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300807 if absolute_paths:
808 cmd.append('--src-prefix=a/%s/' % self.relpath)
809 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810 cmd.append('--')
811 p = GitCommand(self,
812 cmd,
813 capture_stdout = True,
814 capture_stderr = True)
815 has_diff = False
816 for line in p.process.stdout:
817 if not has_diff:
818 out.nl()
819 out.project('project %s/' % self.relpath)
820 out.nl()
821 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700822 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700823 p.Wait()
824
825
826## Publish / Upload ##
827
David Pursehouse8a68ff92012-09-24 12:15:13 +0900828 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829 """Was the branch published (uploaded) for code review?
830 If so, returns the SHA-1 hash of the last published
831 state for the branch.
832 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700833 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900834 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700835 try:
836 return self.bare_git.rev_parse(key)
837 except GitError:
838 return None
839 else:
840 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900841 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700842 except KeyError:
843 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844
David Pursehouse8a68ff92012-09-24 12:15:13 +0900845 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 """Prunes any stale published refs.
847 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900848 if all_refs is None:
849 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700850 heads = set()
851 canrm = {}
David Pursehouse8a68ff92012-09-24 12:15:13 +0900852 for name, ref_id in all_refs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853 if name.startswith(R_HEADS):
854 heads.add(name)
855 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900856 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857
David Pursehouse8a68ff92012-09-24 12:15:13 +0900858 for name, ref_id in canrm.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700859 n = name[len(R_PUB):]
860 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900861 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700863 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700864 """List any branches which can be uploaded for review.
865 """
866 heads = {}
867 pubed = {}
868
David Pursehouse8a68ff92012-09-24 12:15:13 +0900869 for name, ref_id in self._allrefs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700870 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900871 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900873 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874
875 ready = []
David Pursehouse8a68ff92012-09-24 12:15:13 +0900876 for branch, ref_id in heads.iteritems():
877 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700879 if selected_branch and branch != selected_branch:
880 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800882 rb = self.GetUploadableBranch(branch)
883 if rb:
884 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700885 return ready
886
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800887 def GetUploadableBranch(self, branch_name):
888 """Get a single uploadable branch, or None.
889 """
890 branch = self.GetBranch(branch_name)
891 base = branch.LocalMerge
892 if branch.LocalMerge:
893 rb = ReviewableBranch(self, branch, base)
894 if rb.commits:
895 return rb
896 return None
897
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700898 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700899 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700900 auto_topic=False,
901 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902 """Uploads the named branch for code review.
903 """
904 if branch is None:
905 branch = self.CurrentBranch
906 if branch is None:
907 raise GitError('not currently on a branch')
908
909 branch = self.GetBranch(branch)
910 if not branch.LocalMerge:
911 raise GitError('branch %s does not track a remote' % branch.name)
912 if not branch.remote.review:
913 raise GitError('remote %s has no review url' % branch.remote.name)
914
915 dest_branch = branch.merge
916 if not dest_branch.startswith(R_HEADS):
917 dest_branch = R_HEADS + dest_branch
918
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800919 if not branch.remote.projectname:
920 branch.remote.projectname = self.name
921 branch.remote.Save()
922
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800923 url = branch.remote.ReviewUrl(self.UserEmail)
924 if url is None:
925 raise UploadError('review not configured')
926 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800927
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800928 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800929 rp = ['gerrit receive-pack']
930 for e in people[0]:
931 rp.append('--reviewer=%s' % sq(e))
932 for e in people[1]:
933 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800934 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700935
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800936 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800937
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800938 if dest_branch.startswith(R_HEADS):
939 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700940
941 upload_type = 'for'
942 if draft:
943 upload_type = 'drafts'
944
945 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
946 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800947 if auto_topic:
948 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800949 if not url.startswith('ssh://'):
950 rp = ['r=%s' % p for p in people[0]] + \
951 ['cc=%s' % p for p in people[1]]
952 if rp:
953 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800954 cmd.append(ref_spec)
955
956 if GitCommand(self, cmd, bare = True).Wait() != 0:
957 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958
959 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
960 self.bare_git.UpdateRef(R_PUB + branch.name,
961 R_HEADS + branch.name,
962 message = msg)
963
964
965## Sync ##
966
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700967 def Sync_NetworkHalf(self,
968 quiet=False,
969 is_new=None,
970 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700971 clone_bundle=True,
972 no_tags=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973 """Perform only the network IO portion of the sync process.
974 Local working directory/branch state is not affected.
975 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700976 if is_new is None:
977 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200978 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +0200980 else:
981 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700983
984 if is_new:
985 alt = os.path.join(self.gitdir, 'objects/info/alternates')
986 try:
987 fd = open(alt, 'rb')
988 try:
989 alt_dir = fd.readline().rstrip()
990 finally:
991 fd.close()
992 except IOError:
993 alt_dir = None
994 else:
995 alt_dir = None
996
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700997 if clone_bundle \
998 and alt_dir is None \
999 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001000 is_new = False
1001
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001002 if not current_branch_only:
1003 if self.sync_c:
1004 current_branch_only = True
1005 elif not self.manifest._loaded:
1006 # Manifest cannot check defaults until it syncs.
1007 current_branch_only = False
1008 elif self.manifest.default.sync_c:
1009 current_branch_only = True
1010
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001011 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001012 current_branch_only=current_branch_only,
1013 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001014 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001015
1016 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001017 self._InitMRef()
1018 else:
1019 self._InitMirrorHead()
1020 try:
1021 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1022 except OSError:
1023 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001024 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001025
1026 def PostRepoUpgrade(self):
1027 self._InitHooks()
1028
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001029 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001030 for copyfile in self.copyfiles:
1031 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001032
David Pursehouse8a68ff92012-09-24 12:15:13 +09001033 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001034 if self.revisionId:
1035 return self.revisionId
1036
1037 rem = self.GetRemote(self.remote.name)
1038 rev = rem.ToLocal(self.revisionExpr)
1039
David Pursehouse8a68ff92012-09-24 12:15:13 +09001040 if all_refs is not None and rev in all_refs:
1041 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001042
1043 try:
1044 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1045 except GitError:
1046 raise ManifestInvalidRevisionError(
1047 'revision %s in %s not found' % (self.revisionExpr,
1048 self.name))
1049
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001050 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 """Perform only the local IO portion of the sync process.
1052 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001054 all_refs = self.bare_ref.all
1055 self.CleanPublishedCache(all_refs)
1056 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001057
David Pursehouse1d947b32012-10-25 12:23:11 +09001058 def _doff():
1059 self._FastForward(revid)
1060 self._CopyFiles()
1061
Skyler Kaufman835cd682011-03-08 12:14:41 -08001062 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001063 head = self.work_git.GetHead()
1064 if head.startswith(R_HEADS):
1065 branch = head[len(R_HEADS):]
1066 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001067 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001068 except KeyError:
1069 head = None
1070 else:
1071 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001073 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074 # Currently on a detached HEAD. The user is assumed to
1075 # not have any local modifications worth worrying about.
1076 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001077 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001078 syncbuf.fail(self, _PriorSyncFailedError())
1079 return
1080
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001081 if head == revid:
1082 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001083 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001084 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001085 if not syncbuf.detach_head:
1086 return
1087 else:
1088 lost = self._revlist(not_rev(revid), HEAD)
1089 if lost:
1090 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001091
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001092 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001093 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001094 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001095 syncbuf.fail(self, e)
1096 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001098 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001099
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001100 if head == revid:
1101 # No changes; don't do anything further.
1102 #
1103 return
1104
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001105 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001107 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001109 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001110 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001111 syncbuf.info(self,
1112 "leaving %s; does not track upstream",
1113 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001114 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001115 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001116 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001117 syncbuf.fail(self, e)
1118 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001120 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001122 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001123 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001124 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001125 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126 if not_merged:
1127 if upstream_gain:
1128 # The user has published this branch and some of those
1129 # commits are not yet merged upstream. We do not want
1130 # to rewrite the published commits so we punt.
1131 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001132 syncbuf.fail(self,
1133 "branch %s is published (but not merged) and is now %d commits behind"
1134 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001135 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001136 elif pub == head:
1137 # All published commits are merged, and thus we are a
1138 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001139 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001140 syncbuf.later1(self, _doff)
1141 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001142
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001143 # Examine the local commits not in the remote. Find the
1144 # last one attributed to this user, if any.
1145 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001146 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001147 last_mine = None
1148 cnt_mine = 0
1149 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001150 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001151 if committer_email == self.UserEmail:
1152 last_mine = commit_id
1153 cnt_mine += 1
1154
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001155 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001156 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001157
1158 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001159 syncbuf.fail(self, _DirtyError())
1160 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001161
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001162 # If the upstream switched on us, warn the user.
1163 #
1164 if branch.merge != self.revisionExpr:
1165 if branch.merge and self.revisionExpr:
1166 syncbuf.info(self,
1167 'manifest switched %s...%s',
1168 branch.merge,
1169 self.revisionExpr)
1170 elif branch.merge:
1171 syncbuf.info(self,
1172 'manifest no longer tracks %s',
1173 branch.merge)
1174
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001175 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001177 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001178 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001179 syncbuf.info(self,
1180 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001181 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001182
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001183 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001184 if not ID_RE.match(self.revisionExpr):
1185 # in case of manifest sync the revisionExpr might be a SHA1
1186 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187 branch.Save()
1188
Mike Pontillod3153822012-02-28 11:53:24 -08001189 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001190 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001191 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001192 self._CopyFiles()
1193 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001194 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001196 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001197 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001198 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001199 syncbuf.fail(self, e)
1200 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001202 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001203
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001204 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001205 # dest should already be an absolute path, but src is project relative
1206 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001207 abssrc = os.path.join(self.worktree, src)
1208 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001209
James W. Mills24c13082012-04-12 15:04:13 -05001210 def AddAnnotation(self, name, value, keep):
1211 self.annotations.append(_Annotation(name, value, keep))
1212
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001213 def DownloadPatchSet(self, change_id, patch_id):
1214 """Download a single patch set of a single change to FETCH_HEAD.
1215 """
1216 remote = self.GetRemote(self.remote.name)
1217
1218 cmd = ['fetch', remote.name]
1219 cmd.append('refs/changes/%2.2d/%d/%d' \
1220 % (change_id % 100, change_id, patch_id))
David Pursehouse7e6dd2d2012-10-25 12:40:51 +09001221 cmd.extend(map(str, remote.fetch))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001222 if GitCommand(self, cmd, bare=True).Wait() != 0:
1223 return None
1224 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001225 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001226 change_id,
1227 patch_id,
1228 self.bare_git.rev_parse('FETCH_HEAD'))
1229
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001230
1231## Branch Management ##
1232
1233 def StartBranch(self, name):
1234 """Create a new branch off the manifest's revision.
1235 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001236 head = self.work_git.GetHead()
1237 if head == (R_HEADS + name):
1238 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001239
David Pursehouse8a68ff92012-09-24 12:15:13 +09001240 all_refs = self.bare_ref.all
1241 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001242 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001243 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001244 capture_stdout = True,
1245 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001246
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001247 branch = self.GetBranch(name)
1248 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001249 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001250 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001251
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001252 if head.startswith(R_HEADS):
1253 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001254 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001255 except KeyError:
1256 head = None
1257
1258 if revid and head and revid == head:
1259 ref = os.path.join(self.gitdir, R_HEADS + name)
1260 try:
1261 os.makedirs(os.path.dirname(ref))
1262 except OSError:
1263 pass
1264 _lwrite(ref, '%s\n' % revid)
1265 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1266 'ref: %s%s\n' % (R_HEADS, name))
1267 branch.Save()
1268 return True
1269
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001270 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001271 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001272 capture_stdout = True,
1273 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001274 branch.Save()
1275 return True
1276 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001277
Wink Saville02d79452009-04-10 13:01:24 -07001278 def CheckoutBranch(self, name):
1279 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001280
1281 Args:
1282 name: The name of the branch to checkout.
1283
1284 Returns:
1285 True if the checkout succeeded; False if it didn't; None if the branch
1286 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001287 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001288 rev = R_HEADS + name
1289 head = self.work_git.GetHead()
1290 if head == rev:
1291 # Already on the branch
1292 #
1293 return True
Wink Saville02d79452009-04-10 13:01:24 -07001294
David Pursehouse8a68ff92012-09-24 12:15:13 +09001295 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001296 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001297 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001298 except KeyError:
1299 # Branch does not exist in this project
1300 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001301 return None
Wink Saville02d79452009-04-10 13:01:24 -07001302
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001303 if head.startswith(R_HEADS):
1304 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001305 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001306 except KeyError:
1307 head = None
1308
1309 if head == revid:
1310 # Same revision; just update HEAD to point to the new
1311 # target branch, but otherwise take no other action.
1312 #
1313 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1314 'ref: %s%s\n' % (R_HEADS, name))
1315 return True
1316
1317 return GitCommand(self,
1318 ['checkout', name, '--'],
1319 capture_stdout = True,
1320 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001321
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001322 def AbandonBranch(self, name):
1323 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001324
1325 Args:
1326 name: The name of the branch to abandon.
1327
1328 Returns:
1329 True if the abandon succeeded; False if it didn't; None if the branch
1330 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001331 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001332 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001333 all_refs = self.bare_ref.all
1334 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001335 # Doesn't exist
1336 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001337
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001338 head = self.work_git.GetHead()
1339 if head == rev:
1340 # We can't destroy the branch while we are sitting
1341 # on it. Switch to a detached HEAD.
1342 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001343 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001344
David Pursehouse8a68ff92012-09-24 12:15:13 +09001345 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001346 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001347 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1348 '%s\n' % revid)
1349 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001350 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001351
1352 return GitCommand(self,
1353 ['branch', '-D', name],
1354 capture_stdout = True,
1355 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001356
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357 def PruneHeads(self):
1358 """Prune any topic branches already merged into upstream.
1359 """
1360 cb = self.CurrentBranch
1361 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001362 left = self._allrefs
1363 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 if name.startswith(R_HEADS):
1365 name = name[len(R_HEADS):]
1366 if cb is None or name != cb:
1367 kill.append(name)
1368
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001369 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001370 if cb is not None \
1371 and not self._revlist(HEAD + '...' + rev) \
1372 and not self.IsDirty(consider_untracked = False):
1373 self.work_git.DetachHead(HEAD)
1374 kill.append(cb)
1375
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001377 old = self.bare_git.GetHead()
1378 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001379 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1380
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381 try:
1382 self.bare_git.DetachHead(rev)
1383
1384 b = ['branch', '-d']
1385 b.extend(kill)
1386 b = GitCommand(self, b, bare=True,
1387 capture_stdout=True,
1388 capture_stderr=True)
1389 b.Wait()
1390 finally:
1391 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001392 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001394 for branch in kill:
1395 if (R_HEADS + branch) not in left:
1396 self.CleanPublishedCache()
1397 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398
1399 if cb and cb not in kill:
1400 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001401 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001402
1403 kept = []
1404 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001405 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001406 branch = self.GetBranch(branch)
1407 base = branch.LocalMerge
1408 if not base:
1409 base = rev
1410 kept.append(ReviewableBranch(self, branch, base))
1411 return kept
1412
1413
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001414## Submodule Management ##
1415
1416 def GetRegisteredSubprojects(self):
1417 result = []
1418 def rec(subprojects):
1419 if not subprojects:
1420 return
1421 result.extend(subprojects)
1422 for p in subprojects:
1423 rec(p.subprojects)
1424 rec(self.subprojects)
1425 return result
1426
1427 def _GetSubmodules(self):
1428 # Unfortunately we cannot call `git submodule status --recursive` here
1429 # because the working tree might not exist yet, and it cannot be used
1430 # without a working tree in its current implementation.
1431
1432 def get_submodules(gitdir, rev):
1433 # Parse .gitmodules for submodule sub_paths and sub_urls
1434 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1435 if not sub_paths:
1436 return []
1437 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1438 # revision of submodule repository
1439 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1440 submodules = []
1441 for sub_path, sub_url in zip(sub_paths, sub_urls):
1442 try:
1443 sub_rev = sub_revs[sub_path]
1444 except KeyError:
1445 # Ignore non-exist submodules
1446 continue
1447 submodules.append((sub_rev, sub_path, sub_url))
1448 return submodules
1449
1450 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1451 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1452 def parse_gitmodules(gitdir, rev):
1453 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1454 try:
1455 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1456 bare = True, gitdir = gitdir)
1457 except GitError:
1458 return [], []
1459 if p.Wait() != 0:
1460 return [], []
1461
1462 gitmodules_lines = []
1463 fd, temp_gitmodules_path = tempfile.mkstemp()
1464 try:
1465 os.write(fd, p.stdout)
1466 os.close(fd)
1467 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1468 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1469 bare = True, gitdir = gitdir)
1470 if p.Wait() != 0:
1471 return [], []
1472 gitmodules_lines = p.stdout.split('\n')
1473 except GitError:
1474 return [], []
1475 finally:
1476 os.remove(temp_gitmodules_path)
1477
1478 names = set()
1479 paths = {}
1480 urls = {}
1481 for line in gitmodules_lines:
1482 if not line:
1483 continue
1484 m = re_path.match(line)
1485 if m:
1486 names.add(m.group(1))
1487 paths[m.group(1)] = m.group(2)
1488 continue
1489 m = re_url.match(line)
1490 if m:
1491 names.add(m.group(1))
1492 urls[m.group(1)] = m.group(2)
1493 continue
1494 names = sorted(names)
1495 return ([paths.get(name, '') for name in names],
1496 [urls.get(name, '') for name in names])
1497
1498 def git_ls_tree(gitdir, rev, paths):
1499 cmd = ['ls-tree', rev, '--']
1500 cmd.extend(paths)
1501 try:
1502 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1503 bare = True, gitdir = gitdir)
1504 except GitError:
1505 return []
1506 if p.Wait() != 0:
1507 return []
1508 objects = {}
1509 for line in p.stdout.split('\n'):
1510 if not line.strip():
1511 continue
1512 object_rev, object_path = line.split()[2:4]
1513 objects[object_path] = object_rev
1514 return objects
1515
1516 try:
1517 rev = self.GetRevisionId()
1518 except GitError:
1519 return []
1520 return get_submodules(self.gitdir, rev)
1521
1522 def GetDerivedSubprojects(self):
1523 result = []
1524 if not self.Exists:
1525 # If git repo does not exist yet, querying its submodules will
1526 # mess up its states; so return here.
1527 return result
1528 for rev, path, url in self._GetSubmodules():
1529 name = self.manifest.GetSubprojectName(self, path)
1530 project = self.manifest.projects.get(name)
1531 if project:
1532 result.extend(project.GetDerivedSubprojects())
1533 continue
1534 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1535 remote = RemoteSpec(self.remote.name,
1536 url = url,
1537 review = self.remote.review)
1538 subproject = Project(manifest = self.manifest,
1539 name = name,
1540 remote = remote,
1541 gitdir = gitdir,
1542 worktree = worktree,
1543 relpath = relpath,
1544 revisionExpr = self.revisionExpr,
1545 revisionId = rev,
1546 rebase = self.rebase,
1547 groups = self.groups,
1548 sync_c = self.sync_c,
1549 sync_s = self.sync_s,
1550 parent = self,
1551 is_derived = True)
1552 result.append(subproject)
1553 result.extend(subproject.GetDerivedSubprojects())
1554 return result
1555
1556
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001557## Direct Git Commands ##
1558
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001559 def _RemoteFetch(self, name=None,
1560 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001561 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001562 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001563 alt_dir=None,
1564 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001565
1566 is_sha1 = False
1567 tag_name = None
1568
Brian Harring14a66742012-09-28 20:21:57 -07001569 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001570 try:
1571 # if revision (sha or tag) is not present then following function
1572 # throws an error.
1573 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1574 return True
1575 except GitError:
1576 # There is no such persistent revision. We have to fetch it.
1577 return False
Brian Harring14a66742012-09-28 20:21:57 -07001578
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001579 if current_branch_only:
1580 if ID_RE.match(self.revisionExpr) is not None:
1581 is_sha1 = True
1582 elif self.revisionExpr.startswith(R_TAGS):
1583 # this is a tag and its sha1 value should never change
1584 tag_name = self.revisionExpr[len(R_TAGS):]
1585
1586 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001587 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001588 return True
Brian Harring14a66742012-09-28 20:21:57 -07001589 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1590 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001591
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001592 if not name:
1593 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001594
1595 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001596 remote = self.GetRemote(name)
1597 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001598 ssh_proxy = True
1599
Shawn O. Pearce88443382010-10-08 10:02:09 +02001600 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001601 if alt_dir and 'objects' == os.path.basename(alt_dir):
1602 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001603 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1604 remote = self.GetRemote(name)
1605
David Pursehouse8a68ff92012-09-24 12:15:13 +09001606 all_refs = self.bare_ref.all
1607 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001608 tmp = set()
1609
David Pursehouse8a68ff92012-09-24 12:15:13 +09001610 for r, ref_id in GitRefs(ref_dir).all.iteritems():
1611 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001612 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001613 all_refs[r] = ref_id
1614 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001615 continue
1616
David Pursehouse8a68ff92012-09-24 12:15:13 +09001617 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001618 continue
1619
David Pursehouse8a68ff92012-09-24 12:15:13 +09001620 r = 'refs/_alt/%s' % ref_id
1621 all_refs[r] = ref_id
1622 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001623 tmp.add(r)
1624
David Pursehouse8a68ff92012-09-24 12:15:13 +09001625 ref_names = list(all_refs.keys())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001626 ref_names.sort()
1627
1628 tmp_packed = ''
1629 old_packed = ''
1630
1631 for r in ref_names:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001632 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001633 tmp_packed += line
1634 if r not in tmp:
1635 old_packed += line
1636
1637 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001638 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001639 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001640
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001641 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001642
1643 # The --depth option only affects the initial fetch; after that we'll do
1644 # full fetches of changes.
1645 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1646 if depth and initial:
1647 cmd.append('--depth=%s' % depth)
1648
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001649 if quiet:
1650 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001651 if not self.worktree:
1652 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001653 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001654
Brian Harring14a66742012-09-28 20:21:57 -07001655 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001656 # Fetch whole repo
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001657 if no_tags:
1658 cmd.append('--no-tags')
1659 else:
1660 cmd.append('--tags')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001661 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1662 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001663 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001664 cmd.append(tag_name)
1665 else:
1666 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001667 if is_sha1:
1668 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001669 if branch.startswith(R_HEADS):
1670 branch = branch[len(R_HEADS):]
1671 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001672
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001673 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001674 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001675 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1676 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001677 ok = True
1678 break
Brian Harring14a66742012-09-28 20:21:57 -07001679 elif current_branch_only and is_sha1 and ret == 128:
1680 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1681 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1682 # abort the optimization attempt and do a full sync.
1683 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001684 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001685
1686 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001687 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001688 if old_packed != '':
1689 _lwrite(packed_refs, old_packed)
1690 else:
1691 os.remove(packed_refs)
1692 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001693
1694 if is_sha1 and current_branch_only and self.upstream:
1695 # We just synced the upstream given branch; verify we
1696 # got what we wanted, else trigger a second run of all
1697 # refs.
1698 if not CheckForSha1():
1699 return self._RemoteFetch(name=name, current_branch_only=False,
1700 initial=False, quiet=quiet, alt_dir=alt_dir)
1701
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001702 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001703
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001704 def _ApplyCloneBundle(self, initial=False, quiet=False):
1705 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1706 return False
1707
1708 remote = self.GetRemote(self.remote.name)
1709 bundle_url = remote.url + '/clone.bundle'
1710 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001711 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1712 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001713 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1714 return False
1715
1716 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1717 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1718
1719 exist_dst = os.path.exists(bundle_dst)
1720 exist_tmp = os.path.exists(bundle_tmp)
1721
1722 if not initial and not exist_dst and not exist_tmp:
1723 return False
1724
1725 if not exist_dst:
1726 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1727 if not exist_dst:
1728 return False
1729
1730 cmd = ['fetch']
1731 if quiet:
1732 cmd.append('--quiet')
1733 if not self.worktree:
1734 cmd.append('--update-head-ok')
1735 cmd.append(bundle_dst)
1736 for f in remote.fetch:
1737 cmd.append(str(f))
1738 cmd.append('refs/tags/*:refs/tags/*')
1739
1740 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001741 if os.path.exists(bundle_dst):
1742 os.remove(bundle_dst)
1743 if os.path.exists(bundle_tmp):
1744 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001745 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001746
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001747 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001748 if os.path.exists(dstPath):
1749 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001750
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001751 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001752 if quiet:
1753 cmd += ['--silent']
1754 if os.path.exists(tmpPath):
1755 size = os.stat(tmpPath).st_size
1756 if size >= 1024:
1757 cmd += ['--continue-at', '%d' % (size,)]
1758 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001759 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001760 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1761 cmd += ['--proxy', os.environ['http_proxy']]
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001762 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
1763 if cookiefile:
1764 cmd += ['--cookie', cookiefile]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001765 cmd += [srcUrl]
1766
1767 if IsTrace():
1768 Trace('%s', ' '.join(cmd))
1769 try:
1770 proc = subprocess.Popen(cmd)
1771 except OSError:
1772 return False
1773
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001774 curlret = proc.wait()
1775
1776 if curlret == 22:
1777 # From curl man page:
1778 # 22: HTTP page not retrieved. The requested url was not found or
1779 # returned another error with the HTTP error code being 400 or above.
1780 # This return code only appears if -f, --fail is used.
1781 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001782 print("Server does not provide clone.bundle; ignoring.",
1783 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001784 return False
1785
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001786 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001787 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001788 os.rename(tmpPath, dstPath)
1789 return True
1790 else:
1791 os.remove(tmpPath)
1792 return False
1793 else:
1794 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001795
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001796 def _Checkout(self, rev, quiet=False):
1797 cmd = ['checkout']
1798 if quiet:
1799 cmd.append('-q')
1800 cmd.append(rev)
1801 cmd.append('--')
1802 if GitCommand(self, cmd).Wait() != 0:
1803 if self._allrefs:
1804 raise GitError('%s checkout %s ' % (self.name, rev))
1805
Pierre Tardye5a21222011-03-24 16:28:18 +01001806 def _CherryPick(self, rev, quiet=False):
1807 cmd = ['cherry-pick']
1808 cmd.append(rev)
1809 cmd.append('--')
1810 if GitCommand(self, cmd).Wait() != 0:
1811 if self._allrefs:
1812 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1813
Erwan Mahea94f1622011-08-19 13:56:09 +02001814 def _Revert(self, rev, quiet=False):
1815 cmd = ['revert']
1816 cmd.append('--no-edit')
1817 cmd.append(rev)
1818 cmd.append('--')
1819 if GitCommand(self, cmd).Wait() != 0:
1820 if self._allrefs:
1821 raise GitError('%s revert %s ' % (self.name, rev))
1822
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001823 def _ResetHard(self, rev, quiet=True):
1824 cmd = ['reset', '--hard']
1825 if quiet:
1826 cmd.append('-q')
1827 cmd.append(rev)
1828 if GitCommand(self, cmd).Wait() != 0:
1829 raise GitError('%s reset --hard %s ' % (self.name, rev))
1830
1831 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001832 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001833 if onto is not None:
1834 cmd.extend(['--onto', onto])
1835 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001836 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001837 raise GitError('%s rebase %s ' % (self.name, upstream))
1838
Pierre Tardy3d125942012-05-04 12:18:12 +02001839 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001840 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001841 if ffonly:
1842 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001843 if GitCommand(self, cmd).Wait() != 0:
1844 raise GitError('%s merge %s ' % (self.name, head))
1845
1846 def _InitGitDir(self):
1847 if not os.path.exists(self.gitdir):
1848 os.makedirs(self.gitdir)
1849 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001850
Shawn O. Pearce88443382010-10-08 10:02:09 +02001851 mp = self.manifest.manifestProject
1852 ref_dir = mp.config.GetString('repo.reference')
1853
1854 if ref_dir:
1855 mirror_git = os.path.join(ref_dir, self.name + '.git')
1856 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1857 self.relpath + '.git')
1858
1859 if os.path.exists(mirror_git):
1860 ref_dir = mirror_git
1861
1862 elif os.path.exists(repo_git):
1863 ref_dir = repo_git
1864
1865 else:
1866 ref_dir = None
1867
1868 if ref_dir:
1869 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1870 os.path.join(ref_dir, 'objects') + '\n')
1871
Jimmie Westera0444582012-10-24 13:44:42 +02001872 self._UpdateHooks()
1873
1874 m = self.manifest.manifestProject.config
1875 for key in ['user.name', 'user.email']:
1876 if m.Has(key, include_defaults = False):
1877 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001878 if self.manifest.IsMirror:
1879 self.config.SetString('core.bare', 'true')
1880 else:
1881 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001882
Jimmie Westera0444582012-10-24 13:44:42 +02001883 def _UpdateHooks(self):
1884 if os.path.exists(self.gitdir):
1885 # Always recreate hooks since they can have been changed
1886 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001887 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001888 try:
1889 to_rm = os.listdir(hooks)
1890 except OSError:
1891 to_rm = []
1892 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001893 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001894 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001895
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001896 def _InitHooks(self):
1897 hooks = self._gitdir_path('hooks')
1898 if not os.path.exists(hooks):
1899 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001900 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001901 name = os.path.basename(stock_hook)
1902
Victor Boivie65e0f352011-04-18 11:23:29 +02001903 if name in ('commit-msg',) and not self.remote.review \
1904 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001905 # Don't install a Gerrit Code Review hook if this
1906 # project does not appear to use it for reviews.
1907 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001908 # Since the manifest project is one of those, but also
1909 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001910 continue
1911
1912 dst = os.path.join(hooks, name)
1913 if os.path.islink(dst):
1914 continue
1915 if os.path.exists(dst):
1916 if filecmp.cmp(stock_hook, dst, shallow=False):
1917 os.remove(dst)
1918 else:
1919 _error("%s: Not replacing %s hook", self.relpath, name)
1920 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001921 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001922 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001923 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001924 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001925 raise GitError('filesystem must support symlinks')
1926 else:
1927 raise
1928
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001929 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001930 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001931 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001932 remote.url = self.remote.url
1933 remote.review = self.remote.review
1934 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001935
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001936 if self.worktree:
1937 remote.ResetFetch(mirror=False)
1938 else:
1939 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001940 remote.Save()
1941
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001942 def _InitMRef(self):
1943 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001944 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001945
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001946 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001947 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001948
1949 def _InitAnyMRef(self, ref):
1950 cur = self.bare_ref.symref(ref)
1951
1952 if self.revisionId:
1953 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1954 msg = 'manifest set to %s' % self.revisionId
1955 dst = self.revisionId + '^0'
1956 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1957 else:
1958 remote = self.GetRemote(self.remote.name)
1959 dst = remote.ToLocal(self.revisionExpr)
1960 if cur != dst:
1961 msg = 'manifest set to %s' % self.revisionExpr
1962 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001963
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001964 def _InitWorkTree(self):
1965 dotgit = os.path.join(self.worktree, '.git')
1966 if not os.path.exists(dotgit):
1967 os.makedirs(dotgit)
1968
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001969 for name in ['config',
1970 'description',
1971 'hooks',
1972 'info',
1973 'logs',
1974 'objects',
1975 'packed-refs',
1976 'refs',
1977 'rr-cache',
1978 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001979 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001980 src = os.path.join(self.gitdir, name)
1981 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001982 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001983 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001984 else:
1985 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001986 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001987 if e.errno == errno.EPERM:
1988 raise GitError('filesystem must support symlinks')
1989 else:
1990 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001991
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001992 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001993
1994 cmd = ['read-tree', '--reset', '-u']
1995 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001996 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001997 if GitCommand(self, cmd).Wait() != 0:
1998 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001999
2000 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2001 if not os.path.exists(rr_cache):
2002 os.makedirs(rr_cache)
2003
Shawn O. Pearce93609662009-04-21 10:50:33 -07002004 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002005
2006 def _gitdir_path(self, path):
2007 return os.path.join(self.gitdir, path)
2008
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002009 def _revlist(self, *args, **kw):
2010 a = []
2011 a.extend(args)
2012 a.append('--')
2013 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002014
2015 @property
2016 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002017 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002018
2019 class _GitGetByExec(object):
2020 def __init__(self, project, bare):
2021 self._project = project
2022 self._bare = bare
2023
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002024 def LsOthers(self):
2025 p = GitCommand(self._project,
2026 ['ls-files',
2027 '-z',
2028 '--others',
2029 '--exclude-standard'],
2030 bare = False,
2031 capture_stdout = True,
2032 capture_stderr = True)
2033 if p.Wait() == 0:
2034 out = p.stdout
2035 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002036 return out[:-1].split('\0') # pylint: disable=W1401
2037 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002038 return []
2039
2040 def DiffZ(self, name, *args):
2041 cmd = [name]
2042 cmd.append('-z')
2043 cmd.extend(args)
2044 p = GitCommand(self._project,
2045 cmd,
2046 bare = False,
2047 capture_stdout = True,
2048 capture_stderr = True)
2049 try:
2050 out = p.process.stdout.read()
2051 r = {}
2052 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002053 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002054 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002055 try:
2056 info = out.next()
2057 path = out.next()
2058 except StopIteration:
2059 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002060
2061 class _Info(object):
2062 def __init__(self, path, omode, nmode, oid, nid, state):
2063 self.path = path
2064 self.src_path = None
2065 self.old_mode = omode
2066 self.new_mode = nmode
2067 self.old_id = oid
2068 self.new_id = nid
2069
2070 if len(state) == 1:
2071 self.status = state
2072 self.level = None
2073 else:
2074 self.status = state[:1]
2075 self.level = state[1:]
2076 while self.level.startswith('0'):
2077 self.level = self.level[1:]
2078
2079 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002080 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002081 if info.status in ('R', 'C'):
2082 info.src_path = info.path
2083 info.path = out.next()
2084 r[info.path] = info
2085 return r
2086 finally:
2087 p.Wait()
2088
2089 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002090 if self._bare:
2091 path = os.path.join(self._project.gitdir, HEAD)
2092 else:
2093 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002094 try:
2095 fd = open(path, 'rb')
2096 except IOError:
2097 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002098 try:
2099 line = fd.read()
2100 finally:
2101 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002102 if line.startswith('ref: '):
2103 return line[5:-1]
2104 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002105
2106 def SetHead(self, ref, message=None):
2107 cmdv = []
2108 if message is not None:
2109 cmdv.extend(['-m', message])
2110 cmdv.append(HEAD)
2111 cmdv.append(ref)
2112 self.symbolic_ref(*cmdv)
2113
2114 def DetachHead(self, new, message=None):
2115 cmdv = ['--no-deref']
2116 if message is not None:
2117 cmdv.extend(['-m', message])
2118 cmdv.append(HEAD)
2119 cmdv.append(new)
2120 self.update_ref(*cmdv)
2121
2122 def UpdateRef(self, name, new, old=None,
2123 message=None,
2124 detach=False):
2125 cmdv = []
2126 if message is not None:
2127 cmdv.extend(['-m', message])
2128 if detach:
2129 cmdv.append('--no-deref')
2130 cmdv.append(name)
2131 cmdv.append(new)
2132 if old is not None:
2133 cmdv.append(old)
2134 self.update_ref(*cmdv)
2135
2136 def DeleteRef(self, name, old=None):
2137 if not old:
2138 old = self.rev_parse(name)
2139 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002140 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002141
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002142 def rev_list(self, *args, **kw):
2143 if 'format' in kw:
2144 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2145 else:
2146 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002147 cmdv.extend(args)
2148 p = GitCommand(self._project,
2149 cmdv,
2150 bare = self._bare,
2151 capture_stdout = True,
2152 capture_stderr = True)
2153 r = []
2154 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002155 if line[-1] == '\n':
2156 line = line[:-1]
2157 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002158 if p.Wait() != 0:
2159 raise GitError('%s rev-list %s: %s' % (
2160 self._project.name,
2161 str(args),
2162 p.stderr))
2163 return r
2164
2165 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002166 """Allow arbitrary git commands using pythonic syntax.
2167
2168 This allows you to do things like:
2169 git_obj.rev_parse('HEAD')
2170
2171 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2172 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002173 Any other positional arguments will be passed to the git command, and the
2174 following keyword arguments are supported:
2175 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002176
2177 Args:
2178 name: The name of the git command to call. Any '_' characters will
2179 be replaced with '-'.
2180
2181 Returns:
2182 A callable object that will try to call git with the named command.
2183 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002184 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002185 def runner(*args, **kwargs):
2186 cmdv = []
2187 config = kwargs.pop('config', None)
2188 for k in kwargs:
2189 raise TypeError('%s() got an unexpected keyword argument %r'
2190 % (name, k))
2191 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002192 if not git_require((1, 7, 2)):
2193 raise ValueError('cannot set config on command line for %s()'
2194 % name)
Dave Borowitz091f8932012-10-23 17:01:04 -07002195 for k, v in config.iteritems():
2196 cmdv.append('-c')
2197 cmdv.append('%s=%s' % (k, v))
2198 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002199 cmdv.extend(args)
2200 p = GitCommand(self._project,
2201 cmdv,
2202 bare = self._bare,
2203 capture_stdout = True,
2204 capture_stderr = True)
2205 if p.Wait() != 0:
2206 raise GitError('%s %s: %s' % (
2207 self._project.name,
2208 name,
2209 p.stderr))
2210 r = p.stdout
2211 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2212 return r[:-1]
2213 return r
2214 return runner
2215
2216
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002217class _PriorSyncFailedError(Exception):
2218 def __str__(self):
2219 return 'prior sync failed; rebase still in progress'
2220
2221class _DirtyError(Exception):
2222 def __str__(self):
2223 return 'contains uncommitted changes'
2224
2225class _InfoMessage(object):
2226 def __init__(self, project, text):
2227 self.project = project
2228 self.text = text
2229
2230 def Print(self, syncbuf):
2231 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2232 syncbuf.out.nl()
2233
2234class _Failure(object):
2235 def __init__(self, project, why):
2236 self.project = project
2237 self.why = why
2238
2239 def Print(self, syncbuf):
2240 syncbuf.out.fail('error: %s/: %s',
2241 self.project.relpath,
2242 str(self.why))
2243 syncbuf.out.nl()
2244
2245class _Later(object):
2246 def __init__(self, project, action):
2247 self.project = project
2248 self.action = action
2249
2250 def Run(self, syncbuf):
2251 out = syncbuf.out
2252 out.project('project %s/', self.project.relpath)
2253 out.nl()
2254 try:
2255 self.action()
2256 out.nl()
2257 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002258 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002259 out.nl()
2260 return False
2261
2262class _SyncColoring(Coloring):
2263 def __init__(self, config):
2264 Coloring.__init__(self, config, 'reposync')
2265 self.project = self.printer('header', attr = 'bold')
2266 self.info = self.printer('info')
2267 self.fail = self.printer('fail', fg='red')
2268
2269class SyncBuffer(object):
2270 def __init__(self, config, detach_head=False):
2271 self._messages = []
2272 self._failures = []
2273 self._later_queue1 = []
2274 self._later_queue2 = []
2275
2276 self.out = _SyncColoring(config)
2277 self.out.redirect(sys.stderr)
2278
2279 self.detach_head = detach_head
2280 self.clean = True
2281
2282 def info(self, project, fmt, *args):
2283 self._messages.append(_InfoMessage(project, fmt % args))
2284
2285 def fail(self, project, err=None):
2286 self._failures.append(_Failure(project, err))
2287 self.clean = False
2288
2289 def later1(self, project, what):
2290 self._later_queue1.append(_Later(project, what))
2291
2292 def later2(self, project, what):
2293 self._later_queue2.append(_Later(project, what))
2294
2295 def Finish(self):
2296 self._PrintMessages()
2297 self._RunLater()
2298 self._PrintMessages()
2299 return self.clean
2300
2301 def _RunLater(self):
2302 for q in ['_later_queue1', '_later_queue2']:
2303 if not self._RunQueue(q):
2304 return
2305
2306 def _RunQueue(self, queue):
2307 for m in getattr(self, queue):
2308 if not m.Run(self):
2309 self.clean = False
2310 return False
2311 setattr(self, queue, [])
2312 return True
2313
2314 def _PrintMessages(self):
2315 for m in self._messages:
2316 m.Print(self)
2317 for m in self._failures:
2318 m.Print(self)
2319
2320 self._messages = []
2321 self._failures = []
2322
2323
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002324class MetaProject(Project):
2325 """A special project housed under .repo.
2326 """
2327 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002328 Project.__init__(self,
2329 manifest = manifest,
2330 name = name,
2331 gitdir = gitdir,
2332 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002333 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002334 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002335 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002336 revisionId = None,
2337 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002338
2339 def PreSync(self):
2340 if self.Exists:
2341 cb = self.CurrentBranch
2342 if cb:
2343 base = self.GetBranch(cb).merge
2344 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002345 self.revisionExpr = base
2346 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002347
Florian Vallee5d016502012-06-07 17:19:26 +02002348 def MetaBranchSwitch(self, target):
2349 """ Prepare MetaProject for manifest branch switch
2350 """
2351
2352 # detach and delete manifest branch, allowing a new
2353 # branch to take over
2354 syncbuf = SyncBuffer(self.config, detach_head = True)
2355 self.Sync_LocalHalf(syncbuf)
2356 syncbuf.Finish()
2357
2358 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002359 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002360 capture_stdout = True,
2361 capture_stderr = True).Wait() == 0
2362
2363
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002364 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002365 def LastFetch(self):
2366 try:
2367 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2368 return os.path.getmtime(fh)
2369 except OSError:
2370 return 0
2371
2372 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002373 def HasChanges(self):
2374 """Has the remote received new commits not yet checked out?
2375 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002376 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002377 return False
2378
David Pursehouse8a68ff92012-09-24 12:15:13 +09002379 all_refs = self.bare_ref.all
2380 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002381 head = self.work_git.GetHead()
2382 if head.startswith(R_HEADS):
2383 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002384 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002385 except KeyError:
2386 head = None
2387
2388 if revid == head:
2389 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002390 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002391 return True
2392 return False