blob: 22e4a5d65f61daab3eae415b3e35d79b506ef797 [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()
980 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700981
982 if is_new:
983 alt = os.path.join(self.gitdir, 'objects/info/alternates')
984 try:
985 fd = open(alt, 'rb')
986 try:
987 alt_dir = fd.readline().rstrip()
988 finally:
989 fd.close()
990 except IOError:
991 alt_dir = None
992 else:
993 alt_dir = None
994
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700995 if clone_bundle \
996 and alt_dir is None \
997 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700998 is_new = False
999
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001000 if not current_branch_only:
1001 if self.sync_c:
1002 current_branch_only = True
1003 elif not self.manifest._loaded:
1004 # Manifest cannot check defaults until it syncs.
1005 current_branch_only = False
1006 elif self.manifest.default.sync_c:
1007 current_branch_only = True
1008
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001009 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001010 current_branch_only=current_branch_only,
1011 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001012 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001013
1014 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001015 self._InitMRef()
1016 else:
1017 self._InitMirrorHead()
1018 try:
1019 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1020 except OSError:
1021 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001023
1024 def PostRepoUpgrade(self):
1025 self._InitHooks()
1026
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001027 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001028 for copyfile in self.copyfiles:
1029 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001030
David Pursehouse8a68ff92012-09-24 12:15:13 +09001031 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001032 if self.revisionId:
1033 return self.revisionId
1034
1035 rem = self.GetRemote(self.remote.name)
1036 rev = rem.ToLocal(self.revisionExpr)
1037
David Pursehouse8a68ff92012-09-24 12:15:13 +09001038 if all_refs is not None and rev in all_refs:
1039 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001040
1041 try:
1042 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1043 except GitError:
1044 raise ManifestInvalidRevisionError(
1045 'revision %s in %s not found' % (self.revisionExpr,
1046 self.name))
1047
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001048 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001049 """Perform only the local IO portion of the sync process.
1050 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001052 all_refs = self.bare_ref.all
1053 self.CleanPublishedCache(all_refs)
1054 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001055
David Pursehouse1d947b32012-10-25 12:23:11 +09001056 def _doff():
1057 self._FastForward(revid)
1058 self._CopyFiles()
1059
Skyler Kaufman835cd682011-03-08 12:14:41 -08001060 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001061 head = self.work_git.GetHead()
1062 if head.startswith(R_HEADS):
1063 branch = head[len(R_HEADS):]
1064 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001065 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001066 except KeyError:
1067 head = None
1068 else:
1069 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001071 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 # Currently on a detached HEAD. The user is assumed to
1073 # not have any local modifications worth worrying about.
1074 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001075 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001076 syncbuf.fail(self, _PriorSyncFailedError())
1077 return
1078
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001079 if head == revid:
1080 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001081 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001082 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001083 if not syncbuf.detach_head:
1084 return
1085 else:
1086 lost = self._revlist(not_rev(revid), HEAD)
1087 if lost:
1088 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001089
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001091 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001092 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001093 syncbuf.fail(self, e)
1094 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001095 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001096 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001098 if head == revid:
1099 # No changes; don't do anything further.
1100 #
1101 return
1102
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001103 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001104
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001105 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001107 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001109 syncbuf.info(self,
1110 "leaving %s; does not track upstream",
1111 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001112 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001113 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001114 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001115 syncbuf.fail(self, e)
1116 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001118 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001120 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001121 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001122 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001123 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001124 if not_merged:
1125 if upstream_gain:
1126 # The user has published this branch and some of those
1127 # commits are not yet merged upstream. We do not want
1128 # to rewrite the published commits so we punt.
1129 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001130 syncbuf.fail(self,
1131 "branch %s is published (but not merged) and is now %d commits behind"
1132 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001133 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001134 elif pub == head:
1135 # All published commits are merged, and thus we are a
1136 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001137 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001138 syncbuf.later1(self, _doff)
1139 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001140
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001141 # Examine the local commits not in the remote. Find the
1142 # last one attributed to this user, if any.
1143 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001144 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001145 last_mine = None
1146 cnt_mine = 0
1147 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001148 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001149 if committer_email == self.UserEmail:
1150 last_mine = commit_id
1151 cnt_mine += 1
1152
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001153 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001154 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001155
1156 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001157 syncbuf.fail(self, _DirtyError())
1158 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001159
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001160 # If the upstream switched on us, warn the user.
1161 #
1162 if branch.merge != self.revisionExpr:
1163 if branch.merge and self.revisionExpr:
1164 syncbuf.info(self,
1165 'manifest switched %s...%s',
1166 branch.merge,
1167 self.revisionExpr)
1168 elif branch.merge:
1169 syncbuf.info(self,
1170 'manifest no longer tracks %s',
1171 branch.merge)
1172
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001173 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001174 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001175 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001177 syncbuf.info(self,
1178 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001179 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001180
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001181 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001182 if not ID_RE.match(self.revisionExpr):
1183 # in case of manifest sync the revisionExpr might be a SHA1
1184 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001185 branch.Save()
1186
Mike Pontillod3153822012-02-28 11:53:24 -08001187 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001188 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001189 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001190 self._CopyFiles()
1191 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001192 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001194 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001195 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001196 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001197 syncbuf.fail(self, e)
1198 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001199 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001200 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001202 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001203 # dest should already be an absolute path, but src is project relative
1204 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001205 abssrc = os.path.join(self.worktree, src)
1206 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001207
James W. Mills24c13082012-04-12 15:04:13 -05001208 def AddAnnotation(self, name, value, keep):
1209 self.annotations.append(_Annotation(name, value, keep))
1210
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001211 def DownloadPatchSet(self, change_id, patch_id):
1212 """Download a single patch set of a single change to FETCH_HEAD.
1213 """
1214 remote = self.GetRemote(self.remote.name)
1215
1216 cmd = ['fetch', remote.name]
1217 cmd.append('refs/changes/%2.2d/%d/%d' \
1218 % (change_id % 100, change_id, patch_id))
David Pursehouse7e6dd2d2012-10-25 12:40:51 +09001219 cmd.extend(map(str, remote.fetch))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001220 if GitCommand(self, cmd, bare=True).Wait() != 0:
1221 return None
1222 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001223 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001224 change_id,
1225 patch_id,
1226 self.bare_git.rev_parse('FETCH_HEAD'))
1227
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001228
1229## Branch Management ##
1230
1231 def StartBranch(self, name):
1232 """Create a new branch off the manifest's revision.
1233 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001234 head = self.work_git.GetHead()
1235 if head == (R_HEADS + name):
1236 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001237
David Pursehouse8a68ff92012-09-24 12:15:13 +09001238 all_refs = self.bare_ref.all
1239 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001240 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001241 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001242 capture_stdout = True,
1243 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001244
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001245 branch = self.GetBranch(name)
1246 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001247 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001248 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001249
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001250 if head.startswith(R_HEADS):
1251 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001252 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001253 except KeyError:
1254 head = None
1255
1256 if revid and head and revid == head:
1257 ref = os.path.join(self.gitdir, R_HEADS + name)
1258 try:
1259 os.makedirs(os.path.dirname(ref))
1260 except OSError:
1261 pass
1262 _lwrite(ref, '%s\n' % revid)
1263 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1264 'ref: %s%s\n' % (R_HEADS, name))
1265 branch.Save()
1266 return True
1267
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001268 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001269 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001270 capture_stdout = True,
1271 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001272 branch.Save()
1273 return True
1274 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001275
Wink Saville02d79452009-04-10 13:01:24 -07001276 def CheckoutBranch(self, name):
1277 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001278
1279 Args:
1280 name: The name of the branch to checkout.
1281
1282 Returns:
1283 True if the checkout succeeded; False if it didn't; None if the branch
1284 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001285 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001286 rev = R_HEADS + name
1287 head = self.work_git.GetHead()
1288 if head == rev:
1289 # Already on the branch
1290 #
1291 return True
Wink Saville02d79452009-04-10 13:01:24 -07001292
David Pursehouse8a68ff92012-09-24 12:15:13 +09001293 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001294 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001295 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001296 except KeyError:
1297 # Branch does not exist in this project
1298 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001299 return None
Wink Saville02d79452009-04-10 13:01:24 -07001300
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001301 if head.startswith(R_HEADS):
1302 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001303 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001304 except KeyError:
1305 head = None
1306
1307 if head == revid:
1308 # Same revision; just update HEAD to point to the new
1309 # target branch, but otherwise take no other action.
1310 #
1311 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1312 'ref: %s%s\n' % (R_HEADS, name))
1313 return True
1314
1315 return GitCommand(self,
1316 ['checkout', name, '--'],
1317 capture_stdout = True,
1318 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001319
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001320 def AbandonBranch(self, name):
1321 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001322
1323 Args:
1324 name: The name of the branch to abandon.
1325
1326 Returns:
1327 True if the abandon succeeded; False if it didn't; None if the branch
1328 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001329 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001330 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001331 all_refs = self.bare_ref.all
1332 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001333 # Doesn't exist
1334 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001335
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001336 head = self.work_git.GetHead()
1337 if head == rev:
1338 # We can't destroy the branch while we are sitting
1339 # on it. Switch to a detached HEAD.
1340 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001341 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001342
David Pursehouse8a68ff92012-09-24 12:15:13 +09001343 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001344 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001345 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1346 '%s\n' % revid)
1347 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001348 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001349
1350 return GitCommand(self,
1351 ['branch', '-D', name],
1352 capture_stdout = True,
1353 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001354
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001355 def PruneHeads(self):
1356 """Prune any topic branches already merged into upstream.
1357 """
1358 cb = self.CurrentBranch
1359 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001360 left = self._allrefs
1361 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001362 if name.startswith(R_HEADS):
1363 name = name[len(R_HEADS):]
1364 if cb is None or name != cb:
1365 kill.append(name)
1366
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001367 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368 if cb is not None \
1369 and not self._revlist(HEAD + '...' + rev) \
1370 and not self.IsDirty(consider_untracked = False):
1371 self.work_git.DetachHead(HEAD)
1372 kill.append(cb)
1373
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001374 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001375 old = self.bare_git.GetHead()
1376 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001377 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1378
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001379 try:
1380 self.bare_git.DetachHead(rev)
1381
1382 b = ['branch', '-d']
1383 b.extend(kill)
1384 b = GitCommand(self, b, bare=True,
1385 capture_stdout=True,
1386 capture_stderr=True)
1387 b.Wait()
1388 finally:
1389 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001390 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001392 for branch in kill:
1393 if (R_HEADS + branch) not in left:
1394 self.CleanPublishedCache()
1395 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001396
1397 if cb and cb not in kill:
1398 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001399 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001400
1401 kept = []
1402 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001403 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001404 branch = self.GetBranch(branch)
1405 base = branch.LocalMerge
1406 if not base:
1407 base = rev
1408 kept.append(ReviewableBranch(self, branch, base))
1409 return kept
1410
1411
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001412## Submodule Management ##
1413
1414 def GetRegisteredSubprojects(self):
1415 result = []
1416 def rec(subprojects):
1417 if not subprojects:
1418 return
1419 result.extend(subprojects)
1420 for p in subprojects:
1421 rec(p.subprojects)
1422 rec(self.subprojects)
1423 return result
1424
1425 def _GetSubmodules(self):
1426 # Unfortunately we cannot call `git submodule status --recursive` here
1427 # because the working tree might not exist yet, and it cannot be used
1428 # without a working tree in its current implementation.
1429
1430 def get_submodules(gitdir, rev):
1431 # Parse .gitmodules for submodule sub_paths and sub_urls
1432 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1433 if not sub_paths:
1434 return []
1435 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1436 # revision of submodule repository
1437 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1438 submodules = []
1439 for sub_path, sub_url in zip(sub_paths, sub_urls):
1440 try:
1441 sub_rev = sub_revs[sub_path]
1442 except KeyError:
1443 # Ignore non-exist submodules
1444 continue
1445 submodules.append((sub_rev, sub_path, sub_url))
1446 return submodules
1447
1448 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1449 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1450 def parse_gitmodules(gitdir, rev):
1451 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1452 try:
1453 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1454 bare = True, gitdir = gitdir)
1455 except GitError:
1456 return [], []
1457 if p.Wait() != 0:
1458 return [], []
1459
1460 gitmodules_lines = []
1461 fd, temp_gitmodules_path = tempfile.mkstemp()
1462 try:
1463 os.write(fd, p.stdout)
1464 os.close(fd)
1465 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1466 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1467 bare = True, gitdir = gitdir)
1468 if p.Wait() != 0:
1469 return [], []
1470 gitmodules_lines = p.stdout.split('\n')
1471 except GitError:
1472 return [], []
1473 finally:
1474 os.remove(temp_gitmodules_path)
1475
1476 names = set()
1477 paths = {}
1478 urls = {}
1479 for line in gitmodules_lines:
1480 if not line:
1481 continue
1482 m = re_path.match(line)
1483 if m:
1484 names.add(m.group(1))
1485 paths[m.group(1)] = m.group(2)
1486 continue
1487 m = re_url.match(line)
1488 if m:
1489 names.add(m.group(1))
1490 urls[m.group(1)] = m.group(2)
1491 continue
1492 names = sorted(names)
1493 return ([paths.get(name, '') for name in names],
1494 [urls.get(name, '') for name in names])
1495
1496 def git_ls_tree(gitdir, rev, paths):
1497 cmd = ['ls-tree', rev, '--']
1498 cmd.extend(paths)
1499 try:
1500 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1501 bare = True, gitdir = gitdir)
1502 except GitError:
1503 return []
1504 if p.Wait() != 0:
1505 return []
1506 objects = {}
1507 for line in p.stdout.split('\n'):
1508 if not line.strip():
1509 continue
1510 object_rev, object_path = line.split()[2:4]
1511 objects[object_path] = object_rev
1512 return objects
1513
1514 try:
1515 rev = self.GetRevisionId()
1516 except GitError:
1517 return []
1518 return get_submodules(self.gitdir, rev)
1519
1520 def GetDerivedSubprojects(self):
1521 result = []
1522 if not self.Exists:
1523 # If git repo does not exist yet, querying its submodules will
1524 # mess up its states; so return here.
1525 return result
1526 for rev, path, url in self._GetSubmodules():
1527 name = self.manifest.GetSubprojectName(self, path)
1528 project = self.manifest.projects.get(name)
1529 if project:
1530 result.extend(project.GetDerivedSubprojects())
1531 continue
1532 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1533 remote = RemoteSpec(self.remote.name,
1534 url = url,
1535 review = self.remote.review)
1536 subproject = Project(manifest = self.manifest,
1537 name = name,
1538 remote = remote,
1539 gitdir = gitdir,
1540 worktree = worktree,
1541 relpath = relpath,
1542 revisionExpr = self.revisionExpr,
1543 revisionId = rev,
1544 rebase = self.rebase,
1545 groups = self.groups,
1546 sync_c = self.sync_c,
1547 sync_s = self.sync_s,
1548 parent = self,
1549 is_derived = True)
1550 result.append(subproject)
1551 result.extend(subproject.GetDerivedSubprojects())
1552 return result
1553
1554
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001555## Direct Git Commands ##
1556
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001557 def _RemoteFetch(self, name=None,
1558 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001559 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001560 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001561 alt_dir=None,
1562 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001563
1564 is_sha1 = False
1565 tag_name = None
1566
Brian Harring14a66742012-09-28 20:21:57 -07001567 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001568 try:
1569 # if revision (sha or tag) is not present then following function
1570 # throws an error.
1571 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1572 return True
1573 except GitError:
1574 # There is no such persistent revision. We have to fetch it.
1575 return False
Brian Harring14a66742012-09-28 20:21:57 -07001576
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001577 if current_branch_only:
1578 if ID_RE.match(self.revisionExpr) is not None:
1579 is_sha1 = True
1580 elif self.revisionExpr.startswith(R_TAGS):
1581 # this is a tag and its sha1 value should never change
1582 tag_name = self.revisionExpr[len(R_TAGS):]
1583
1584 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001585 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001586 return True
Brian Harring14a66742012-09-28 20:21:57 -07001587 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1588 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001589
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001590 if not name:
1591 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001592
1593 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001594 remote = self.GetRemote(name)
1595 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001596 ssh_proxy = True
1597
Shawn O. Pearce88443382010-10-08 10:02:09 +02001598 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001599 if alt_dir and 'objects' == os.path.basename(alt_dir):
1600 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001601 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1602 remote = self.GetRemote(name)
1603
David Pursehouse8a68ff92012-09-24 12:15:13 +09001604 all_refs = self.bare_ref.all
1605 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001606 tmp = set()
1607
David Pursehouse8a68ff92012-09-24 12:15:13 +09001608 for r, ref_id in GitRefs(ref_dir).all.iteritems():
1609 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001610 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001611 all_refs[r] = ref_id
1612 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001613 continue
1614
David Pursehouse8a68ff92012-09-24 12:15:13 +09001615 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001616 continue
1617
David Pursehouse8a68ff92012-09-24 12:15:13 +09001618 r = 'refs/_alt/%s' % ref_id
1619 all_refs[r] = ref_id
1620 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001621 tmp.add(r)
1622
David Pursehouse8a68ff92012-09-24 12:15:13 +09001623 ref_names = list(all_refs.keys())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001624 ref_names.sort()
1625
1626 tmp_packed = ''
1627 old_packed = ''
1628
1629 for r in ref_names:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001630 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001631 tmp_packed += line
1632 if r not in tmp:
1633 old_packed += line
1634
1635 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001636 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001637 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001638
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001639 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001640
1641 # The --depth option only affects the initial fetch; after that we'll do
1642 # full fetches of changes.
1643 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1644 if depth and initial:
1645 cmd.append('--depth=%s' % depth)
1646
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001647 if quiet:
1648 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001649 if not self.worktree:
1650 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001651 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001652
Brian Harring14a66742012-09-28 20:21:57 -07001653 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001654 # Fetch whole repo
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001655 if no_tags:
1656 cmd.append('--no-tags')
1657 else:
1658 cmd.append('--tags')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001659 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1660 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001661 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001662 cmd.append(tag_name)
1663 else:
1664 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001665 if is_sha1:
1666 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001667 if branch.startswith(R_HEADS):
1668 branch = branch[len(R_HEADS):]
1669 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001670
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001671 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001672 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001673 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1674 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001675 ok = True
1676 break
Brian Harring14a66742012-09-28 20:21:57 -07001677 elif current_branch_only and is_sha1 and ret == 128:
1678 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1679 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1680 # abort the optimization attempt and do a full sync.
1681 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001682 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001683
1684 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001685 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001686 if old_packed != '':
1687 _lwrite(packed_refs, old_packed)
1688 else:
1689 os.remove(packed_refs)
1690 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001691
1692 if is_sha1 and current_branch_only and self.upstream:
1693 # We just synced the upstream given branch; verify we
1694 # got what we wanted, else trigger a second run of all
1695 # refs.
1696 if not CheckForSha1():
1697 return self._RemoteFetch(name=name, current_branch_only=False,
1698 initial=False, quiet=quiet, alt_dir=alt_dir)
1699
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001700 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001701
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001702 def _ApplyCloneBundle(self, initial=False, quiet=False):
1703 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1704 return False
1705
1706 remote = self.GetRemote(self.remote.name)
1707 bundle_url = remote.url + '/clone.bundle'
1708 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001709 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1710 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001711 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1712 return False
1713
1714 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1715 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1716
1717 exist_dst = os.path.exists(bundle_dst)
1718 exist_tmp = os.path.exists(bundle_tmp)
1719
1720 if not initial and not exist_dst and not exist_tmp:
1721 return False
1722
1723 if not exist_dst:
1724 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1725 if not exist_dst:
1726 return False
1727
1728 cmd = ['fetch']
1729 if quiet:
1730 cmd.append('--quiet')
1731 if not self.worktree:
1732 cmd.append('--update-head-ok')
1733 cmd.append(bundle_dst)
1734 for f in remote.fetch:
1735 cmd.append(str(f))
1736 cmd.append('refs/tags/*:refs/tags/*')
1737
1738 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001739 if os.path.exists(bundle_dst):
1740 os.remove(bundle_dst)
1741 if os.path.exists(bundle_tmp):
1742 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001743 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001744
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001745 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001746 if os.path.exists(dstPath):
1747 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001748
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001749 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001750 if quiet:
1751 cmd += ['--silent']
1752 if os.path.exists(tmpPath):
1753 size = os.stat(tmpPath).st_size
1754 if size >= 1024:
1755 cmd += ['--continue-at', '%d' % (size,)]
1756 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001757 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001758 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1759 cmd += ['--proxy', os.environ['http_proxy']]
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001760 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
1761 if cookiefile:
1762 cmd += ['--cookie', cookiefile]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001763 cmd += [srcUrl]
1764
1765 if IsTrace():
1766 Trace('%s', ' '.join(cmd))
1767 try:
1768 proc = subprocess.Popen(cmd)
1769 except OSError:
1770 return False
1771
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001772 curlret = proc.wait()
1773
1774 if curlret == 22:
1775 # From curl man page:
1776 # 22: HTTP page not retrieved. The requested url was not found or
1777 # returned another error with the HTTP error code being 400 or above.
1778 # This return code only appears if -f, --fail is used.
1779 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001780 print("Server does not provide clone.bundle; ignoring.",
1781 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001782 return False
1783
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001784 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001785 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001786 os.rename(tmpPath, dstPath)
1787 return True
1788 else:
1789 os.remove(tmpPath)
1790 return False
1791 else:
1792 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001793
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001794 def _Checkout(self, rev, quiet=False):
1795 cmd = ['checkout']
1796 if quiet:
1797 cmd.append('-q')
1798 cmd.append(rev)
1799 cmd.append('--')
1800 if GitCommand(self, cmd).Wait() != 0:
1801 if self._allrefs:
1802 raise GitError('%s checkout %s ' % (self.name, rev))
1803
Pierre Tardye5a21222011-03-24 16:28:18 +01001804 def _CherryPick(self, rev, quiet=False):
1805 cmd = ['cherry-pick']
1806 cmd.append(rev)
1807 cmd.append('--')
1808 if GitCommand(self, cmd).Wait() != 0:
1809 if self._allrefs:
1810 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1811
Erwan Mahea94f1622011-08-19 13:56:09 +02001812 def _Revert(self, rev, quiet=False):
1813 cmd = ['revert']
1814 cmd.append('--no-edit')
1815 cmd.append(rev)
1816 cmd.append('--')
1817 if GitCommand(self, cmd).Wait() != 0:
1818 if self._allrefs:
1819 raise GitError('%s revert %s ' % (self.name, rev))
1820
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001821 def _ResetHard(self, rev, quiet=True):
1822 cmd = ['reset', '--hard']
1823 if quiet:
1824 cmd.append('-q')
1825 cmd.append(rev)
1826 if GitCommand(self, cmd).Wait() != 0:
1827 raise GitError('%s reset --hard %s ' % (self.name, rev))
1828
1829 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001830 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001831 if onto is not None:
1832 cmd.extend(['--onto', onto])
1833 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001834 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001835 raise GitError('%s rebase %s ' % (self.name, upstream))
1836
Pierre Tardy3d125942012-05-04 12:18:12 +02001837 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001838 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001839 if ffonly:
1840 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001841 if GitCommand(self, cmd).Wait() != 0:
1842 raise GitError('%s merge %s ' % (self.name, head))
1843
1844 def _InitGitDir(self):
1845 if not os.path.exists(self.gitdir):
1846 os.makedirs(self.gitdir)
1847 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001848
Shawn O. Pearce88443382010-10-08 10:02:09 +02001849 mp = self.manifest.manifestProject
1850 ref_dir = mp.config.GetString('repo.reference')
1851
1852 if ref_dir:
1853 mirror_git = os.path.join(ref_dir, self.name + '.git')
1854 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1855 self.relpath + '.git')
1856
1857 if os.path.exists(mirror_git):
1858 ref_dir = mirror_git
1859
1860 elif os.path.exists(repo_git):
1861 ref_dir = repo_git
1862
1863 else:
1864 ref_dir = None
1865
1866 if ref_dir:
1867 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1868 os.path.join(ref_dir, 'objects') + '\n')
1869
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001870 if self.manifest.IsMirror:
1871 self.config.SetString('core.bare', 'true')
1872 else:
1873 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001874
1875 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001876 try:
1877 to_rm = os.listdir(hooks)
1878 except OSError:
1879 to_rm = []
1880 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001881 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001882 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001883
1884 m = self.manifest.manifestProject.config
1885 for key in ['user.name', 'user.email']:
1886 if m.Has(key, include_defaults = False):
1887 self.config.SetString(key, m.GetString(key))
1888
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001889 def _InitHooks(self):
1890 hooks = self._gitdir_path('hooks')
1891 if not os.path.exists(hooks):
1892 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001893 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001894 name = os.path.basename(stock_hook)
1895
Victor Boivie65e0f352011-04-18 11:23:29 +02001896 if name in ('commit-msg',) and not self.remote.review \
1897 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001898 # Don't install a Gerrit Code Review hook if this
1899 # project does not appear to use it for reviews.
1900 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001901 # Since the manifest project is one of those, but also
1902 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001903 continue
1904
1905 dst = os.path.join(hooks, name)
1906 if os.path.islink(dst):
1907 continue
1908 if os.path.exists(dst):
1909 if filecmp.cmp(stock_hook, dst, shallow=False):
1910 os.remove(dst)
1911 else:
1912 _error("%s: Not replacing %s hook", self.relpath, name)
1913 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001914 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001915 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001916 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001917 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001918 raise GitError('filesystem must support symlinks')
1919 else:
1920 raise
1921
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001922 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001923 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001924 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001925 remote.url = self.remote.url
1926 remote.review = self.remote.review
1927 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001928
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001929 if self.worktree:
1930 remote.ResetFetch(mirror=False)
1931 else:
1932 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001933 remote.Save()
1934
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001935 def _InitMRef(self):
1936 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001937 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001938
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001939 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001940 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001941
1942 def _InitAnyMRef(self, ref):
1943 cur = self.bare_ref.symref(ref)
1944
1945 if self.revisionId:
1946 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1947 msg = 'manifest set to %s' % self.revisionId
1948 dst = self.revisionId + '^0'
1949 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1950 else:
1951 remote = self.GetRemote(self.remote.name)
1952 dst = remote.ToLocal(self.revisionExpr)
1953 if cur != dst:
1954 msg = 'manifest set to %s' % self.revisionExpr
1955 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001956
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001957 def _InitWorkTree(self):
1958 dotgit = os.path.join(self.worktree, '.git')
1959 if not os.path.exists(dotgit):
1960 os.makedirs(dotgit)
1961
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001962 for name in ['config',
1963 'description',
1964 'hooks',
1965 'info',
1966 'logs',
1967 'objects',
1968 'packed-refs',
1969 'refs',
1970 'rr-cache',
1971 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001972 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001973 src = os.path.join(self.gitdir, name)
1974 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001975 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001976 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001977 else:
1978 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001979 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001980 if e.errno == errno.EPERM:
1981 raise GitError('filesystem must support symlinks')
1982 else:
1983 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001984
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001985 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001986
1987 cmd = ['read-tree', '--reset', '-u']
1988 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001989 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001990 if GitCommand(self, cmd).Wait() != 0:
1991 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001992
1993 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1994 if not os.path.exists(rr_cache):
1995 os.makedirs(rr_cache)
1996
Shawn O. Pearce93609662009-04-21 10:50:33 -07001997 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001998
1999 def _gitdir_path(self, path):
2000 return os.path.join(self.gitdir, path)
2001
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002002 def _revlist(self, *args, **kw):
2003 a = []
2004 a.extend(args)
2005 a.append('--')
2006 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002007
2008 @property
2009 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002010 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002011
2012 class _GitGetByExec(object):
2013 def __init__(self, project, bare):
2014 self._project = project
2015 self._bare = bare
2016
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002017 def LsOthers(self):
2018 p = GitCommand(self._project,
2019 ['ls-files',
2020 '-z',
2021 '--others',
2022 '--exclude-standard'],
2023 bare = False,
2024 capture_stdout = True,
2025 capture_stderr = True)
2026 if p.Wait() == 0:
2027 out = p.stdout
2028 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002029 return out[:-1].split('\0') # pylint: disable=W1401
2030 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002031 return []
2032
2033 def DiffZ(self, name, *args):
2034 cmd = [name]
2035 cmd.append('-z')
2036 cmd.extend(args)
2037 p = GitCommand(self._project,
2038 cmd,
2039 bare = False,
2040 capture_stdout = True,
2041 capture_stderr = True)
2042 try:
2043 out = p.process.stdout.read()
2044 r = {}
2045 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002046 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002047 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002048 try:
2049 info = out.next()
2050 path = out.next()
2051 except StopIteration:
2052 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002053
2054 class _Info(object):
2055 def __init__(self, path, omode, nmode, oid, nid, state):
2056 self.path = path
2057 self.src_path = None
2058 self.old_mode = omode
2059 self.new_mode = nmode
2060 self.old_id = oid
2061 self.new_id = nid
2062
2063 if len(state) == 1:
2064 self.status = state
2065 self.level = None
2066 else:
2067 self.status = state[:1]
2068 self.level = state[1:]
2069 while self.level.startswith('0'):
2070 self.level = self.level[1:]
2071
2072 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002073 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002074 if info.status in ('R', 'C'):
2075 info.src_path = info.path
2076 info.path = out.next()
2077 r[info.path] = info
2078 return r
2079 finally:
2080 p.Wait()
2081
2082 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002083 if self._bare:
2084 path = os.path.join(self._project.gitdir, HEAD)
2085 else:
2086 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002087 try:
2088 fd = open(path, 'rb')
2089 except IOError:
2090 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002091 try:
2092 line = fd.read()
2093 finally:
2094 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002095 if line.startswith('ref: '):
2096 return line[5:-1]
2097 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002098
2099 def SetHead(self, ref, message=None):
2100 cmdv = []
2101 if message is not None:
2102 cmdv.extend(['-m', message])
2103 cmdv.append(HEAD)
2104 cmdv.append(ref)
2105 self.symbolic_ref(*cmdv)
2106
2107 def DetachHead(self, new, message=None):
2108 cmdv = ['--no-deref']
2109 if message is not None:
2110 cmdv.extend(['-m', message])
2111 cmdv.append(HEAD)
2112 cmdv.append(new)
2113 self.update_ref(*cmdv)
2114
2115 def UpdateRef(self, name, new, old=None,
2116 message=None,
2117 detach=False):
2118 cmdv = []
2119 if message is not None:
2120 cmdv.extend(['-m', message])
2121 if detach:
2122 cmdv.append('--no-deref')
2123 cmdv.append(name)
2124 cmdv.append(new)
2125 if old is not None:
2126 cmdv.append(old)
2127 self.update_ref(*cmdv)
2128
2129 def DeleteRef(self, name, old=None):
2130 if not old:
2131 old = self.rev_parse(name)
2132 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002133 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002135 def rev_list(self, *args, **kw):
2136 if 'format' in kw:
2137 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2138 else:
2139 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002140 cmdv.extend(args)
2141 p = GitCommand(self._project,
2142 cmdv,
2143 bare = self._bare,
2144 capture_stdout = True,
2145 capture_stderr = True)
2146 r = []
2147 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002148 if line[-1] == '\n':
2149 line = line[:-1]
2150 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002151 if p.Wait() != 0:
2152 raise GitError('%s rev-list %s: %s' % (
2153 self._project.name,
2154 str(args),
2155 p.stderr))
2156 return r
2157
2158 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002159 """Allow arbitrary git commands using pythonic syntax.
2160
2161 This allows you to do things like:
2162 git_obj.rev_parse('HEAD')
2163
2164 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2165 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002166 Any other positional arguments will be passed to the git command, and the
2167 following keyword arguments are supported:
2168 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002169
2170 Args:
2171 name: The name of the git command to call. Any '_' characters will
2172 be replaced with '-'.
2173
2174 Returns:
2175 A callable object that will try to call git with the named command.
2176 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002177 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002178 def runner(*args, **kwargs):
2179 cmdv = []
2180 config = kwargs.pop('config', None)
2181 for k in kwargs:
2182 raise TypeError('%s() got an unexpected keyword argument %r'
2183 % (name, k))
2184 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002185 if not git_require((1, 7, 2)):
2186 raise ValueError('cannot set config on command line for %s()'
2187 % name)
Dave Borowitz091f8932012-10-23 17:01:04 -07002188 for k, v in config.iteritems():
2189 cmdv.append('-c')
2190 cmdv.append('%s=%s' % (k, v))
2191 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002192 cmdv.extend(args)
2193 p = GitCommand(self._project,
2194 cmdv,
2195 bare = self._bare,
2196 capture_stdout = True,
2197 capture_stderr = True)
2198 if p.Wait() != 0:
2199 raise GitError('%s %s: %s' % (
2200 self._project.name,
2201 name,
2202 p.stderr))
2203 r = p.stdout
2204 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2205 return r[:-1]
2206 return r
2207 return runner
2208
2209
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002210class _PriorSyncFailedError(Exception):
2211 def __str__(self):
2212 return 'prior sync failed; rebase still in progress'
2213
2214class _DirtyError(Exception):
2215 def __str__(self):
2216 return 'contains uncommitted changes'
2217
2218class _InfoMessage(object):
2219 def __init__(self, project, text):
2220 self.project = project
2221 self.text = text
2222
2223 def Print(self, syncbuf):
2224 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2225 syncbuf.out.nl()
2226
2227class _Failure(object):
2228 def __init__(self, project, why):
2229 self.project = project
2230 self.why = why
2231
2232 def Print(self, syncbuf):
2233 syncbuf.out.fail('error: %s/: %s',
2234 self.project.relpath,
2235 str(self.why))
2236 syncbuf.out.nl()
2237
2238class _Later(object):
2239 def __init__(self, project, action):
2240 self.project = project
2241 self.action = action
2242
2243 def Run(self, syncbuf):
2244 out = syncbuf.out
2245 out.project('project %s/', self.project.relpath)
2246 out.nl()
2247 try:
2248 self.action()
2249 out.nl()
2250 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002251 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002252 out.nl()
2253 return False
2254
2255class _SyncColoring(Coloring):
2256 def __init__(self, config):
2257 Coloring.__init__(self, config, 'reposync')
2258 self.project = self.printer('header', attr = 'bold')
2259 self.info = self.printer('info')
2260 self.fail = self.printer('fail', fg='red')
2261
2262class SyncBuffer(object):
2263 def __init__(self, config, detach_head=False):
2264 self._messages = []
2265 self._failures = []
2266 self._later_queue1 = []
2267 self._later_queue2 = []
2268
2269 self.out = _SyncColoring(config)
2270 self.out.redirect(sys.stderr)
2271
2272 self.detach_head = detach_head
2273 self.clean = True
2274
2275 def info(self, project, fmt, *args):
2276 self._messages.append(_InfoMessage(project, fmt % args))
2277
2278 def fail(self, project, err=None):
2279 self._failures.append(_Failure(project, err))
2280 self.clean = False
2281
2282 def later1(self, project, what):
2283 self._later_queue1.append(_Later(project, what))
2284
2285 def later2(self, project, what):
2286 self._later_queue2.append(_Later(project, what))
2287
2288 def Finish(self):
2289 self._PrintMessages()
2290 self._RunLater()
2291 self._PrintMessages()
2292 return self.clean
2293
2294 def _RunLater(self):
2295 for q in ['_later_queue1', '_later_queue2']:
2296 if not self._RunQueue(q):
2297 return
2298
2299 def _RunQueue(self, queue):
2300 for m in getattr(self, queue):
2301 if not m.Run(self):
2302 self.clean = False
2303 return False
2304 setattr(self, queue, [])
2305 return True
2306
2307 def _PrintMessages(self):
2308 for m in self._messages:
2309 m.Print(self)
2310 for m in self._failures:
2311 m.Print(self)
2312
2313 self._messages = []
2314 self._failures = []
2315
2316
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002317class MetaProject(Project):
2318 """A special project housed under .repo.
2319 """
2320 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002321 Project.__init__(self,
2322 manifest = manifest,
2323 name = name,
2324 gitdir = gitdir,
2325 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002326 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002327 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002328 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002329 revisionId = None,
2330 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002331
2332 def PreSync(self):
2333 if self.Exists:
2334 cb = self.CurrentBranch
2335 if cb:
2336 base = self.GetBranch(cb).merge
2337 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002338 self.revisionExpr = base
2339 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002340
Florian Vallee5d016502012-06-07 17:19:26 +02002341 def MetaBranchSwitch(self, target):
2342 """ Prepare MetaProject for manifest branch switch
2343 """
2344
2345 # detach and delete manifest branch, allowing a new
2346 # branch to take over
2347 syncbuf = SyncBuffer(self.config, detach_head = True)
2348 self.Sync_LocalHalf(syncbuf)
2349 syncbuf.Finish()
2350
2351 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002352 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002353 capture_stdout = True,
2354 capture_stderr = True).Wait() == 0
2355
2356
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002357 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002358 def LastFetch(self):
2359 try:
2360 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2361 return os.path.getmtime(fh)
2362 except OSError:
2363 return 0
2364
2365 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002366 def HasChanges(self):
2367 """Has the remote received new commits not yet checked out?
2368 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002369 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002370 return False
2371
David Pursehouse8a68ff92012-09-24 12:15:13 +09002372 all_refs = self.bare_ref.all
2373 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002374 head = self.work_git.GetHead()
2375 if head.startswith(R_HEADS):
2376 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002377 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002378 except KeyError:
2379 head = None
2380
2381 if revid == head:
2382 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002383 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002384 return True
2385 return False