blob: 295fb10f02df87d1768f7bfc677dad408d51c4f1 [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".
Colin Cross5acde752012-03-28 20:15:45 -0700670
Conley Owens971de8e2012-04-16 10:36:08 -0700671 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.
675 """
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)
937
938 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
949 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800950
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800951 if GitCommand(self, cmd, bare = True).Wait() != 0:
952 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953
954 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
955 self.bare_git.UpdateRef(R_PUB + branch.name,
956 R_HEADS + branch.name,
957 message = msg)
958
959
960## Sync ##
961
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700962 def Sync_NetworkHalf(self,
963 quiet=False,
964 is_new=None,
965 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700966 clone_bundle=True,
967 no_tags=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968 """Perform only the network IO portion of the sync process.
969 Local working directory/branch state is not affected.
970 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700971 if is_new is None:
972 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200973 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700974 self._InitGitDir()
975 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700976
977 if is_new:
978 alt = os.path.join(self.gitdir, 'objects/info/alternates')
979 try:
980 fd = open(alt, 'rb')
981 try:
982 alt_dir = fd.readline().rstrip()
983 finally:
984 fd.close()
985 except IOError:
986 alt_dir = None
987 else:
988 alt_dir = None
989
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700990 if clone_bundle \
991 and alt_dir is None \
992 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700993 is_new = False
994
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700995 if not current_branch_only:
996 if self.sync_c:
997 current_branch_only = True
998 elif not self.manifest._loaded:
999 # Manifest cannot check defaults until it syncs.
1000 current_branch_only = False
1001 elif self.manifest.default.sync_c:
1002 current_branch_only = True
1003
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001004 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001005 current_branch_only=current_branch_only,
1006 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001007 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001008
1009 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001010 self._InitMRef()
1011 else:
1012 self._InitMirrorHead()
1013 try:
1014 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1015 except OSError:
1016 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001018
1019 def PostRepoUpgrade(self):
1020 self._InitHooks()
1021
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001023 for copyfile in self.copyfiles:
1024 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001025
David Pursehouse8a68ff92012-09-24 12:15:13 +09001026 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001027 if self.revisionId:
1028 return self.revisionId
1029
1030 rem = self.GetRemote(self.remote.name)
1031 rev = rem.ToLocal(self.revisionExpr)
1032
David Pursehouse8a68ff92012-09-24 12:15:13 +09001033 if all_refs is not None and rev in all_refs:
1034 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001035
1036 try:
1037 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1038 except GitError:
1039 raise ManifestInvalidRevisionError(
1040 'revision %s in %s not found' % (self.revisionExpr,
1041 self.name))
1042
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001043 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001044 """Perform only the local IO portion of the sync process.
1045 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001046 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001047 all_refs = self.bare_ref.all
1048 self.CleanPublishedCache(all_refs)
1049 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001050
David Pursehouse1d947b32012-10-25 12:23:11 +09001051 def _doff():
1052 self._FastForward(revid)
1053 self._CopyFiles()
1054
Skyler Kaufman835cd682011-03-08 12:14:41 -08001055 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001056 head = self.work_git.GetHead()
1057 if head.startswith(R_HEADS):
1058 branch = head[len(R_HEADS):]
1059 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001060 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001061 except KeyError:
1062 head = None
1063 else:
1064 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001066 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067 # Currently on a detached HEAD. The user is assumed to
1068 # not have any local modifications worth worrying about.
1069 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001070 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001071 syncbuf.fail(self, _PriorSyncFailedError())
1072 return
1073
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001074 if head == revid:
1075 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001076 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001077 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001078 if not syncbuf.detach_head:
1079 return
1080 else:
1081 lost = self._revlist(not_rev(revid), HEAD)
1082 if lost:
1083 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001084
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001086 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001087 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001088 syncbuf.fail(self, e)
1089 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001091 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001092
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001093 if head == revid:
1094 # No changes; don't do anything further.
1095 #
1096 return
1097
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001098 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001099
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001100 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001102 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001103 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001104 syncbuf.info(self,
1105 "leaving %s; does not track upstream",
1106 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001107 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001108 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001109 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001110 syncbuf.fail(self, e)
1111 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001112 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001113 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001114
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001115 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001116 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001118 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119 if not_merged:
1120 if upstream_gain:
1121 # The user has published this branch and some of those
1122 # commits are not yet merged upstream. We do not want
1123 # to rewrite the published commits so we punt.
1124 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001125 syncbuf.fail(self,
1126 "branch %s is published (but not merged) and is now %d commits behind"
1127 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001128 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001129 elif pub == head:
1130 # All published commits are merged, and thus we are a
1131 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001132 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001133 syncbuf.later1(self, _doff)
1134 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001136 # Examine the local commits not in the remote. Find the
1137 # last one attributed to this user, if any.
1138 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001139 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001140 last_mine = None
1141 cnt_mine = 0
1142 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001143 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001144 if committer_email == self.UserEmail:
1145 last_mine = commit_id
1146 cnt_mine += 1
1147
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001148 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001149 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001150
1151 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001152 syncbuf.fail(self, _DirtyError())
1153 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001154
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001155 # If the upstream switched on us, warn the user.
1156 #
1157 if branch.merge != self.revisionExpr:
1158 if branch.merge and self.revisionExpr:
1159 syncbuf.info(self,
1160 'manifest switched %s...%s',
1161 branch.merge,
1162 self.revisionExpr)
1163 elif branch.merge:
1164 syncbuf.info(self,
1165 'manifest no longer tracks %s',
1166 branch.merge)
1167
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001168 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001170 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001171 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001172 syncbuf.info(self,
1173 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001174 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001175
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001176 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001177 if not ID_RE.match(self.revisionExpr):
1178 # in case of manifest sync the revisionExpr might be a SHA1
1179 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001180 branch.Save()
1181
Mike Pontillod3153822012-02-28 11:53:24 -08001182 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001183 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001184 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001185 self._CopyFiles()
1186 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001187 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001188 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001189 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001190 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001191 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001192 syncbuf.fail(self, e)
1193 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001195 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001197 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001198 # dest should already be an absolute path, but src is project relative
1199 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001200 abssrc = os.path.join(self.worktree, src)
1201 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001202
James W. Mills24c13082012-04-12 15:04:13 -05001203 def AddAnnotation(self, name, value, keep):
1204 self.annotations.append(_Annotation(name, value, keep))
1205
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001206 def DownloadPatchSet(self, change_id, patch_id):
1207 """Download a single patch set of a single change to FETCH_HEAD.
1208 """
1209 remote = self.GetRemote(self.remote.name)
1210
1211 cmd = ['fetch', remote.name]
1212 cmd.append('refs/changes/%2.2d/%d/%d' \
1213 % (change_id % 100, change_id, patch_id))
David Pursehouse7e6dd2d2012-10-25 12:40:51 +09001214 cmd.extend(map(str, remote.fetch))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001215 if GitCommand(self, cmd, bare=True).Wait() != 0:
1216 return None
1217 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001218 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001219 change_id,
1220 patch_id,
1221 self.bare_git.rev_parse('FETCH_HEAD'))
1222
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001223
1224## Branch Management ##
1225
1226 def StartBranch(self, name):
1227 """Create a new branch off the manifest's revision.
1228 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001229 head = self.work_git.GetHead()
1230 if head == (R_HEADS + name):
1231 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001232
David Pursehouse8a68ff92012-09-24 12:15:13 +09001233 all_refs = self.bare_ref.all
1234 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001235 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001236 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001237 capture_stdout = True,
1238 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001239
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001240 branch = self.GetBranch(name)
1241 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001242 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001243 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001244
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001245 if head.startswith(R_HEADS):
1246 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001247 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001248 except KeyError:
1249 head = None
1250
1251 if revid and head and revid == head:
1252 ref = os.path.join(self.gitdir, R_HEADS + name)
1253 try:
1254 os.makedirs(os.path.dirname(ref))
1255 except OSError:
1256 pass
1257 _lwrite(ref, '%s\n' % revid)
1258 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1259 'ref: %s%s\n' % (R_HEADS, name))
1260 branch.Save()
1261 return True
1262
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001263 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001264 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001265 capture_stdout = True,
1266 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001267 branch.Save()
1268 return True
1269 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001270
Wink Saville02d79452009-04-10 13:01:24 -07001271 def CheckoutBranch(self, name):
1272 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001273
1274 Args:
1275 name: The name of the branch to checkout.
1276
1277 Returns:
1278 True if the checkout succeeded; False if it didn't; None if the branch
1279 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001280 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001281 rev = R_HEADS + name
1282 head = self.work_git.GetHead()
1283 if head == rev:
1284 # Already on the branch
1285 #
1286 return True
Wink Saville02d79452009-04-10 13:01:24 -07001287
David Pursehouse8a68ff92012-09-24 12:15:13 +09001288 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001289 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001290 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001291 except KeyError:
1292 # Branch does not exist in this project
1293 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001294 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001295
1296 if head.startswith(R_HEADS):
1297 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001298 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001299 except KeyError:
1300 head = None
1301
1302 if head == revid:
1303 # Same revision; just update HEAD to point to the new
1304 # target branch, but otherwise take no other action.
1305 #
1306 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1307 'ref: %s%s\n' % (R_HEADS, name))
1308 return True
Wink Saville02d79452009-04-10 13:01:24 -07001309
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001310 return GitCommand(self,
1311 ['checkout', name, '--'],
1312 capture_stdout = True,
1313 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001314
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001315 def AbandonBranch(self, name):
1316 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001317
1318 Args:
1319 name: The name of the branch to abandon.
1320
1321 Returns:
1322 True if the abandon succeeded; False if it didn't; None if the branch
1323 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001324 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001325 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001326 all_refs = self.bare_ref.all
1327 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001328 # Doesn't exist
1329 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001330
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001331 head = self.work_git.GetHead()
1332 if head == rev:
1333 # We can't destroy the branch while we are sitting
1334 # on it. Switch to a detached HEAD.
1335 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001336 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001337
David Pursehouse8a68ff92012-09-24 12:15:13 +09001338 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001339 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001340 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1341 '%s\n' % revid)
1342 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001343 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001344
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001345 return GitCommand(self,
1346 ['branch', '-D', name],
1347 capture_stdout = True,
1348 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001349
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350 def PruneHeads(self):
1351 """Prune any topic branches already merged into upstream.
1352 """
1353 cb = self.CurrentBranch
1354 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001355 left = self._allrefs
1356 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357 if name.startswith(R_HEADS):
1358 name = name[len(R_HEADS):]
1359 if cb is None or name != cb:
1360 kill.append(name)
1361
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001362 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363 if cb is not None \
1364 and not self._revlist(HEAD + '...' + rev) \
1365 and not self.IsDirty(consider_untracked = False):
1366 self.work_git.DetachHead(HEAD)
1367 kill.append(cb)
1368
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001369 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001370 old = self.bare_git.GetHead()
1371 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001372 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1373
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001374 try:
1375 self.bare_git.DetachHead(rev)
1376
1377 b = ['branch', '-d']
1378 b.extend(kill)
1379 b = GitCommand(self, b, bare=True,
1380 capture_stdout=True,
1381 capture_stderr=True)
1382 b.Wait()
1383 finally:
1384 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001385 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001387 for branch in kill:
1388 if (R_HEADS + branch) not in left:
1389 self.CleanPublishedCache()
1390 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391
1392 if cb and cb not in kill:
1393 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001394 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001395
1396 kept = []
1397 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001398 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399 branch = self.GetBranch(branch)
1400 base = branch.LocalMerge
1401 if not base:
1402 base = rev
1403 kept.append(ReviewableBranch(self, branch, base))
1404 return kept
1405
1406
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001407## Submodule Management ##
1408
1409 def GetRegisteredSubprojects(self):
1410 result = []
1411 def rec(subprojects):
1412 if not subprojects:
1413 return
1414 result.extend(subprojects)
1415 for p in subprojects:
1416 rec(p.subprojects)
1417 rec(self.subprojects)
1418 return result
1419
1420 def _GetSubmodules(self):
1421 # Unfortunately we cannot call `git submodule status --recursive` here
1422 # because the working tree might not exist yet, and it cannot be used
1423 # without a working tree in its current implementation.
1424
1425 def get_submodules(gitdir, rev):
1426 # Parse .gitmodules for submodule sub_paths and sub_urls
1427 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1428 if not sub_paths:
1429 return []
1430 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1431 # revision of submodule repository
1432 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1433 submodules = []
1434 for sub_path, sub_url in zip(sub_paths, sub_urls):
1435 try:
1436 sub_rev = sub_revs[sub_path]
1437 except KeyError:
1438 # Ignore non-exist submodules
1439 continue
1440 submodules.append((sub_rev, sub_path, sub_url))
1441 return submodules
1442
1443 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1444 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1445 def parse_gitmodules(gitdir, rev):
1446 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1447 try:
1448 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1449 bare = True, gitdir = gitdir)
1450 except GitError:
1451 return [], []
1452 if p.Wait() != 0:
1453 return [], []
1454
1455 gitmodules_lines = []
1456 fd, temp_gitmodules_path = tempfile.mkstemp()
1457 try:
1458 os.write(fd, p.stdout)
1459 os.close(fd)
1460 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1461 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1462 bare = True, gitdir = gitdir)
1463 if p.Wait() != 0:
1464 return [], []
1465 gitmodules_lines = p.stdout.split('\n')
1466 except GitError:
1467 return [], []
1468 finally:
1469 os.remove(temp_gitmodules_path)
1470
1471 names = set()
1472 paths = {}
1473 urls = {}
1474 for line in gitmodules_lines:
1475 if not line:
1476 continue
1477 m = re_path.match(line)
1478 if m:
1479 names.add(m.group(1))
1480 paths[m.group(1)] = m.group(2)
1481 continue
1482 m = re_url.match(line)
1483 if m:
1484 names.add(m.group(1))
1485 urls[m.group(1)] = m.group(2)
1486 continue
1487 names = sorted(names)
1488 return ([paths.get(name, '') for name in names],
1489 [urls.get(name, '') for name in names])
1490
1491 def git_ls_tree(gitdir, rev, paths):
1492 cmd = ['ls-tree', rev, '--']
1493 cmd.extend(paths)
1494 try:
1495 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1496 bare = True, gitdir = gitdir)
1497 except GitError:
1498 return []
1499 if p.Wait() != 0:
1500 return []
1501 objects = {}
1502 for line in p.stdout.split('\n'):
1503 if not line.strip():
1504 continue
1505 object_rev, object_path = line.split()[2:4]
1506 objects[object_path] = object_rev
1507 return objects
1508
1509 try:
1510 rev = self.GetRevisionId()
1511 except GitError:
1512 return []
1513 return get_submodules(self.gitdir, rev)
1514
1515 def GetDerivedSubprojects(self):
1516 result = []
1517 if not self.Exists:
1518 # If git repo does not exist yet, querying its submodules will
1519 # mess up its states; so return here.
1520 return result
1521 for rev, path, url in self._GetSubmodules():
1522 name = self.manifest.GetSubprojectName(self, path)
1523 project = self.manifest.projects.get(name)
1524 if project:
1525 result.extend(project.GetDerivedSubprojects())
1526 continue
1527 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1528 remote = RemoteSpec(self.remote.name,
1529 url = url,
1530 review = self.remote.review)
1531 subproject = Project(manifest = self.manifest,
1532 name = name,
1533 remote = remote,
1534 gitdir = gitdir,
1535 worktree = worktree,
1536 relpath = relpath,
1537 revisionExpr = self.revisionExpr,
1538 revisionId = rev,
1539 rebase = self.rebase,
1540 groups = self.groups,
1541 sync_c = self.sync_c,
1542 sync_s = self.sync_s,
1543 parent = self,
1544 is_derived = True)
1545 result.append(subproject)
1546 result.extend(subproject.GetDerivedSubprojects())
1547 return result
1548
1549
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001550## Direct Git Commands ##
1551
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001552 def _RemoteFetch(self, name=None,
1553 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001554 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001555 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001556 alt_dir=None,
1557 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001558
1559 is_sha1 = False
1560 tag_name = None
1561
Brian Harring14a66742012-09-28 20:21:57 -07001562 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001563 try:
1564 # if revision (sha or tag) is not present then following function
1565 # throws an error.
1566 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1567 return True
1568 except GitError:
1569 # There is no such persistent revision. We have to fetch it.
1570 return False
Brian Harring14a66742012-09-28 20:21:57 -07001571
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001572 if current_branch_only:
1573 if ID_RE.match(self.revisionExpr) is not None:
1574 is_sha1 = True
1575 elif self.revisionExpr.startswith(R_TAGS):
1576 # this is a tag and its sha1 value should never change
1577 tag_name = self.revisionExpr[len(R_TAGS):]
1578
1579 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001580 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001581 return True
Brian Harring14a66742012-09-28 20:21:57 -07001582 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1583 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001584
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001585 if not name:
1586 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001587
1588 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001589 remote = self.GetRemote(name)
1590 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001591 ssh_proxy = True
1592
Shawn O. Pearce88443382010-10-08 10:02:09 +02001593 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001594 if alt_dir and 'objects' == os.path.basename(alt_dir):
1595 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001596 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1597 remote = self.GetRemote(name)
1598
David Pursehouse8a68ff92012-09-24 12:15:13 +09001599 all_refs = self.bare_ref.all
1600 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001601 tmp = set()
1602
David Pursehouse8a68ff92012-09-24 12:15:13 +09001603 for r, ref_id in GitRefs(ref_dir).all.iteritems():
1604 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001605 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001606 all_refs[r] = ref_id
1607 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001608 continue
1609
David Pursehouse8a68ff92012-09-24 12:15:13 +09001610 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001611 continue
1612
David Pursehouse8a68ff92012-09-24 12:15:13 +09001613 r = 'refs/_alt/%s' % ref_id
1614 all_refs[r] = ref_id
1615 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001616 tmp.add(r)
1617
David Pursehouse8a68ff92012-09-24 12:15:13 +09001618 ref_names = list(all_refs.keys())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001619 ref_names.sort()
1620
1621 tmp_packed = ''
1622 old_packed = ''
1623
1624 for r in ref_names:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001625 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001626 tmp_packed += line
1627 if r not in tmp:
1628 old_packed += line
1629
1630 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001631 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001632 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001633
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001634 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001635
1636 # The --depth option only affects the initial fetch; after that we'll do
1637 # full fetches of changes.
1638 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1639 if depth and initial:
1640 cmd.append('--depth=%s' % depth)
1641
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001642 if quiet:
1643 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001644 if not self.worktree:
1645 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001646 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001647
Brian Harring14a66742012-09-28 20:21:57 -07001648 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001649 # Fetch whole repo
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001650 if no_tags:
1651 cmd.append('--no-tags')
1652 else:
1653 cmd.append('--tags')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001654 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1655 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001656 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001657 cmd.append(tag_name)
1658 else:
1659 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001660 if is_sha1:
1661 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001662 if branch.startswith(R_HEADS):
1663 branch = branch[len(R_HEADS):]
1664 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001665
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001666 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001667 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001668 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1669 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001670 ok = True
1671 break
Brian Harring14a66742012-09-28 20:21:57 -07001672 elif current_branch_only and is_sha1 and ret == 128:
1673 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1674 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1675 # abort the optimization attempt and do a full sync.
1676 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001677 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001678
1679 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001680 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001681 if old_packed != '':
1682 _lwrite(packed_refs, old_packed)
1683 else:
1684 os.remove(packed_refs)
1685 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001686
1687 if is_sha1 and current_branch_only and self.upstream:
1688 # We just synced the upstream given branch; verify we
1689 # got what we wanted, else trigger a second run of all
1690 # refs.
1691 if not CheckForSha1():
1692 return self._RemoteFetch(name=name, current_branch_only=False,
1693 initial=False, quiet=quiet, alt_dir=alt_dir)
1694
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001695 return ok
1696
1697 def _ApplyCloneBundle(self, initial=False, quiet=False):
1698 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1699 return False
1700
1701 remote = self.GetRemote(self.remote.name)
1702 bundle_url = remote.url + '/clone.bundle'
1703 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001704 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1705 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001706 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1707 return False
1708
1709 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1710 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001711
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001712 exist_dst = os.path.exists(bundle_dst)
1713 exist_tmp = os.path.exists(bundle_tmp)
1714
1715 if not initial and not exist_dst and not exist_tmp:
1716 return False
1717
1718 if not exist_dst:
1719 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1720 if not exist_dst:
1721 return False
1722
1723 cmd = ['fetch']
1724 if quiet:
1725 cmd.append('--quiet')
1726 if not self.worktree:
1727 cmd.append('--update-head-ok')
1728 cmd.append(bundle_dst)
1729 for f in remote.fetch:
1730 cmd.append(str(f))
1731 cmd.append('refs/tags/*:refs/tags/*')
1732
1733 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001734 if os.path.exists(bundle_dst):
1735 os.remove(bundle_dst)
1736 if os.path.exists(bundle_tmp):
1737 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001738 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001739
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001740 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001741 if os.path.exists(dstPath):
1742 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001743
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001744 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001745 if quiet:
1746 cmd += ['--silent']
1747 if os.path.exists(tmpPath):
1748 size = os.stat(tmpPath).st_size
1749 if size >= 1024:
1750 cmd += ['--continue-at', '%d' % (size,)]
1751 else:
1752 os.remove(tmpPath)
1753 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1754 cmd += ['--proxy', os.environ['http_proxy']]
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001755 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
1756 if cookiefile:
1757 cmd += ['--cookie', cookiefile]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001758 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001759
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001760 if IsTrace():
1761 Trace('%s', ' '.join(cmd))
1762 try:
1763 proc = subprocess.Popen(cmd)
1764 except OSError:
1765 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001766
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001767 curlret = proc.wait()
1768
1769 if curlret == 22:
1770 # From curl man page:
1771 # 22: HTTP page not retrieved. The requested url was not found or
1772 # returned another error with the HTTP error code being 400 or above.
1773 # This return code only appears if -f, --fail is used.
1774 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001775 print("Server does not provide clone.bundle; ignoring.",
1776 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001777 return False
1778
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001779 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001780 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001781 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001782 return True
1783 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001784 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001785 return False
1786 else:
1787 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001788
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001789 def _Checkout(self, rev, quiet=False):
1790 cmd = ['checkout']
1791 if quiet:
1792 cmd.append('-q')
1793 cmd.append(rev)
1794 cmd.append('--')
1795 if GitCommand(self, cmd).Wait() != 0:
1796 if self._allrefs:
1797 raise GitError('%s checkout %s ' % (self.name, rev))
1798
Pierre Tardye5a21222011-03-24 16:28:18 +01001799 def _CherryPick(self, rev, quiet=False):
1800 cmd = ['cherry-pick']
1801 cmd.append(rev)
1802 cmd.append('--')
1803 if GitCommand(self, cmd).Wait() != 0:
1804 if self._allrefs:
1805 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1806
Erwan Mahea94f1622011-08-19 13:56:09 +02001807 def _Revert(self, rev, quiet=False):
1808 cmd = ['revert']
1809 cmd.append('--no-edit')
1810 cmd.append(rev)
1811 cmd.append('--')
1812 if GitCommand(self, cmd).Wait() != 0:
1813 if self._allrefs:
1814 raise GitError('%s revert %s ' % (self.name, rev))
1815
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001816 def _ResetHard(self, rev, quiet=True):
1817 cmd = ['reset', '--hard']
1818 if quiet:
1819 cmd.append('-q')
1820 cmd.append(rev)
1821 if GitCommand(self, cmd).Wait() != 0:
1822 raise GitError('%s reset --hard %s ' % (self.name, rev))
1823
1824 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001825 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001826 if onto is not None:
1827 cmd.extend(['--onto', onto])
1828 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001829 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001830 raise GitError('%s rebase %s ' % (self.name, upstream))
1831
Pierre Tardy3d125942012-05-04 12:18:12 +02001832 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001833 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001834 if ffonly:
1835 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001836 if GitCommand(self, cmd).Wait() != 0:
1837 raise GitError('%s merge %s ' % (self.name, head))
1838
1839 def _InitGitDir(self):
1840 if not os.path.exists(self.gitdir):
1841 os.makedirs(self.gitdir)
1842 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001843
Shawn O. Pearce88443382010-10-08 10:02:09 +02001844 mp = self.manifest.manifestProject
1845 ref_dir = mp.config.GetString('repo.reference')
1846
1847 if ref_dir:
1848 mirror_git = os.path.join(ref_dir, self.name + '.git')
1849 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1850 self.relpath + '.git')
1851
1852 if os.path.exists(mirror_git):
1853 ref_dir = mirror_git
1854
1855 elif os.path.exists(repo_git):
1856 ref_dir = repo_git
1857
1858 else:
1859 ref_dir = None
1860
1861 if ref_dir:
1862 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1863 os.path.join(ref_dir, 'objects') + '\n')
1864
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001865 if self.manifest.IsMirror:
1866 self.config.SetString('core.bare', 'true')
1867 else:
1868 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001869
1870 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001871 try:
1872 to_rm = os.listdir(hooks)
1873 except OSError:
1874 to_rm = []
1875 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001876 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001877 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001878
1879 m = self.manifest.manifestProject.config
1880 for key in ['user.name', 'user.email']:
1881 if m.Has(key, include_defaults = False):
1882 self.config.SetString(key, m.GetString(key))
1883
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001884 def _InitHooks(self):
1885 hooks = self._gitdir_path('hooks')
1886 if not os.path.exists(hooks):
1887 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001888 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001889 name = os.path.basename(stock_hook)
1890
Victor Boivie65e0f352011-04-18 11:23:29 +02001891 if name in ('commit-msg',) and not self.remote.review \
1892 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001893 # Don't install a Gerrit Code Review hook if this
1894 # project does not appear to use it for reviews.
1895 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001896 # Since the manifest project is one of those, but also
1897 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001898 continue
1899
1900 dst = os.path.join(hooks, name)
1901 if os.path.islink(dst):
1902 continue
1903 if os.path.exists(dst):
1904 if filecmp.cmp(stock_hook, dst, shallow=False):
1905 os.remove(dst)
1906 else:
1907 _error("%s: Not replacing %s hook", self.relpath, name)
1908 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001909 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001910 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001911 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001912 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001913 raise GitError('filesystem must support symlinks')
1914 else:
1915 raise
1916
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001917 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001918 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001919 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001920 remote.url = self.remote.url
1921 remote.review = self.remote.review
1922 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001923
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001924 if self.worktree:
1925 remote.ResetFetch(mirror=False)
1926 else:
1927 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001928 remote.Save()
1929
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001930 def _InitMRef(self):
1931 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001932 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001933
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001934 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001935 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001936
1937 def _InitAnyMRef(self, ref):
1938 cur = self.bare_ref.symref(ref)
1939
1940 if self.revisionId:
1941 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1942 msg = 'manifest set to %s' % self.revisionId
1943 dst = self.revisionId + '^0'
1944 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1945 else:
1946 remote = self.GetRemote(self.remote.name)
1947 dst = remote.ToLocal(self.revisionExpr)
1948 if cur != dst:
1949 msg = 'manifest set to %s' % self.revisionExpr
1950 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001951
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001952 def _InitWorkTree(self):
1953 dotgit = os.path.join(self.worktree, '.git')
1954 if not os.path.exists(dotgit):
1955 os.makedirs(dotgit)
1956
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001957 for name in ['config',
1958 'description',
1959 'hooks',
1960 'info',
1961 'logs',
1962 'objects',
1963 'packed-refs',
1964 'refs',
1965 'rr-cache',
1966 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001967 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001968 src = os.path.join(self.gitdir, name)
1969 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001970 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001971 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001972 else:
1973 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001974 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001975 if e.errno == errno.EPERM:
1976 raise GitError('filesystem must support symlinks')
1977 else:
1978 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001979
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001980 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001981
1982 cmd = ['read-tree', '--reset', '-u']
1983 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001984 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001985 if GitCommand(self, cmd).Wait() != 0:
1986 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001987
1988 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1989 if not os.path.exists(rr_cache):
1990 os.makedirs(rr_cache)
1991
Shawn O. Pearce93609662009-04-21 10:50:33 -07001992 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001993
1994 def _gitdir_path(self, path):
1995 return os.path.join(self.gitdir, path)
1996
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001997 def _revlist(self, *args, **kw):
1998 a = []
1999 a.extend(args)
2000 a.append('--')
2001 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002002
2003 @property
2004 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002005 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002006
2007 class _GitGetByExec(object):
2008 def __init__(self, project, bare):
2009 self._project = project
2010 self._bare = bare
2011
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002012 def LsOthers(self):
2013 p = GitCommand(self._project,
2014 ['ls-files',
2015 '-z',
2016 '--others',
2017 '--exclude-standard'],
2018 bare = False,
2019 capture_stdout = True,
2020 capture_stderr = True)
2021 if p.Wait() == 0:
2022 out = p.stdout
2023 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002024 return out[:-1].split('\0') # pylint: disable=W1401
2025 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002026 return []
2027
2028 def DiffZ(self, name, *args):
2029 cmd = [name]
2030 cmd.append('-z')
2031 cmd.extend(args)
2032 p = GitCommand(self._project,
2033 cmd,
2034 bare = False,
2035 capture_stdout = True,
2036 capture_stderr = True)
2037 try:
2038 out = p.process.stdout.read()
2039 r = {}
2040 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002041 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002042 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002043 try:
2044 info = out.next()
2045 path = out.next()
2046 except StopIteration:
2047 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002048
2049 class _Info(object):
2050 def __init__(self, path, omode, nmode, oid, nid, state):
2051 self.path = path
2052 self.src_path = None
2053 self.old_mode = omode
2054 self.new_mode = nmode
2055 self.old_id = oid
2056 self.new_id = nid
2057
2058 if len(state) == 1:
2059 self.status = state
2060 self.level = None
2061 else:
2062 self.status = state[:1]
2063 self.level = state[1:]
2064 while self.level.startswith('0'):
2065 self.level = self.level[1:]
2066
2067 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002068 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002069 if info.status in ('R', 'C'):
2070 info.src_path = info.path
2071 info.path = out.next()
2072 r[info.path] = info
2073 return r
2074 finally:
2075 p.Wait()
2076
2077 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002078 if self._bare:
2079 path = os.path.join(self._project.gitdir, HEAD)
2080 else:
2081 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002082 try:
2083 fd = open(path, 'rb')
2084 except IOError:
2085 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002086 try:
2087 line = fd.read()
2088 finally:
2089 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002090 if line.startswith('ref: '):
2091 return line[5:-1]
2092 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002093
2094 def SetHead(self, ref, message=None):
2095 cmdv = []
2096 if message is not None:
2097 cmdv.extend(['-m', message])
2098 cmdv.append(HEAD)
2099 cmdv.append(ref)
2100 self.symbolic_ref(*cmdv)
2101
2102 def DetachHead(self, new, message=None):
2103 cmdv = ['--no-deref']
2104 if message is not None:
2105 cmdv.extend(['-m', message])
2106 cmdv.append(HEAD)
2107 cmdv.append(new)
2108 self.update_ref(*cmdv)
2109
2110 def UpdateRef(self, name, new, old=None,
2111 message=None,
2112 detach=False):
2113 cmdv = []
2114 if message is not None:
2115 cmdv.extend(['-m', message])
2116 if detach:
2117 cmdv.append('--no-deref')
2118 cmdv.append(name)
2119 cmdv.append(new)
2120 if old is not None:
2121 cmdv.append(old)
2122 self.update_ref(*cmdv)
2123
2124 def DeleteRef(self, name, old=None):
2125 if not old:
2126 old = self.rev_parse(name)
2127 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002128 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002129
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002130 def rev_list(self, *args, **kw):
2131 if 'format' in kw:
2132 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2133 else:
2134 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002135 cmdv.extend(args)
2136 p = GitCommand(self._project,
2137 cmdv,
2138 bare = self._bare,
2139 capture_stdout = True,
2140 capture_stderr = True)
2141 r = []
2142 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002143 if line[-1] == '\n':
2144 line = line[:-1]
2145 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002146 if p.Wait() != 0:
2147 raise GitError('%s rev-list %s: %s' % (
2148 self._project.name,
2149 str(args),
2150 p.stderr))
2151 return r
2152
2153 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002154 """Allow arbitrary git commands using pythonic syntax.
2155
2156 This allows you to do things like:
2157 git_obj.rev_parse('HEAD')
2158
2159 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2160 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002161 Any other positional arguments will be passed to the git command, and the
2162 following keyword arguments are supported:
2163 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002164
2165 Args:
2166 name: The name of the git command to call. Any '_' characters will
2167 be replaced with '-'.
2168
2169 Returns:
2170 A callable object that will try to call git with the named command.
2171 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002172 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002173 def runner(*args, **kwargs):
2174 cmdv = []
2175 config = kwargs.pop('config', None)
2176 for k in kwargs:
2177 raise TypeError('%s() got an unexpected keyword argument %r'
2178 % (name, k))
2179 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002180 if not git_require((1, 7, 2)):
2181 raise ValueError('cannot set config on command line for %s()'
2182 % name)
Dave Borowitz091f8932012-10-23 17:01:04 -07002183 for k, v in config.iteritems():
2184 cmdv.append('-c')
2185 cmdv.append('%s=%s' % (k, v))
2186 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002187 cmdv.extend(args)
2188 p = GitCommand(self._project,
2189 cmdv,
2190 bare = self._bare,
2191 capture_stdout = True,
2192 capture_stderr = True)
2193 if p.Wait() != 0:
2194 raise GitError('%s %s: %s' % (
2195 self._project.name,
2196 name,
2197 p.stderr))
2198 r = p.stdout
2199 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2200 return r[:-1]
2201 return r
2202 return runner
2203
2204
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002205class _PriorSyncFailedError(Exception):
2206 def __str__(self):
2207 return 'prior sync failed; rebase still in progress'
2208
2209class _DirtyError(Exception):
2210 def __str__(self):
2211 return 'contains uncommitted changes'
2212
2213class _InfoMessage(object):
2214 def __init__(self, project, text):
2215 self.project = project
2216 self.text = text
2217
2218 def Print(self, syncbuf):
2219 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2220 syncbuf.out.nl()
2221
2222class _Failure(object):
2223 def __init__(self, project, why):
2224 self.project = project
2225 self.why = why
2226
2227 def Print(self, syncbuf):
2228 syncbuf.out.fail('error: %s/: %s',
2229 self.project.relpath,
2230 str(self.why))
2231 syncbuf.out.nl()
2232
2233class _Later(object):
2234 def __init__(self, project, action):
2235 self.project = project
2236 self.action = action
2237
2238 def Run(self, syncbuf):
2239 out = syncbuf.out
2240 out.project('project %s/', self.project.relpath)
2241 out.nl()
2242 try:
2243 self.action()
2244 out.nl()
2245 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002246 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002247 out.nl()
2248 return False
2249
2250class _SyncColoring(Coloring):
2251 def __init__(self, config):
2252 Coloring.__init__(self, config, 'reposync')
2253 self.project = self.printer('header', attr = 'bold')
2254 self.info = self.printer('info')
2255 self.fail = self.printer('fail', fg='red')
2256
2257class SyncBuffer(object):
2258 def __init__(self, config, detach_head=False):
2259 self._messages = []
2260 self._failures = []
2261 self._later_queue1 = []
2262 self._later_queue2 = []
2263
2264 self.out = _SyncColoring(config)
2265 self.out.redirect(sys.stderr)
2266
2267 self.detach_head = detach_head
2268 self.clean = True
2269
2270 def info(self, project, fmt, *args):
2271 self._messages.append(_InfoMessage(project, fmt % args))
2272
2273 def fail(self, project, err=None):
2274 self._failures.append(_Failure(project, err))
2275 self.clean = False
2276
2277 def later1(self, project, what):
2278 self._later_queue1.append(_Later(project, what))
2279
2280 def later2(self, project, what):
2281 self._later_queue2.append(_Later(project, what))
2282
2283 def Finish(self):
2284 self._PrintMessages()
2285 self._RunLater()
2286 self._PrintMessages()
2287 return self.clean
2288
2289 def _RunLater(self):
2290 for q in ['_later_queue1', '_later_queue2']:
2291 if not self._RunQueue(q):
2292 return
2293
2294 def _RunQueue(self, queue):
2295 for m in getattr(self, queue):
2296 if not m.Run(self):
2297 self.clean = False
2298 return False
2299 setattr(self, queue, [])
2300 return True
2301
2302 def _PrintMessages(self):
2303 for m in self._messages:
2304 m.Print(self)
2305 for m in self._failures:
2306 m.Print(self)
2307
2308 self._messages = []
2309 self._failures = []
2310
2311
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002312class MetaProject(Project):
2313 """A special project housed under .repo.
2314 """
2315 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002316 Project.__init__(self,
2317 manifest = manifest,
2318 name = name,
2319 gitdir = gitdir,
2320 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002321 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002322 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002323 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002324 revisionId = None,
2325 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002326
2327 def PreSync(self):
2328 if self.Exists:
2329 cb = self.CurrentBranch
2330 if cb:
2331 base = self.GetBranch(cb).merge
2332 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002333 self.revisionExpr = base
2334 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002335
Florian Vallee5d016502012-06-07 17:19:26 +02002336 def MetaBranchSwitch(self, target):
2337 """ Prepare MetaProject for manifest branch switch
2338 """
2339
2340 # detach and delete manifest branch, allowing a new
2341 # branch to take over
2342 syncbuf = SyncBuffer(self.config, detach_head = True)
2343 self.Sync_LocalHalf(syncbuf)
2344 syncbuf.Finish()
2345
2346 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002347 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002348 capture_stdout = True,
2349 capture_stderr = True).Wait() == 0
2350
2351
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002352 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002353 def LastFetch(self):
2354 try:
2355 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2356 return os.path.getmtime(fh)
2357 except OSError:
2358 return 0
2359
2360 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002361 def HasChanges(self):
2362 """Has the remote received new commits not yet checked out?
2363 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002364 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002365 return False
2366
David Pursehouse8a68ff92012-09-24 12:15:13 +09002367 all_refs = self.bare_ref.all
2368 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002369 head = self.work_git.GetHead()
2370 if head.startswith(R_HEADS):
2371 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002372 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002373 except KeyError:
2374 head = None
2375
2376 if revid == head:
2377 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002378 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002379 return True
2380 return False