blob: 46b76118cb5d43a33d703da86110109e57783b09 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Doug Anderson37282b42011-03-04 11:54:18 -080016import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080017import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import filecmp
19import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080026import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070027import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070028
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070029from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070030from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070031from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090032from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080033from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080034from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070035from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036
Shawn O. Pearced237b692009-04-17 18:49:50 -070037from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070038
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070039def _lwrite(path, content):
40 lock = '%s.lock' % path
41
42 fd = open(lock, 'wb')
43 try:
44 fd.write(content)
45 finally:
46 fd.close()
47
48 try:
49 os.rename(lock, path)
50 except OSError:
51 os.remove(lock)
52 raise
53
Shawn O. Pearce48244782009-04-16 08:25:57 -070054def _error(fmt, *args):
55 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070056 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070057
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070058def not_rev(r):
59 return '^' + r
60
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080061def sq(r):
62 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080063
Doug Anderson8ced8642011-01-10 14:16:30 -080064_project_hook_list = None
65def _ProjectHooks():
66 """List the hooks present in the 'hooks' directory.
67
68 These hooks are project hooks and are copied to the '.git/hooks' directory
69 of all subprojects.
70
71 This function caches the list of hooks (based on the contents of the
72 'repo/hooks' directory) on the first call.
73
74 Returns:
75 A list of absolute paths to all of the files in the hooks directory.
76 """
77 global _project_hook_list
78 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080079 d = os.path.abspath(os.path.dirname(__file__))
80 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080081 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
82 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080083
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080084
Shawn O. Pearce632768b2008-10-23 11:58:52 -070085class DownloadedChange(object):
86 _commit_cache = None
87
88 def __init__(self, project, base, change_id, ps_id, commit):
89 self.project = project
90 self.base = base
91 self.change_id = change_id
92 self.ps_id = ps_id
93 self.commit = commit
94
95 @property
96 def commits(self):
97 if self._commit_cache is None:
98 self._commit_cache = self.project.bare_git.rev_list(
99 '--abbrev=8',
100 '--abbrev-commit',
101 '--pretty=oneline',
102 '--reverse',
103 '--date-order',
104 not_rev(self.base),
105 self.commit,
106 '--')
107 return self._commit_cache
108
109
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700110class ReviewableBranch(object):
111 _commit_cache = None
112
113 def __init__(self, project, branch, base):
114 self.project = project
115 self.branch = branch
116 self.base = base
117
118 @property
119 def name(self):
120 return self.branch.name
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
125 self._commit_cache = self.project.bare_git.rev_list(
126 '--abbrev=8',
127 '--abbrev-commit',
128 '--pretty=oneline',
129 '--reverse',
130 '--date-order',
131 not_rev(self.base),
132 R_HEADS + self.name,
133 '--')
134 return self._commit_cache
135
136 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800137 def unabbrev_commits(self):
138 r = dict()
139 for commit in self.project.bare_git.rev_list(
140 not_rev(self.base),
141 R_HEADS + self.name,
142 '--'):
143 r[commit[0:8]] = commit
144 return r
145
146 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700147 def date(self):
148 return self.project.bare_git.log(
149 '--pretty=format:%cd',
150 '-n', '1',
151 R_HEADS + self.name,
152 '--')
153
Brian Harring435370c2012-07-28 15:37:04 -0700154 def UploadForReview(self, people, auto_topic=False, draft=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800155 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700156 people,
Brian Harring435370c2012-07-28 15:37:04 -0700157 auto_topic=auto_topic,
158 draft=draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700160 def GetPublishedRefs(self):
161 refs = {}
162 output = self.project.bare_git.ls_remote(
163 self.branch.remote.SshReviewUrl(self.project.UserEmail),
164 'refs/changes/*')
165 for line in output.split('\n'):
166 try:
167 (sha, ref) = line.split()
168 refs[sha] = ref
169 except ValueError:
170 pass
171
172 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700173
174class StatusColoring(Coloring):
175 def __init__(self, config):
176 Coloring.__init__(self, config, 'status')
177 self.project = self.printer('header', attr = 'bold')
178 self.branch = self.printer('header', attr = 'bold')
179 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700180 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
182 self.added = self.printer('added', fg = 'green')
183 self.changed = self.printer('changed', fg = 'red')
184 self.untracked = self.printer('untracked', fg = 'red')
185
186
187class DiffColoring(Coloring):
188 def __init__(self, config):
189 Coloring.__init__(self, config, 'diff')
190 self.project = self.printer('header', attr = 'bold')
191
James W. Mills24c13082012-04-12 15:04:13 -0500192class _Annotation:
193 def __init__(self, name, value, keep):
194 self.name = name
195 self.value = value
196 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197
198class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800199 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700200 self.src = src
201 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800202 self.abs_src = abssrc
203 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204
205 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800206 src = self.abs_src
207 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 # copy file if it does not exist or is out of date
209 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
210 try:
211 # remove existing file first, since it might be read-only
212 if os.path.exists(dest):
213 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400214 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200215 dest_dir = os.path.dirname(dest)
216 if not os.path.isdir(dest_dir):
217 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218 shutil.copy(src, dest)
219 # make the file read-only
220 mode = os.stat(dest)[stat.ST_MODE]
221 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
222 os.chmod(dest, mode)
223 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700224 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700226class RemoteSpec(object):
227 def __init__(self,
228 name,
229 url = None,
230 review = None):
231 self.name = name
232 self.url = url
233 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234
Doug Anderson37282b42011-03-04 11:54:18 -0800235class RepoHook(object):
236 """A RepoHook contains information about a script to run as a hook.
237
238 Hooks are used to run a python script before running an upload (for instance,
239 to run presubmit checks). Eventually, we may have hooks for other actions.
240
241 This shouldn't be confused with files in the 'repo/hooks' directory. Those
242 files are copied into each '.git/hooks' folder for each project. Repo-level
243 hooks are associated instead with repo actions.
244
245 Hooks are always python. When a hook is run, we will load the hook into the
246 interpreter and execute its main() function.
247 """
248 def __init__(self,
249 hook_type,
250 hooks_project,
251 topdir,
252 abort_if_user_denies=False):
253 """RepoHook constructor.
254
255 Params:
256 hook_type: A string representing the type of hook. This is also used
257 to figure out the name of the file containing the hook. For
258 example: 'pre-upload'.
259 hooks_project: The project containing the repo hooks. If you have a
260 manifest, this is manifest.repo_hooks_project. OK if this is None,
261 which will make the hook a no-op.
262 topdir: Repo's top directory (the one containing the .repo directory).
263 Scripts will run with CWD as this directory. If you have a manifest,
264 this is manifest.topdir
265 abort_if_user_denies: If True, we'll throw a HookError() if the user
266 doesn't allow us to run the hook.
267 """
268 self._hook_type = hook_type
269 self._hooks_project = hooks_project
270 self._topdir = topdir
271 self._abort_if_user_denies = abort_if_user_denies
272
273 # Store the full path to the script for convenience.
274 if self._hooks_project:
275 self._script_fullpath = os.path.join(self._hooks_project.worktree,
276 self._hook_type + '.py')
277 else:
278 self._script_fullpath = None
279
280 def _GetHash(self):
281 """Return a hash of the contents of the hooks directory.
282
283 We'll just use git to do this. This hash has the property that if anything
284 changes in the directory we will return a different has.
285
286 SECURITY CONSIDERATION:
287 This hash only represents the contents of files in the hook directory, not
288 any other files imported or called by hooks. Changes to imported files
289 can change the script behavior without affecting the hash.
290
291 Returns:
292 A string representing the hash. This will always be ASCII so that it can
293 be printed to the user easily.
294 """
295 assert self._hooks_project, "Must have hooks to calculate their hash."
296
297 # We will use the work_git object rather than just calling GetRevisionId().
298 # That gives us a hash of the latest checked in version of the files that
299 # the user will actually be executing. Specifically, GetRevisionId()
300 # doesn't appear to change even if a user checks out a different version
301 # of the hooks repo (via git checkout) nor if a user commits their own revs.
302 #
303 # NOTE: Local (non-committed) changes will not be factored into this hash.
304 # I think this is OK, since we're really only worried about warning the user
305 # about upstream changes.
306 return self._hooks_project.work_git.rev_parse('HEAD')
307
308 def _GetMustVerb(self):
309 """Return 'must' if the hook is required; 'should' if not."""
310 if self._abort_if_user_denies:
311 return 'must'
312 else:
313 return 'should'
314
315 def _CheckForHookApproval(self):
316 """Check to see whether this hook has been approved.
317
318 We'll look at the hash of all of the hooks. If this matches the hash that
319 the user last approved, we're done. If it doesn't, we'll ask the user
320 about approval.
321
322 Note that we ask permission for each individual hook even though we use
323 the hash of all hooks when detecting changes. We'd like the user to be
324 able to approve / deny each hook individually. We only use the hash of all
325 hooks because there is no other easy way to detect changes to local imports.
326
327 Returns:
328 True if this hook is approved to run; False otherwise.
329
330 Raises:
331 HookError: Raised if the user doesn't approve and abort_if_user_denies
332 was passed to the consturctor.
333 """
Doug Anderson37282b42011-03-04 11:54:18 -0800334 hooks_config = self._hooks_project.config
335 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
336
337 # Get the last hash that the user approved for this hook; may be None.
338 old_hash = hooks_config.GetString(git_approval_key)
339
340 # Get the current hash so we can tell if scripts changed since approval.
341 new_hash = self._GetHash()
342
343 if old_hash is not None:
344 # User previously approved hook and asked not to be prompted again.
345 if new_hash == old_hash:
346 # Approval matched. We're done.
347 return True
348 else:
349 # Give the user a reason why we're prompting, since they last told
350 # us to "never ask again".
351 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
352 self._hook_type)
353 else:
354 prompt = ''
355
356 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
357 if sys.stdout.isatty():
358 prompt += ('Repo %s run the script:\n'
359 ' %s\n'
360 '\n'
361 'Do you want to allow this script to run '
362 '(yes/yes-never-ask-again/NO)? ') % (
363 self._GetMustVerb(), self._script_fullpath)
364 response = raw_input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900365 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800366
367 # User is doing a one-time approval.
368 if response in ('y', 'yes'):
369 return True
370 elif response == 'yes-never-ask-again':
371 hooks_config.SetString(git_approval_key, new_hash)
372 return True
373
374 # For anything else, we'll assume no approval.
375 if self._abort_if_user_denies:
376 raise HookError('You must allow the %s hook or use --no-verify.' %
377 self._hook_type)
378
379 return False
380
381 def _ExecuteHook(self, **kwargs):
382 """Actually execute the given hook.
383
384 This will run the hook's 'main' function in our python interpreter.
385
386 Args:
387 kwargs: Keyword arguments to pass to the hook. These are often specific
388 to the hook type. For instance, pre-upload hooks will contain
389 a project_list.
390 """
391 # Keep sys.path and CWD stashed away so that we can always restore them
392 # upon function exit.
393 orig_path = os.getcwd()
394 orig_syspath = sys.path
395
396 try:
397 # Always run hooks with CWD as topdir.
398 os.chdir(self._topdir)
399
400 # Put the hook dir as the first item of sys.path so hooks can do
401 # relative imports. We want to replace the repo dir as [0] so
402 # hooks can't import repo files.
403 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
404
405 # Exec, storing global context in the context dict. We catch exceptions
406 # and convert to a HookError w/ just the failing traceback.
407 context = {}
408 try:
409 execfile(self._script_fullpath, context)
410 except Exception:
411 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
412 traceback.format_exc(), self._hook_type))
413
414 # Running the script should have defined a main() function.
415 if 'main' not in context:
416 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
417
418
419 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
420 # We don't actually want hooks to define their main with this argument--
421 # it's there to remind them that their hook should always take **kwargs.
422 # For instance, a pre-upload hook should be defined like:
423 # def main(project_list, **kwargs):
424 #
425 # This allows us to later expand the API without breaking old hooks.
426 kwargs = kwargs.copy()
427 kwargs['hook_should_take_kwargs'] = True
428
429 # Call the main function in the hook. If the hook should cause the
430 # build to fail, it will raise an Exception. We'll catch that convert
431 # to a HookError w/ just the failing traceback.
432 try:
433 context['main'](**kwargs)
434 except Exception:
435 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
436 'above.' % (
437 traceback.format_exc(), self._hook_type))
438 finally:
439 # Restore sys.path and CWD.
440 sys.path = orig_syspath
441 os.chdir(orig_path)
442
443 def Run(self, user_allows_all_hooks, **kwargs):
444 """Run the hook.
445
446 If the hook doesn't exist (because there is no hooks project or because
447 this particular hook is not enabled), this is a no-op.
448
449 Args:
450 user_allows_all_hooks: If True, we will never prompt about running the
451 hook--we'll just assume it's OK to run it.
452 kwargs: Keyword arguments to pass to the hook. These are often specific
453 to the hook type. For instance, pre-upload hooks will contain
454 a project_list.
455
456 Raises:
457 HookError: If there was a problem finding the hook or the user declined
458 to run a required hook (from _CheckForHookApproval).
459 """
460 # No-op if there is no hooks project or if hook is disabled.
461 if ((not self._hooks_project) or
462 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
463 return
464
465 # Bail with a nice error if we can't find the hook.
466 if not os.path.isfile(self._script_fullpath):
467 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
468
469 # Make sure the user is OK with running the hook.
470 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
471 return
472
473 # Run the hook with the same version of python we're using.
474 self._ExecuteHook(**kwargs)
475
476
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700477class Project(object):
478 def __init__(self,
479 manifest,
480 name,
481 remote,
482 gitdir,
483 worktree,
484 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700485 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800486 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700487 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700488 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700489 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800490 sync_s = False,
491 upstream = None,
492 parent = None,
493 is_derived = False):
494 """Init a Project object.
495
496 Args:
497 manifest: The XmlManifest object.
498 name: The `name` attribute of manifest.xml's project element.
499 remote: RemoteSpec object specifying its remote's properties.
500 gitdir: Absolute path of git directory.
501 worktree: Absolute path of git working tree.
502 relpath: Relative path of git working tree to repo's top directory.
503 revisionExpr: The `revision` attribute of manifest.xml's project element.
504 revisionId: git commit id for checking out.
505 rebase: The `rebase` attribute of manifest.xml's project element.
506 groups: The `groups` attribute of manifest.xml's project element.
507 sync_c: The `sync-c` attribute of manifest.xml's project element.
508 sync_s: The `sync-s` attribute of manifest.xml's project element.
509 upstream: The `upstream` attribute of manifest.xml's project element.
510 parent: The parent Project object.
511 is_derived: False if the project was explicitly defined in the manifest;
512 True if the project is a discovered submodule.
513 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700514 self.manifest = manifest
515 self.name = name
516 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800517 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800518 if worktree:
519 self.worktree = worktree.replace('\\', '/')
520 else:
521 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700522 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700523 self.revisionExpr = revisionExpr
524
525 if revisionId is None \
526 and revisionExpr \
527 and IsId(revisionExpr):
528 self.revisionId = revisionExpr
529 else:
530 self.revisionId = revisionId
531
Mike Pontillod3153822012-02-28 11:53:24 -0800532 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700533 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700534 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800535 self.sync_s = sync_s
Brian Harring14a66742012-09-28 20:21:57 -0700536 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800537 self.parent = parent
538 self.is_derived = is_derived
539 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800540
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700541 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700542 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500543 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700544 self.config = GitConfig.ForRepository(
545 gitdir = self.gitdir,
546 defaults = self.manifest.globalConfig)
547
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800548 if self.worktree:
549 self.work_git = self._GitGetByExec(self, bare=False)
550 else:
551 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700553 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700554
Doug Anderson37282b42011-03-04 11:54:18 -0800555 # This will be filled in if a project is later identified to be the
556 # project containing repo hooks.
557 self.enabled_repo_hooks = []
558
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700559 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800560 def Derived(self):
561 return self.is_derived
562
563 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700564 def Exists(self):
565 return os.path.isdir(self.gitdir)
566
567 @property
568 def CurrentBranch(self):
569 """Obtain the name of the currently checked out branch.
570 The branch name omits the 'refs/heads/' prefix.
571 None is returned if the project is on a detached HEAD.
572 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700573 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700574 if b.startswith(R_HEADS):
575 return b[len(R_HEADS):]
576 return None
577
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700578 def IsRebaseInProgress(self):
579 w = self.worktree
580 g = os.path.join(w, '.git')
581 return os.path.exists(os.path.join(g, 'rebase-apply')) \
582 or os.path.exists(os.path.join(g, 'rebase-merge')) \
583 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200584
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700585 def IsDirty(self, consider_untracked=True):
586 """Is the working directory modified in some way?
587 """
588 self.work_git.update_index('-q',
589 '--unmerged',
590 '--ignore-missing',
591 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900592 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700593 return True
594 if self.work_git.DiffZ('diff-files'):
595 return True
596 if consider_untracked and self.work_git.LsOthers():
597 return True
598 return False
599
600 _userident_name = None
601 _userident_email = None
602
603 @property
604 def UserName(self):
605 """Obtain the user's personal name.
606 """
607 if self._userident_name is None:
608 self._LoadUserIdentity()
609 return self._userident_name
610
611 @property
612 def UserEmail(self):
613 """Obtain the user's email address. This is very likely
614 to be their Gerrit login.
615 """
616 if self._userident_email is None:
617 self._LoadUserIdentity()
618 return self._userident_email
619
620 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900621 u = self.bare_git.var('GIT_COMMITTER_IDENT')
622 m = re.compile("^(.*) <([^>]*)> ").match(u)
623 if m:
624 self._userident_name = m.group(1)
625 self._userident_email = m.group(2)
626 else:
627 self._userident_name = ''
628 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700629
630 def GetRemote(self, name):
631 """Get the configuration for a single remote.
632 """
633 return self.config.GetRemote(name)
634
635 def GetBranch(self, name):
636 """Get the configuration for a single branch.
637 """
638 return self.config.GetBranch(name)
639
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700640 def GetBranches(self):
641 """Get all existing local branches.
642 """
643 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900644 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700645 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700646
David Pursehouse8a68ff92012-09-24 12:15:13 +0900647 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700648 if name.startswith(R_HEADS):
649 name = name[len(R_HEADS):]
650 b = self.GetBranch(name)
651 b.current = name == current
652 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900653 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700654 heads[name] = b
655
David Pursehouse8a68ff92012-09-24 12:15:13 +0900656 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700657 if name.startswith(R_PUB):
658 name = name[len(R_PUB):]
659 b = heads.get(name)
660 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900661 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700662
663 return heads
664
Colin Cross5acde752012-03-28 20:15:45 -0700665 def MatchesGroups(self, manifest_groups):
666 """Returns true if the manifest groups specified at init should cause
667 this project to be synced.
668 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700669 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700670
671 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700672 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700673 manifest_groups: "-group1,group2"
674 the project will be matched.
Colin Cross5acde752012-03-28 20:15:45 -0700675 """
Conley Owensbb1b5f52012-08-13 13:11:18 -0700676 expanded_manifest_groups = manifest_groups or ['all', '-notdefault']
677 expanded_project_groups = ['all'] + (self.groups or [])
678
Conley Owens971de8e2012-04-16 10:36:08 -0700679 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700680 for group in expanded_manifest_groups:
681 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700682 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700683 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700684 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700685
Conley Owens971de8e2012-04-16 10:36:08 -0700686 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700687
688## Status Display ##
689
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500690 def HasChanges(self):
691 """Returns true if there are uncommitted changes.
692 """
693 self.work_git.update_index('-q',
694 '--unmerged',
695 '--ignore-missing',
696 '--refresh')
697 if self.IsRebaseInProgress():
698 return True
699
700 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
701 return True
702
703 if self.work_git.DiffZ('diff-files'):
704 return True
705
706 if self.work_git.LsOthers():
707 return True
708
709 return False
710
Terence Haddock4655e812011-03-31 12:33:34 +0200711 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200713
714 Args:
715 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700716 """
717 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200718 if output_redir == None:
719 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700720 print(file=output_redir)
721 print('project %s/' % self.relpath, file=output_redir)
722 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700723 return
724
725 self.work_git.update_index('-q',
726 '--unmerged',
727 '--ignore-missing',
728 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700729 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700730 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
731 df = self.work_git.DiffZ('diff-files')
732 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100733 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700734 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735
736 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200737 if not output_redir == None:
738 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700739 out.project('project %-40s', self.relpath + '/')
740
741 branch = self.CurrentBranch
742 if branch is None:
743 out.nobranch('(*** NO BRANCH ***)')
744 else:
745 out.branch('branch %s', branch)
746 out.nl()
747
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700748 if rb:
749 out.important('prior sync failed; rebase still in progress')
750 out.nl()
751
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700752 paths = list()
753 paths.extend(di.keys())
754 paths.extend(df.keys())
755 paths.extend(do)
756
757 paths = list(set(paths))
758 paths.sort()
759
760 for p in paths:
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900761 try:
762 i = di[p]
763 except KeyError:
764 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700765
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900766 try:
767 f = df[p]
768 except KeyError:
769 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200770
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900771 if i:
772 i_status = i.status.upper()
773 else:
774 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900776 if f:
777 f_status = f.status.lower()
778 else:
779 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780
781 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800782 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700783 i.src_path, p, i.level)
784 else:
785 line = ' %s%s\t%s' % (i_status, f_status, p)
786
787 if i and not f:
788 out.added('%s', line)
789 elif (i and f) or (not i and f):
790 out.changed('%s', line)
791 elif not i and not f:
792 out.untracked('%s', line)
793 else:
794 out.write('%s', line)
795 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200796
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700797 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700798
pelyad67872d2012-03-28 14:49:58 +0300799 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700800 """Prints the status of the repository to stdout.
801 """
802 out = DiffColoring(self.config)
803 cmd = ['diff']
804 if out.is_on:
805 cmd.append('--color')
806 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300807 if absolute_paths:
808 cmd.append('--src-prefix=a/%s/' % self.relpath)
809 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810 cmd.append('--')
811 p = GitCommand(self,
812 cmd,
813 capture_stdout = True,
814 capture_stderr = True)
815 has_diff = False
816 for line in p.process.stdout:
817 if not has_diff:
818 out.nl()
819 out.project('project %s/' % self.relpath)
820 out.nl()
821 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700822 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700823 p.Wait()
824
825
826## Publish / Upload ##
827
David Pursehouse8a68ff92012-09-24 12:15:13 +0900828 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829 """Was the branch published (uploaded) for code review?
830 If so, returns the SHA-1 hash of the last published
831 state for the branch.
832 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700833 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900834 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700835 try:
836 return self.bare_git.rev_parse(key)
837 except GitError:
838 return None
839 else:
840 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900841 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700842 except KeyError:
843 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844
David Pursehouse8a68ff92012-09-24 12:15:13 +0900845 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 """Prunes any stale published refs.
847 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900848 if all_refs is None:
849 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700850 heads = set()
851 canrm = {}
David Pursehouse8a68ff92012-09-24 12:15:13 +0900852 for name, ref_id in all_refs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853 if name.startswith(R_HEADS):
854 heads.add(name)
855 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900856 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857
David Pursehouse8a68ff92012-09-24 12:15:13 +0900858 for name, ref_id in canrm.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700859 n = name[len(R_PUB):]
860 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900861 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700863 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700864 """List any branches which can be uploaded for review.
865 """
866 heads = {}
867 pubed = {}
868
David Pursehouse8a68ff92012-09-24 12:15:13 +0900869 for name, ref_id in self._allrefs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700870 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900871 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900873 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874
875 ready = []
David Pursehouse8a68ff92012-09-24 12:15:13 +0900876 for branch, ref_id in heads.iteritems():
877 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700879 if selected_branch and branch != selected_branch:
880 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800882 rb = self.GetUploadableBranch(branch)
883 if rb:
884 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700885 return ready
886
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800887 def GetUploadableBranch(self, branch_name):
888 """Get a single uploadable branch, or None.
889 """
890 branch = self.GetBranch(branch_name)
891 base = branch.LocalMerge
892 if branch.LocalMerge:
893 rb = ReviewableBranch(self, branch, base)
894 if rb.commits:
895 return rb
896 return None
897
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700898 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700899 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700900 auto_topic=False,
901 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902 """Uploads the named branch for code review.
903 """
904 if branch is None:
905 branch = self.CurrentBranch
906 if branch is None:
907 raise GitError('not currently on a branch')
908
909 branch = self.GetBranch(branch)
910 if not branch.LocalMerge:
911 raise GitError('branch %s does not track a remote' % branch.name)
912 if not branch.remote.review:
913 raise GitError('remote %s has no review url' % branch.remote.name)
914
915 dest_branch = branch.merge
916 if not dest_branch.startswith(R_HEADS):
917 dest_branch = R_HEADS + dest_branch
918
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800919 if not branch.remote.projectname:
920 branch.remote.projectname = self.name
921 branch.remote.Save()
922
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800923 url = branch.remote.ReviewUrl(self.UserEmail)
924 if url is None:
925 raise UploadError('review not configured')
926 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800927
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800928 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800929 rp = ['gerrit receive-pack']
930 for e in people[0]:
931 rp.append('--reviewer=%s' % sq(e))
932 for e in people[1]:
933 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800934 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700935
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800936 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800937
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800938 if dest_branch.startswith(R_HEADS):
939 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700940
941 upload_type = 'for'
942 if draft:
943 upload_type = 'drafts'
944
945 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
946 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800947 if auto_topic:
948 ref_spec = ref_spec + '/' + branch.name
949 cmd.append(ref_spec)
950
951 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,
966 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967 """Perform only the network IO portion of the sync process.
968 Local working directory/branch state is not affected.
969 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700970 if is_new is None:
971 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200972 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973 self._InitGitDir()
974 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700975
976 if is_new:
977 alt = os.path.join(self.gitdir, 'objects/info/alternates')
978 try:
979 fd = open(alt, 'rb')
980 try:
981 alt_dir = fd.readline().rstrip()
982 finally:
983 fd.close()
984 except IOError:
985 alt_dir = None
986 else:
987 alt_dir = None
988
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700989 if clone_bundle \
990 and alt_dir is None \
991 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700992 is_new = False
993
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700994 if not current_branch_only:
995 if self.sync_c:
996 current_branch_only = True
997 elif not self.manifest._loaded:
998 # Manifest cannot check defaults until it syncs.
999 current_branch_only = False
1000 elif self.manifest.default.sync_c:
1001 current_branch_only = True
1002
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001003 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1004 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001005 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001006
1007 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001008 self._InitMRef()
1009 else:
1010 self._InitMirrorHead()
1011 try:
1012 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1013 except OSError:
1014 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001016
1017 def PostRepoUpgrade(self):
1018 self._InitHooks()
1019
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001020 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001021 for copyfile in self.copyfiles:
1022 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023
David Pursehouse8a68ff92012-09-24 12:15:13 +09001024 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001025 if self.revisionId:
1026 return self.revisionId
1027
1028 rem = self.GetRemote(self.remote.name)
1029 rev = rem.ToLocal(self.revisionExpr)
1030
David Pursehouse8a68ff92012-09-24 12:15:13 +09001031 if all_refs is not None and rev in all_refs:
1032 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001033
1034 try:
1035 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1036 except GitError:
1037 raise ManifestInvalidRevisionError(
1038 'revision %s in %s not found' % (self.revisionExpr,
1039 self.name))
1040
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001041 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 """Perform only the local IO portion of the sync process.
1043 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001044 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001045 all_refs = self.bare_ref.all
1046 self.CleanPublishedCache(all_refs)
1047 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001048
David Pursehouse1d947b32012-10-25 12:23:11 +09001049 def _doff():
1050 self._FastForward(revid)
1051 self._CopyFiles()
1052
Skyler Kaufman835cd682011-03-08 12:14:41 -08001053 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001054 head = self.work_git.GetHead()
1055 if head.startswith(R_HEADS):
1056 branch = head[len(R_HEADS):]
1057 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001058 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001059 except KeyError:
1060 head = None
1061 else:
1062 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001064 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065 # Currently on a detached HEAD. The user is assumed to
1066 # not have any local modifications worth worrying about.
1067 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001068 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001069 syncbuf.fail(self, _PriorSyncFailedError())
1070 return
1071
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001072 if head == revid:
1073 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001074 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001075 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001076 if not syncbuf.detach_head:
1077 return
1078 else:
1079 lost = self._revlist(not_rev(revid), HEAD)
1080 if lost:
1081 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001082
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001084 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001085 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001086 syncbuf.fail(self, e)
1087 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001089 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001091 if head == revid:
1092 # No changes; don't do anything further.
1093 #
1094 return
1095
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001096 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001098 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001099 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001100 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001102 syncbuf.info(self,
1103 "leaving %s; does not track upstream",
1104 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001105 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001106 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001107 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001108 syncbuf.fail(self, e)
1109 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001110 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001111 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001112
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001113 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001114 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001115 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001116 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117 if not_merged:
1118 if upstream_gain:
1119 # The user has published this branch and some of those
1120 # commits are not yet merged upstream. We do not want
1121 # to rewrite the published commits so we punt.
1122 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001123 syncbuf.fail(self,
1124 "branch %s is published (but not merged) and is now %d commits behind"
1125 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001126 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001127 elif pub == head:
1128 # All published commits are merged, and thus we are a
1129 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001130 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001131 syncbuf.later1(self, _doff)
1132 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001133
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001134 # Examine the local commits not in the remote. Find the
1135 # last one attributed to this user, if any.
1136 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001137 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001138 last_mine = None
1139 cnt_mine = 0
1140 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001141 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001142 if committer_email == self.UserEmail:
1143 last_mine = commit_id
1144 cnt_mine += 1
1145
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001146 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001147 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001148
1149 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001150 syncbuf.fail(self, _DirtyError())
1151 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001152
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001153 # If the upstream switched on us, warn the user.
1154 #
1155 if branch.merge != self.revisionExpr:
1156 if branch.merge and self.revisionExpr:
1157 syncbuf.info(self,
1158 'manifest switched %s...%s',
1159 branch.merge,
1160 self.revisionExpr)
1161 elif branch.merge:
1162 syncbuf.info(self,
1163 'manifest no longer tracks %s',
1164 branch.merge)
1165
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001166 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001168 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001170 syncbuf.info(self,
1171 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001172 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001173
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001174 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001175 if not ID_RE.match(self.revisionExpr):
1176 # in case of manifest sync the revisionExpr might be a SHA1
1177 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001178 branch.Save()
1179
Mike Pontillod3153822012-02-28 11:53:24 -08001180 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001181 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001182 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001183 self._CopyFiles()
1184 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001185 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001186 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001187 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001188 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001189 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001190 syncbuf.fail(self, e)
1191 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001193 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001195 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196 # dest should already be an absolute path, but src is project relative
1197 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001198 abssrc = os.path.join(self.worktree, src)
1199 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200
James W. Mills24c13082012-04-12 15:04:13 -05001201 def AddAnnotation(self, name, value, keep):
1202 self.annotations.append(_Annotation(name, value, keep))
1203
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001204 def DownloadPatchSet(self, change_id, patch_id):
1205 """Download a single patch set of a single change to FETCH_HEAD.
1206 """
1207 remote = self.GetRemote(self.remote.name)
1208
1209 cmd = ['fetch', remote.name]
1210 cmd.append('refs/changes/%2.2d/%d/%d' \
1211 % (change_id % 100, change_id, patch_id))
David Pursehouse7e6dd2d2012-10-25 12:40:51 +09001212 cmd.extend(map(str, remote.fetch))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001213 if GitCommand(self, cmd, bare=True).Wait() != 0:
1214 return None
1215 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001216 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001217 change_id,
1218 patch_id,
1219 self.bare_git.rev_parse('FETCH_HEAD'))
1220
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001221
1222## Branch Management ##
1223
1224 def StartBranch(self, name):
1225 """Create a new branch off the manifest's revision.
1226 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001227 head = self.work_git.GetHead()
1228 if head == (R_HEADS + name):
1229 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001230
David Pursehouse8a68ff92012-09-24 12:15:13 +09001231 all_refs = self.bare_ref.all
1232 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001233 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001234 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001235 capture_stdout = True,
1236 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001237
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001238 branch = self.GetBranch(name)
1239 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001240 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001241 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001242
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001243 if head.startswith(R_HEADS):
1244 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001245 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001246 except KeyError:
1247 head = None
1248
1249 if revid and head and revid == head:
1250 ref = os.path.join(self.gitdir, R_HEADS + name)
1251 try:
1252 os.makedirs(os.path.dirname(ref))
1253 except OSError:
1254 pass
1255 _lwrite(ref, '%s\n' % revid)
1256 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1257 'ref: %s%s\n' % (R_HEADS, name))
1258 branch.Save()
1259 return True
1260
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001261 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001262 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001263 capture_stdout = True,
1264 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001265 branch.Save()
1266 return True
1267 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001268
Wink Saville02d79452009-04-10 13:01:24 -07001269 def CheckoutBranch(self, name):
1270 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001271
1272 Args:
1273 name: The name of the branch to checkout.
1274
1275 Returns:
1276 True if the checkout succeeded; False if it didn't; None if the branch
1277 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001278 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001279 rev = R_HEADS + name
1280 head = self.work_git.GetHead()
1281 if head == rev:
1282 # Already on the branch
1283 #
1284 return True
Wink Saville02d79452009-04-10 13:01:24 -07001285
David Pursehouse8a68ff92012-09-24 12:15:13 +09001286 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001287 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001288 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001289 except KeyError:
1290 # Branch does not exist in this project
1291 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001292 return None
Wink Saville02d79452009-04-10 13:01:24 -07001293
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001294 if head.startswith(R_HEADS):
1295 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001296 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001297 except KeyError:
1298 head = None
1299
1300 if head == revid:
1301 # Same revision; just update HEAD to point to the new
1302 # target branch, but otherwise take no other action.
1303 #
1304 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1305 'ref: %s%s\n' % (R_HEADS, name))
1306 return True
1307
1308 return GitCommand(self,
1309 ['checkout', name, '--'],
1310 capture_stdout = True,
1311 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001312
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001313 def AbandonBranch(self, name):
1314 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001315
1316 Args:
1317 name: The name of the branch to abandon.
1318
1319 Returns:
1320 True if the abandon succeeded; False if it didn't; None if the branch
1321 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001322 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001323 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001324 all_refs = self.bare_ref.all
1325 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001326 # Doesn't exist
1327 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001328
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001329 head = self.work_git.GetHead()
1330 if head == rev:
1331 # We can't destroy the branch while we are sitting
1332 # on it. Switch to a detached HEAD.
1333 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001334 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001335
David Pursehouse8a68ff92012-09-24 12:15:13 +09001336 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001337 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001338 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1339 '%s\n' % revid)
1340 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001341 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001342
1343 return GitCommand(self,
1344 ['branch', '-D', name],
1345 capture_stdout = True,
1346 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001347
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001348 def PruneHeads(self):
1349 """Prune any topic branches already merged into upstream.
1350 """
1351 cb = self.CurrentBranch
1352 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001353 left = self._allrefs
1354 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001355 if name.startswith(R_HEADS):
1356 name = name[len(R_HEADS):]
1357 if cb is None or name != cb:
1358 kill.append(name)
1359
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001360 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001361 if cb is not None \
1362 and not self._revlist(HEAD + '...' + rev) \
1363 and not self.IsDirty(consider_untracked = False):
1364 self.work_git.DetachHead(HEAD)
1365 kill.append(cb)
1366
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001368 old = self.bare_git.GetHead()
1369 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001370 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1371
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001372 try:
1373 self.bare_git.DetachHead(rev)
1374
1375 b = ['branch', '-d']
1376 b.extend(kill)
1377 b = GitCommand(self, b, bare=True,
1378 capture_stdout=True,
1379 capture_stderr=True)
1380 b.Wait()
1381 finally:
1382 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001383 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001384
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001385 for branch in kill:
1386 if (R_HEADS + branch) not in left:
1387 self.CleanPublishedCache()
1388 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389
1390 if cb and cb not in kill:
1391 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001392 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393
1394 kept = []
1395 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001396 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001397 branch = self.GetBranch(branch)
1398 base = branch.LocalMerge
1399 if not base:
1400 base = rev
1401 kept.append(ReviewableBranch(self, branch, base))
1402 return kept
1403
1404
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001405## Submodule Management ##
1406
1407 def GetRegisteredSubprojects(self):
1408 result = []
1409 def rec(subprojects):
1410 if not subprojects:
1411 return
1412 result.extend(subprojects)
1413 for p in subprojects:
1414 rec(p.subprojects)
1415 rec(self.subprojects)
1416 return result
1417
1418 def _GetSubmodules(self):
1419 # Unfortunately we cannot call `git submodule status --recursive` here
1420 # because the working tree might not exist yet, and it cannot be used
1421 # without a working tree in its current implementation.
1422
1423 def get_submodules(gitdir, rev):
1424 # Parse .gitmodules for submodule sub_paths and sub_urls
1425 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1426 if not sub_paths:
1427 return []
1428 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1429 # revision of submodule repository
1430 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1431 submodules = []
1432 for sub_path, sub_url in zip(sub_paths, sub_urls):
1433 try:
1434 sub_rev = sub_revs[sub_path]
1435 except KeyError:
1436 # Ignore non-exist submodules
1437 continue
1438 submodules.append((sub_rev, sub_path, sub_url))
1439 return submodules
1440
1441 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1442 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1443 def parse_gitmodules(gitdir, rev):
1444 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1445 try:
1446 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1447 bare = True, gitdir = gitdir)
1448 except GitError:
1449 return [], []
1450 if p.Wait() != 0:
1451 return [], []
1452
1453 gitmodules_lines = []
1454 fd, temp_gitmodules_path = tempfile.mkstemp()
1455 try:
1456 os.write(fd, p.stdout)
1457 os.close(fd)
1458 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1459 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1460 bare = True, gitdir = gitdir)
1461 if p.Wait() != 0:
1462 return [], []
1463 gitmodules_lines = p.stdout.split('\n')
1464 except GitError:
1465 return [], []
1466 finally:
1467 os.remove(temp_gitmodules_path)
1468
1469 names = set()
1470 paths = {}
1471 urls = {}
1472 for line in gitmodules_lines:
1473 if not line:
1474 continue
1475 m = re_path.match(line)
1476 if m:
1477 names.add(m.group(1))
1478 paths[m.group(1)] = m.group(2)
1479 continue
1480 m = re_url.match(line)
1481 if m:
1482 names.add(m.group(1))
1483 urls[m.group(1)] = m.group(2)
1484 continue
1485 names = sorted(names)
1486 return ([paths.get(name, '') for name in names],
1487 [urls.get(name, '') for name in names])
1488
1489 def git_ls_tree(gitdir, rev, paths):
1490 cmd = ['ls-tree', rev, '--']
1491 cmd.extend(paths)
1492 try:
1493 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1494 bare = True, gitdir = gitdir)
1495 except GitError:
1496 return []
1497 if p.Wait() != 0:
1498 return []
1499 objects = {}
1500 for line in p.stdout.split('\n'):
1501 if not line.strip():
1502 continue
1503 object_rev, object_path = line.split()[2:4]
1504 objects[object_path] = object_rev
1505 return objects
1506
1507 try:
1508 rev = self.GetRevisionId()
1509 except GitError:
1510 return []
1511 return get_submodules(self.gitdir, rev)
1512
1513 def GetDerivedSubprojects(self):
1514 result = []
1515 if not self.Exists:
1516 # If git repo does not exist yet, querying its submodules will
1517 # mess up its states; so return here.
1518 return result
1519 for rev, path, url in self._GetSubmodules():
1520 name = self.manifest.GetSubprojectName(self, path)
1521 project = self.manifest.projects.get(name)
1522 if project:
1523 result.extend(project.GetDerivedSubprojects())
1524 continue
1525 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1526 remote = RemoteSpec(self.remote.name,
1527 url = url,
1528 review = self.remote.review)
1529 subproject = Project(manifest = self.manifest,
1530 name = name,
1531 remote = remote,
1532 gitdir = gitdir,
1533 worktree = worktree,
1534 relpath = relpath,
1535 revisionExpr = self.revisionExpr,
1536 revisionId = rev,
1537 rebase = self.rebase,
1538 groups = self.groups,
1539 sync_c = self.sync_c,
1540 sync_s = self.sync_s,
1541 parent = self,
1542 is_derived = True)
1543 result.append(subproject)
1544 result.extend(subproject.GetDerivedSubprojects())
1545 return result
1546
1547
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001548## Direct Git Commands ##
1549
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001550 def _RemoteFetch(self, name=None,
1551 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001552 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001553 quiet=False,
1554 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001555
1556 is_sha1 = False
1557 tag_name = None
1558
Brian Harring14a66742012-09-28 20:21:57 -07001559 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001560 try:
1561 # if revision (sha or tag) is not present then following function
1562 # throws an error.
1563 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1564 return True
1565 except GitError:
1566 # There is no such persistent revision. We have to fetch it.
1567 return False
Brian Harring14a66742012-09-28 20:21:57 -07001568
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001569 if current_branch_only:
1570 if ID_RE.match(self.revisionExpr) is not None:
1571 is_sha1 = True
1572 elif self.revisionExpr.startswith(R_TAGS):
1573 # this is a tag and its sha1 value should never change
1574 tag_name = self.revisionExpr[len(R_TAGS):]
1575
1576 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001577 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001578 return True
Brian Harring14a66742012-09-28 20:21:57 -07001579 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1580 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001581
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001582 if not name:
1583 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001584
1585 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001586 remote = self.GetRemote(name)
1587 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001588 ssh_proxy = True
1589
Shawn O. Pearce88443382010-10-08 10:02:09 +02001590 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001591 if alt_dir and 'objects' == os.path.basename(alt_dir):
1592 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001593 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1594 remote = self.GetRemote(name)
1595
David Pursehouse8a68ff92012-09-24 12:15:13 +09001596 all_refs = self.bare_ref.all
1597 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001598 tmp = set()
1599
David Pursehouse8a68ff92012-09-24 12:15:13 +09001600 for r, ref_id in GitRefs(ref_dir).all.iteritems():
1601 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001602 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001603 all_refs[r] = ref_id
1604 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001605 continue
1606
David Pursehouse8a68ff92012-09-24 12:15:13 +09001607 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001608 continue
1609
David Pursehouse8a68ff92012-09-24 12:15:13 +09001610 r = 'refs/_alt/%s' % ref_id
1611 all_refs[r] = ref_id
1612 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001613 tmp.add(r)
1614
David Pursehouse8a68ff92012-09-24 12:15:13 +09001615 ref_names = list(all_refs.keys())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001616 ref_names.sort()
1617
1618 tmp_packed = ''
1619 old_packed = ''
1620
1621 for r in ref_names:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001622 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001623 tmp_packed += line
1624 if r not in tmp:
1625 old_packed += line
1626
1627 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001628 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001629 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001630
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001631 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001632
1633 # The --depth option only affects the initial fetch; after that we'll do
1634 # full fetches of changes.
1635 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1636 if depth and initial:
1637 cmd.append('--depth=%s' % depth)
1638
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001639 if quiet:
1640 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001641 if not self.worktree:
1642 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001643 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001644
Brian Harring14a66742012-09-28 20:21:57 -07001645 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001646 # Fetch whole repo
1647 cmd.append('--tags')
1648 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1649 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001650 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001651 cmd.append(tag_name)
1652 else:
1653 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001654 if is_sha1:
1655 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001656 if branch.startswith(R_HEADS):
1657 branch = branch[len(R_HEADS):]
1658 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001659
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001660 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001661 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001662 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1663 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001664 ok = True
1665 break
Brian Harring14a66742012-09-28 20:21:57 -07001666 elif current_branch_only and is_sha1 and ret == 128:
1667 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1668 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1669 # abort the optimization attempt and do a full sync.
1670 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001671 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001672
1673 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001674 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001675 if old_packed != '':
1676 _lwrite(packed_refs, old_packed)
1677 else:
1678 os.remove(packed_refs)
1679 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001680
1681 if is_sha1 and current_branch_only and self.upstream:
1682 # We just synced the upstream given branch; verify we
1683 # got what we wanted, else trigger a second run of all
1684 # refs.
1685 if not CheckForSha1():
1686 return self._RemoteFetch(name=name, current_branch_only=False,
1687 initial=False, quiet=quiet, alt_dir=alt_dir)
1688
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001689 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001690
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001691 def _ApplyCloneBundle(self, initial=False, quiet=False):
1692 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1693 return False
1694
1695 remote = self.GetRemote(self.remote.name)
1696 bundle_url = remote.url + '/clone.bundle'
1697 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001698 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1699 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001700 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1701 return False
1702
1703 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1704 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1705
1706 exist_dst = os.path.exists(bundle_dst)
1707 exist_tmp = os.path.exists(bundle_tmp)
1708
1709 if not initial and not exist_dst and not exist_tmp:
1710 return False
1711
1712 if not exist_dst:
1713 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1714 if not exist_dst:
1715 return False
1716
1717 cmd = ['fetch']
1718 if quiet:
1719 cmd.append('--quiet')
1720 if not self.worktree:
1721 cmd.append('--update-head-ok')
1722 cmd.append(bundle_dst)
1723 for f in remote.fetch:
1724 cmd.append(str(f))
1725 cmd.append('refs/tags/*:refs/tags/*')
1726
1727 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001728 if os.path.exists(bundle_dst):
1729 os.remove(bundle_dst)
1730 if os.path.exists(bundle_tmp):
1731 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001732 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001733
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001734 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001735 if os.path.exists(dstPath):
1736 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001737
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001738 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001739 if quiet:
1740 cmd += ['--silent']
1741 if os.path.exists(tmpPath):
1742 size = os.stat(tmpPath).st_size
1743 if size >= 1024:
1744 cmd += ['--continue-at', '%d' % (size,)]
1745 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001746 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001747 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1748 cmd += ['--proxy', os.environ['http_proxy']]
1749 cmd += [srcUrl]
1750
1751 if IsTrace():
1752 Trace('%s', ' '.join(cmd))
1753 try:
1754 proc = subprocess.Popen(cmd)
1755 except OSError:
1756 return False
1757
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001758 curlret = proc.wait()
1759
1760 if curlret == 22:
1761 # From curl man page:
1762 # 22: HTTP page not retrieved. The requested url was not found or
1763 # returned another error with the HTTP error code being 400 or above.
1764 # This return code only appears if -f, --fail is used.
1765 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001766 print("Server does not provide clone.bundle; ignoring.",
1767 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001768 return False
1769
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001770 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001771 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001772 os.rename(tmpPath, dstPath)
1773 return True
1774 else:
1775 os.remove(tmpPath)
1776 return False
1777 else:
1778 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001779
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780 def _Checkout(self, rev, quiet=False):
1781 cmd = ['checkout']
1782 if quiet:
1783 cmd.append('-q')
1784 cmd.append(rev)
1785 cmd.append('--')
1786 if GitCommand(self, cmd).Wait() != 0:
1787 if self._allrefs:
1788 raise GitError('%s checkout %s ' % (self.name, rev))
1789
Pierre Tardye5a21222011-03-24 16:28:18 +01001790 def _CherryPick(self, rev, quiet=False):
1791 cmd = ['cherry-pick']
1792 cmd.append(rev)
1793 cmd.append('--')
1794 if GitCommand(self, cmd).Wait() != 0:
1795 if self._allrefs:
1796 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1797
Erwan Mahea94f1622011-08-19 13:56:09 +02001798 def _Revert(self, rev, quiet=False):
1799 cmd = ['revert']
1800 cmd.append('--no-edit')
1801 cmd.append(rev)
1802 cmd.append('--')
1803 if GitCommand(self, cmd).Wait() != 0:
1804 if self._allrefs:
1805 raise GitError('%s revert %s ' % (self.name, rev))
1806
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001807 def _ResetHard(self, rev, quiet=True):
1808 cmd = ['reset', '--hard']
1809 if quiet:
1810 cmd.append('-q')
1811 cmd.append(rev)
1812 if GitCommand(self, cmd).Wait() != 0:
1813 raise GitError('%s reset --hard %s ' % (self.name, rev))
1814
1815 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001816 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001817 if onto is not None:
1818 cmd.extend(['--onto', onto])
1819 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001820 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001821 raise GitError('%s rebase %s ' % (self.name, upstream))
1822
Pierre Tardy3d125942012-05-04 12:18:12 +02001823 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001824 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001825 if ffonly:
1826 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001827 if GitCommand(self, cmd).Wait() != 0:
1828 raise GitError('%s merge %s ' % (self.name, head))
1829
1830 def _InitGitDir(self):
1831 if not os.path.exists(self.gitdir):
1832 os.makedirs(self.gitdir)
1833 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001834
Shawn O. Pearce88443382010-10-08 10:02:09 +02001835 mp = self.manifest.manifestProject
1836 ref_dir = mp.config.GetString('repo.reference')
1837
1838 if ref_dir:
1839 mirror_git = os.path.join(ref_dir, self.name + '.git')
1840 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1841 self.relpath + '.git')
1842
1843 if os.path.exists(mirror_git):
1844 ref_dir = mirror_git
1845
1846 elif os.path.exists(repo_git):
1847 ref_dir = repo_git
1848
1849 else:
1850 ref_dir = None
1851
1852 if ref_dir:
1853 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1854 os.path.join(ref_dir, 'objects') + '\n')
1855
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001856 if self.manifest.IsMirror:
1857 self.config.SetString('core.bare', 'true')
1858 else:
1859 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001860
1861 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001862 try:
1863 to_rm = os.listdir(hooks)
1864 except OSError:
1865 to_rm = []
1866 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001867 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001868 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001869
1870 m = self.manifest.manifestProject.config
1871 for key in ['user.name', 'user.email']:
1872 if m.Has(key, include_defaults = False):
1873 self.config.SetString(key, m.GetString(key))
1874
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001875 def _InitHooks(self):
1876 hooks = self._gitdir_path('hooks')
1877 if not os.path.exists(hooks):
1878 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001879 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001880 name = os.path.basename(stock_hook)
1881
Victor Boivie65e0f352011-04-18 11:23:29 +02001882 if name in ('commit-msg',) and not self.remote.review \
1883 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001884 # Don't install a Gerrit Code Review hook if this
1885 # project does not appear to use it for reviews.
1886 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001887 # Since the manifest project is one of those, but also
1888 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001889 continue
1890
1891 dst = os.path.join(hooks, name)
1892 if os.path.islink(dst):
1893 continue
1894 if os.path.exists(dst):
1895 if filecmp.cmp(stock_hook, dst, shallow=False):
1896 os.remove(dst)
1897 else:
1898 _error("%s: Not replacing %s hook", self.relpath, name)
1899 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001900 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001901 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001902 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001903 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001904 raise GitError('filesystem must support symlinks')
1905 else:
1906 raise
1907
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001908 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001909 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001910 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001911 remote.url = self.remote.url
1912 remote.review = self.remote.review
1913 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001914
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001915 if self.worktree:
1916 remote.ResetFetch(mirror=False)
1917 else:
1918 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001919 remote.Save()
1920
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001921 def _InitMRef(self):
1922 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001923 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001924
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001925 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001926 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001927
1928 def _InitAnyMRef(self, ref):
1929 cur = self.bare_ref.symref(ref)
1930
1931 if self.revisionId:
1932 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1933 msg = 'manifest set to %s' % self.revisionId
1934 dst = self.revisionId + '^0'
1935 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1936 else:
1937 remote = self.GetRemote(self.remote.name)
1938 dst = remote.ToLocal(self.revisionExpr)
1939 if cur != dst:
1940 msg = 'manifest set to %s' % self.revisionExpr
1941 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001942
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001943 def _InitWorkTree(self):
1944 dotgit = os.path.join(self.worktree, '.git')
1945 if not os.path.exists(dotgit):
1946 os.makedirs(dotgit)
1947
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001948 for name in ['config',
1949 'description',
1950 'hooks',
1951 'info',
1952 'logs',
1953 'objects',
1954 'packed-refs',
1955 'refs',
1956 'rr-cache',
1957 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001958 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001959 src = os.path.join(self.gitdir, name)
1960 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001961 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001962 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001963 else:
1964 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001965 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001966 if e.errno == errno.EPERM:
1967 raise GitError('filesystem must support symlinks')
1968 else:
1969 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001970
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001971 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001972
1973 cmd = ['read-tree', '--reset', '-u']
1974 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001975 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001976 if GitCommand(self, cmd).Wait() != 0:
1977 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001978
1979 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1980 if not os.path.exists(rr_cache):
1981 os.makedirs(rr_cache)
1982
Shawn O. Pearce93609662009-04-21 10:50:33 -07001983 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001984
1985 def _gitdir_path(self, path):
1986 return os.path.join(self.gitdir, path)
1987
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001988 def _revlist(self, *args, **kw):
1989 a = []
1990 a.extend(args)
1991 a.append('--')
1992 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001993
1994 @property
1995 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001996 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001997
1998 class _GitGetByExec(object):
1999 def __init__(self, project, bare):
2000 self._project = project
2001 self._bare = bare
2002
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002003 def LsOthers(self):
2004 p = GitCommand(self._project,
2005 ['ls-files',
2006 '-z',
2007 '--others',
2008 '--exclude-standard'],
2009 bare = False,
2010 capture_stdout = True,
2011 capture_stderr = True)
2012 if p.Wait() == 0:
2013 out = p.stdout
2014 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002015 return out[:-1].split('\0') # pylint: disable=W1401
2016 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002017 return []
2018
2019 def DiffZ(self, name, *args):
2020 cmd = [name]
2021 cmd.append('-z')
2022 cmd.extend(args)
2023 p = GitCommand(self._project,
2024 cmd,
2025 bare = False,
2026 capture_stdout = True,
2027 capture_stderr = True)
2028 try:
2029 out = p.process.stdout.read()
2030 r = {}
2031 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002032 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002033 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002034 try:
2035 info = out.next()
2036 path = out.next()
2037 except StopIteration:
2038 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002039
2040 class _Info(object):
2041 def __init__(self, path, omode, nmode, oid, nid, state):
2042 self.path = path
2043 self.src_path = None
2044 self.old_mode = omode
2045 self.new_mode = nmode
2046 self.old_id = oid
2047 self.new_id = nid
2048
2049 if len(state) == 1:
2050 self.status = state
2051 self.level = None
2052 else:
2053 self.status = state[:1]
2054 self.level = state[1:]
2055 while self.level.startswith('0'):
2056 self.level = self.level[1:]
2057
2058 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002059 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002060 if info.status in ('R', 'C'):
2061 info.src_path = info.path
2062 info.path = out.next()
2063 r[info.path] = info
2064 return r
2065 finally:
2066 p.Wait()
2067
2068 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002069 if self._bare:
2070 path = os.path.join(self._project.gitdir, HEAD)
2071 else:
2072 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002073 try:
2074 fd = open(path, 'rb')
2075 except IOError:
2076 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002077 try:
2078 line = fd.read()
2079 finally:
2080 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002081 if line.startswith('ref: '):
2082 return line[5:-1]
2083 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002084
2085 def SetHead(self, ref, message=None):
2086 cmdv = []
2087 if message is not None:
2088 cmdv.extend(['-m', message])
2089 cmdv.append(HEAD)
2090 cmdv.append(ref)
2091 self.symbolic_ref(*cmdv)
2092
2093 def DetachHead(self, new, message=None):
2094 cmdv = ['--no-deref']
2095 if message is not None:
2096 cmdv.extend(['-m', message])
2097 cmdv.append(HEAD)
2098 cmdv.append(new)
2099 self.update_ref(*cmdv)
2100
2101 def UpdateRef(self, name, new, old=None,
2102 message=None,
2103 detach=False):
2104 cmdv = []
2105 if message is not None:
2106 cmdv.extend(['-m', message])
2107 if detach:
2108 cmdv.append('--no-deref')
2109 cmdv.append(name)
2110 cmdv.append(new)
2111 if old is not None:
2112 cmdv.append(old)
2113 self.update_ref(*cmdv)
2114
2115 def DeleteRef(self, name, old=None):
2116 if not old:
2117 old = self.rev_parse(name)
2118 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002119 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002120
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002121 def rev_list(self, *args, **kw):
2122 if 'format' in kw:
2123 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2124 else:
2125 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002126 cmdv.extend(args)
2127 p = GitCommand(self._project,
2128 cmdv,
2129 bare = self._bare,
2130 capture_stdout = True,
2131 capture_stderr = True)
2132 r = []
2133 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002134 if line[-1] == '\n':
2135 line = line[:-1]
2136 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002137 if p.Wait() != 0:
2138 raise GitError('%s rev-list %s: %s' % (
2139 self._project.name,
2140 str(args),
2141 p.stderr))
2142 return r
2143
2144 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002145 """Allow arbitrary git commands using pythonic syntax.
2146
2147 This allows you to do things like:
2148 git_obj.rev_parse('HEAD')
2149
2150 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2151 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002152 Any other positional arguments will be passed to the git command, and the
2153 following keyword arguments are supported:
2154 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002155
2156 Args:
2157 name: The name of the git command to call. Any '_' characters will
2158 be replaced with '-'.
2159
2160 Returns:
2161 A callable object that will try to call git with the named command.
2162 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002163 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002164 def runner(*args, **kwargs):
2165 cmdv = []
2166 config = kwargs.pop('config', None)
2167 for k in kwargs:
2168 raise TypeError('%s() got an unexpected keyword argument %r'
2169 % (name, k))
2170 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002171 if not git_require((1, 7, 2)):
2172 raise ValueError('cannot set config on command line for %s()'
2173 % name)
Dave Borowitz091f8932012-10-23 17:01:04 -07002174 for k, v in config.iteritems():
2175 cmdv.append('-c')
2176 cmdv.append('%s=%s' % (k, v))
2177 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002178 cmdv.extend(args)
2179 p = GitCommand(self._project,
2180 cmdv,
2181 bare = self._bare,
2182 capture_stdout = True,
2183 capture_stderr = True)
2184 if p.Wait() != 0:
2185 raise GitError('%s %s: %s' % (
2186 self._project.name,
2187 name,
2188 p.stderr))
2189 r = p.stdout
2190 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2191 return r[:-1]
2192 return r
2193 return runner
2194
2195
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002196class _PriorSyncFailedError(Exception):
2197 def __str__(self):
2198 return 'prior sync failed; rebase still in progress'
2199
2200class _DirtyError(Exception):
2201 def __str__(self):
2202 return 'contains uncommitted changes'
2203
2204class _InfoMessage(object):
2205 def __init__(self, project, text):
2206 self.project = project
2207 self.text = text
2208
2209 def Print(self, syncbuf):
2210 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2211 syncbuf.out.nl()
2212
2213class _Failure(object):
2214 def __init__(self, project, why):
2215 self.project = project
2216 self.why = why
2217
2218 def Print(self, syncbuf):
2219 syncbuf.out.fail('error: %s/: %s',
2220 self.project.relpath,
2221 str(self.why))
2222 syncbuf.out.nl()
2223
2224class _Later(object):
2225 def __init__(self, project, action):
2226 self.project = project
2227 self.action = action
2228
2229 def Run(self, syncbuf):
2230 out = syncbuf.out
2231 out.project('project %s/', self.project.relpath)
2232 out.nl()
2233 try:
2234 self.action()
2235 out.nl()
2236 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002237 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002238 out.nl()
2239 return False
2240
2241class _SyncColoring(Coloring):
2242 def __init__(self, config):
2243 Coloring.__init__(self, config, 'reposync')
2244 self.project = self.printer('header', attr = 'bold')
2245 self.info = self.printer('info')
2246 self.fail = self.printer('fail', fg='red')
2247
2248class SyncBuffer(object):
2249 def __init__(self, config, detach_head=False):
2250 self._messages = []
2251 self._failures = []
2252 self._later_queue1 = []
2253 self._later_queue2 = []
2254
2255 self.out = _SyncColoring(config)
2256 self.out.redirect(sys.stderr)
2257
2258 self.detach_head = detach_head
2259 self.clean = True
2260
2261 def info(self, project, fmt, *args):
2262 self._messages.append(_InfoMessage(project, fmt % args))
2263
2264 def fail(self, project, err=None):
2265 self._failures.append(_Failure(project, err))
2266 self.clean = False
2267
2268 def later1(self, project, what):
2269 self._later_queue1.append(_Later(project, what))
2270
2271 def later2(self, project, what):
2272 self._later_queue2.append(_Later(project, what))
2273
2274 def Finish(self):
2275 self._PrintMessages()
2276 self._RunLater()
2277 self._PrintMessages()
2278 return self.clean
2279
2280 def _RunLater(self):
2281 for q in ['_later_queue1', '_later_queue2']:
2282 if not self._RunQueue(q):
2283 return
2284
2285 def _RunQueue(self, queue):
2286 for m in getattr(self, queue):
2287 if not m.Run(self):
2288 self.clean = False
2289 return False
2290 setattr(self, queue, [])
2291 return True
2292
2293 def _PrintMessages(self):
2294 for m in self._messages:
2295 m.Print(self)
2296 for m in self._failures:
2297 m.Print(self)
2298
2299 self._messages = []
2300 self._failures = []
2301
2302
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002303class MetaProject(Project):
2304 """A special project housed under .repo.
2305 """
2306 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002307 Project.__init__(self,
2308 manifest = manifest,
2309 name = name,
2310 gitdir = gitdir,
2311 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002312 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002313 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002314 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002315 revisionId = None,
2316 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002317
2318 def PreSync(self):
2319 if self.Exists:
2320 cb = self.CurrentBranch
2321 if cb:
2322 base = self.GetBranch(cb).merge
2323 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002324 self.revisionExpr = base
2325 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002326
Florian Vallee5d016502012-06-07 17:19:26 +02002327 def MetaBranchSwitch(self, target):
2328 """ Prepare MetaProject for manifest branch switch
2329 """
2330
2331 # detach and delete manifest branch, allowing a new
2332 # branch to take over
2333 syncbuf = SyncBuffer(self.config, detach_head = True)
2334 self.Sync_LocalHalf(syncbuf)
2335 syncbuf.Finish()
2336
2337 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002338 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002339 capture_stdout = True,
2340 capture_stderr = True).Wait() == 0
2341
2342
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002343 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002344 def LastFetch(self):
2345 try:
2346 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2347 return os.path.getmtime(fh)
2348 except OSError:
2349 return 0
2350
2351 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002352 def HasChanges(self):
2353 """Has the remote received new commits not yet checked out?
2354 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002355 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002356 return False
2357
David Pursehouse8a68ff92012-09-24 12:15:13 +09002358 all_refs = self.bare_ref.all
2359 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002360 head = self.work_git.GetHead()
2361 if head.startswith(R_HEADS):
2362 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002363 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002364 except KeyError:
2365 head = None
2366
2367 if revid == head:
2368 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002369 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002370 return True
2371 return False