blob: 20bf866cdb9cb13e2d585897915021ec1acab61f [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,
David Pursehouseede7f122012-11-27 22:25:30 +0900491 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800492 upstream = None,
493 parent = None,
494 is_derived = False):
495 """Init a Project object.
496
497 Args:
498 manifest: The XmlManifest object.
499 name: The `name` attribute of manifest.xml's project element.
500 remote: RemoteSpec object specifying its remote's properties.
501 gitdir: Absolute path of git directory.
502 worktree: Absolute path of git working tree.
503 relpath: Relative path of git working tree to repo's top directory.
504 revisionExpr: The `revision` attribute of manifest.xml's project element.
505 revisionId: git commit id for checking out.
506 rebase: The `rebase` attribute of manifest.xml's project element.
507 groups: The `groups` attribute of manifest.xml's project element.
508 sync_c: The `sync-c` attribute of manifest.xml's project element.
509 sync_s: The `sync-s` attribute of manifest.xml's project element.
510 upstream: The `upstream` attribute of manifest.xml's project element.
511 parent: The parent Project object.
512 is_derived: False if the project was explicitly defined in the manifest;
513 True if the project is a discovered submodule.
514 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700515 self.manifest = manifest
516 self.name = name
517 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800518 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800519 if worktree:
520 self.worktree = worktree.replace('\\', '/')
521 else:
522 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700523 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700524 self.revisionExpr = revisionExpr
525
526 if revisionId is None \
527 and revisionExpr \
528 and IsId(revisionExpr):
529 self.revisionId = revisionExpr
530 else:
531 self.revisionId = revisionId
532
Mike Pontillod3153822012-02-28 11:53:24 -0800533 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700534 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700535 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800536 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900537 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700538 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800539 self.parent = parent
540 self.is_derived = is_derived
541 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800542
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700543 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700544 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500545 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700546 self.config = GitConfig.ForRepository(
547 gitdir = self.gitdir,
548 defaults = self.manifest.globalConfig)
549
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800550 if self.worktree:
551 self.work_git = self._GitGetByExec(self, bare=False)
552 else:
553 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700554 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700555 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700556
Doug Anderson37282b42011-03-04 11:54:18 -0800557 # This will be filled in if a project is later identified to be the
558 # project containing repo hooks.
559 self.enabled_repo_hooks = []
560
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700561 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800562 def Derived(self):
563 return self.is_derived
564
565 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700566 def Exists(self):
567 return os.path.isdir(self.gitdir)
568
569 @property
570 def CurrentBranch(self):
571 """Obtain the name of the currently checked out branch.
572 The branch name omits the 'refs/heads/' prefix.
573 None is returned if the project is on a detached HEAD.
574 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700575 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700576 if b.startswith(R_HEADS):
577 return b[len(R_HEADS):]
578 return None
579
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700580 def IsRebaseInProgress(self):
581 w = self.worktree
582 g = os.path.join(w, '.git')
583 return os.path.exists(os.path.join(g, 'rebase-apply')) \
584 or os.path.exists(os.path.join(g, 'rebase-merge')) \
585 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200586
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700587 def IsDirty(self, consider_untracked=True):
588 """Is the working directory modified in some way?
589 """
590 self.work_git.update_index('-q',
591 '--unmerged',
592 '--ignore-missing',
593 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900594 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700595 return True
596 if self.work_git.DiffZ('diff-files'):
597 return True
598 if consider_untracked and self.work_git.LsOthers():
599 return True
600 return False
601
602 _userident_name = None
603 _userident_email = None
604
605 @property
606 def UserName(self):
607 """Obtain the user's personal name.
608 """
609 if self._userident_name is None:
610 self._LoadUserIdentity()
611 return self._userident_name
612
613 @property
614 def UserEmail(self):
615 """Obtain the user's email address. This is very likely
616 to be their Gerrit login.
617 """
618 if self._userident_email is None:
619 self._LoadUserIdentity()
620 return self._userident_email
621
622 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900623 u = self.bare_git.var('GIT_COMMITTER_IDENT')
624 m = re.compile("^(.*) <([^>]*)> ").match(u)
625 if m:
626 self._userident_name = m.group(1)
627 self._userident_email = m.group(2)
628 else:
629 self._userident_name = ''
630 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700631
632 def GetRemote(self, name):
633 """Get the configuration for a single remote.
634 """
635 return self.config.GetRemote(name)
636
637 def GetBranch(self, name):
638 """Get the configuration for a single branch.
639 """
640 return self.config.GetBranch(name)
641
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700642 def GetBranches(self):
643 """Get all existing local branches.
644 """
645 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900646 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700647 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700648
David Pursehouse8a68ff92012-09-24 12:15:13 +0900649 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700650 if name.startswith(R_HEADS):
651 name = name[len(R_HEADS):]
652 b = self.GetBranch(name)
653 b.current = name == current
654 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900655 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700656 heads[name] = b
657
David Pursehouse8a68ff92012-09-24 12:15:13 +0900658 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700659 if name.startswith(R_PUB):
660 name = name[len(R_PUB):]
661 b = heads.get(name)
662 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900663 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700664
665 return heads
666
Colin Cross5acde752012-03-28 20:15:45 -0700667 def MatchesGroups(self, manifest_groups):
668 """Returns true if the manifest groups specified at init should cause
669 this project to be synced.
670 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700671 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700672
673 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700674 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700675 manifest_groups: "-group1,group2"
676 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500677
678 The special manifest group "default" will match any project that
679 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700680 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500681 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700682 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500683 if not 'notdefault' in expanded_project_groups:
684 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700685
Conley Owens971de8e2012-04-16 10:36:08 -0700686 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700687 for group in expanded_manifest_groups:
688 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700689 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700690 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700691 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700692
Conley Owens971de8e2012-04-16 10:36:08 -0700693 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700694
695## Status Display ##
696
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500697 def HasChanges(self):
698 """Returns true if there are uncommitted changes.
699 """
700 self.work_git.update_index('-q',
701 '--unmerged',
702 '--ignore-missing',
703 '--refresh')
704 if self.IsRebaseInProgress():
705 return True
706
707 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
708 return True
709
710 if self.work_git.DiffZ('diff-files'):
711 return True
712
713 if self.work_git.LsOthers():
714 return True
715
716 return False
717
Terence Haddock4655e812011-03-31 12:33:34 +0200718 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700719 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200720
721 Args:
722 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700723 """
724 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200725 if output_redir == None:
726 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700727 print(file=output_redir)
728 print('project %s/' % self.relpath, file=output_redir)
729 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700730 return
731
732 self.work_git.update_index('-q',
733 '--unmerged',
734 '--ignore-missing',
735 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700736 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
738 df = self.work_git.DiffZ('diff-files')
739 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100740 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700741 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742
743 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200744 if not output_redir == None:
745 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700746 out.project('project %-40s', self.relpath + '/')
747
748 branch = self.CurrentBranch
749 if branch is None:
750 out.nobranch('(*** NO BRANCH ***)')
751 else:
752 out.branch('branch %s', branch)
753 out.nl()
754
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700755 if rb:
756 out.important('prior sync failed; rebase still in progress')
757 out.nl()
758
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759 paths = list()
760 paths.extend(di.keys())
761 paths.extend(df.keys())
762 paths.extend(do)
763
764 paths = list(set(paths))
765 paths.sort()
766
767 for p in paths:
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900768 try:
769 i = di[p]
770 except KeyError:
771 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700772
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900773 try:
774 f = df[p]
775 except KeyError:
776 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200777
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900778 if i:
779 i_status = i.status.upper()
780 else:
781 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900783 if f:
784 f_status = f.status.lower()
785 else:
786 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700787
788 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800789 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700790 i.src_path, p, i.level)
791 else:
792 line = ' %s%s\t%s' % (i_status, f_status, p)
793
794 if i and not f:
795 out.added('%s', line)
796 elif (i and f) or (not i and f):
797 out.changed('%s', line)
798 elif not i and not f:
799 out.untracked('%s', line)
800 else:
801 out.write('%s', line)
802 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200803
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700804 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700805
pelyad67872d2012-03-28 14:49:58 +0300806 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807 """Prints the status of the repository to stdout.
808 """
809 out = DiffColoring(self.config)
810 cmd = ['diff']
811 if out.is_on:
812 cmd.append('--color')
813 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300814 if absolute_paths:
815 cmd.append('--src-prefix=a/%s/' % self.relpath)
816 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700817 cmd.append('--')
818 p = GitCommand(self,
819 cmd,
820 capture_stdout = True,
821 capture_stderr = True)
822 has_diff = False
823 for line in p.process.stdout:
824 if not has_diff:
825 out.nl()
826 out.project('project %s/' % self.relpath)
827 out.nl()
828 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700829 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830 p.Wait()
831
832
833## Publish / Upload ##
834
David Pursehouse8a68ff92012-09-24 12:15:13 +0900835 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 """Was the branch published (uploaded) for code review?
837 If so, returns the SHA-1 hash of the last published
838 state for the branch.
839 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700840 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900841 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700842 try:
843 return self.bare_git.rev_parse(key)
844 except GitError:
845 return None
846 else:
847 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900848 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700849 except KeyError:
850 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700851
David Pursehouse8a68ff92012-09-24 12:15:13 +0900852 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853 """Prunes any stale published refs.
854 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900855 if all_refs is None:
856 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857 heads = set()
858 canrm = {}
David Pursehouse8a68ff92012-09-24 12:15:13 +0900859 for name, ref_id in all_refs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700860 if name.startswith(R_HEADS):
861 heads.add(name)
862 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900863 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700864
David Pursehouse8a68ff92012-09-24 12:15:13 +0900865 for name, ref_id in canrm.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700866 n = name[len(R_PUB):]
867 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900868 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700870 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871 """List any branches which can be uploaded for review.
872 """
873 heads = {}
874 pubed = {}
875
David Pursehouse8a68ff92012-09-24 12:15:13 +0900876 for name, ref_id in self._allrefs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900878 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900880 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881
882 ready = []
David Pursehouse8a68ff92012-09-24 12:15:13 +0900883 for branch, ref_id in heads.iteritems():
884 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700885 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700886 if selected_branch and branch != selected_branch:
887 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800889 rb = self.GetUploadableBranch(branch)
890 if rb:
891 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892 return ready
893
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800894 def GetUploadableBranch(self, branch_name):
895 """Get a single uploadable branch, or None.
896 """
897 branch = self.GetBranch(branch_name)
898 base = branch.LocalMerge
899 if branch.LocalMerge:
900 rb = ReviewableBranch(self, branch, base)
901 if rb.commits:
902 return rb
903 return None
904
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700905 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700906 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700907 auto_topic=False,
908 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700909 """Uploads the named branch for code review.
910 """
911 if branch is None:
912 branch = self.CurrentBranch
913 if branch is None:
914 raise GitError('not currently on a branch')
915
916 branch = self.GetBranch(branch)
917 if not branch.LocalMerge:
918 raise GitError('branch %s does not track a remote' % branch.name)
919 if not branch.remote.review:
920 raise GitError('remote %s has no review url' % branch.remote.name)
921
922 dest_branch = branch.merge
923 if not dest_branch.startswith(R_HEADS):
924 dest_branch = R_HEADS + dest_branch
925
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800926 if not branch.remote.projectname:
927 branch.remote.projectname = self.name
928 branch.remote.Save()
929
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800930 url = branch.remote.ReviewUrl(self.UserEmail)
931 if url is None:
932 raise UploadError('review not configured')
933 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800934
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800935 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800936 rp = ['gerrit receive-pack']
937 for e in people[0]:
938 rp.append('--reviewer=%s' % sq(e))
939 for e in people[1]:
940 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800941 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700942
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800943 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800944
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800945 if dest_branch.startswith(R_HEADS):
946 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700947
948 upload_type = 'for'
949 if draft:
950 upload_type = 'drafts'
951
952 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
953 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800954 if auto_topic:
955 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800956 if not url.startswith('ssh://'):
957 rp = ['r=%s' % p for p in people[0]] + \
958 ['cc=%s' % p for p in people[1]]
959 if rp:
960 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800961 cmd.append(ref_spec)
962
963 if GitCommand(self, cmd, bare = True).Wait() != 0:
964 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700965
966 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
967 self.bare_git.UpdateRef(R_PUB + branch.name,
968 R_HEADS + branch.name,
969 message = msg)
970
971
972## Sync ##
973
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700974 def Sync_NetworkHalf(self,
975 quiet=False,
976 is_new=None,
977 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700978 clone_bundle=True,
979 no_tags=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980 """Perform only the network IO portion of the sync process.
981 Local working directory/branch state is not affected.
982 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700983 if is_new is None:
984 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200985 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 self._InitGitDir()
987 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700988
989 if is_new:
990 alt = os.path.join(self.gitdir, 'objects/info/alternates')
991 try:
992 fd = open(alt, 'rb')
993 try:
994 alt_dir = fd.readline().rstrip()
995 finally:
996 fd.close()
997 except IOError:
998 alt_dir = None
999 else:
1000 alt_dir = None
1001
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001002 if clone_bundle \
1003 and alt_dir is None \
1004 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001005 is_new = False
1006
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001007 if not current_branch_only:
1008 if self.sync_c:
1009 current_branch_only = True
1010 elif not self.manifest._loaded:
1011 # Manifest cannot check defaults until it syncs.
1012 current_branch_only = False
1013 elif self.manifest.default.sync_c:
1014 current_branch_only = True
1015
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001016 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001017 current_branch_only=current_branch_only,
1018 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001019 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001020
1021 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001022 self._InitMRef()
1023 else:
1024 self._InitMirrorHead()
1025 try:
1026 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1027 except OSError:
1028 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001029 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001030
1031 def PostRepoUpgrade(self):
1032 self._InitHooks()
1033
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001034 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001035 for copyfile in self.copyfiles:
1036 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037
David Pursehouse8a68ff92012-09-24 12:15:13 +09001038 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001039 if self.revisionId:
1040 return self.revisionId
1041
1042 rem = self.GetRemote(self.remote.name)
1043 rev = rem.ToLocal(self.revisionExpr)
1044
David Pursehouse8a68ff92012-09-24 12:15:13 +09001045 if all_refs is not None and rev in all_refs:
1046 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001047
1048 try:
1049 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1050 except GitError:
1051 raise ManifestInvalidRevisionError(
1052 'revision %s in %s not found' % (self.revisionExpr,
1053 self.name))
1054
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001055 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 """Perform only the local IO portion of the sync process.
1057 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001059 all_refs = self.bare_ref.all
1060 self.CleanPublishedCache(all_refs)
1061 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001062
David Pursehouse1d947b32012-10-25 12:23:11 +09001063 def _doff():
1064 self._FastForward(revid)
1065 self._CopyFiles()
1066
Skyler Kaufman835cd682011-03-08 12:14:41 -08001067 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001068 head = self.work_git.GetHead()
1069 if head.startswith(R_HEADS):
1070 branch = head[len(R_HEADS):]
1071 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001072 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001073 except KeyError:
1074 head = None
1075 else:
1076 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001078 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 # Currently on a detached HEAD. The user is assumed to
1080 # not have any local modifications worth worrying about.
1081 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001082 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001083 syncbuf.fail(self, _PriorSyncFailedError())
1084 return
1085
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001086 if head == revid:
1087 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001088 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001089 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001090 if not syncbuf.detach_head:
1091 return
1092 else:
1093 lost = self._revlist(not_rev(revid), HEAD)
1094 if lost:
1095 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001096
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001098 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001099 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001100 syncbuf.fail(self, e)
1101 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001103 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001104
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001105 if head == revid:
1106 # No changes; don't do anything further.
1107 #
1108 return
1109
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001110 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001112 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001113 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001114 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001115 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001116 syncbuf.info(self,
1117 "leaving %s; does not track upstream",
1118 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001120 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001121 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001122 syncbuf.fail(self, e)
1123 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001124 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001125 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001127 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001128 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001129 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001130 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001131 if not_merged:
1132 if upstream_gain:
1133 # The user has published this branch and some of those
1134 # commits are not yet merged upstream. We do not want
1135 # to rewrite the published commits so we punt.
1136 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001137 syncbuf.fail(self,
1138 "branch %s is published (but not merged) and is now %d commits behind"
1139 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001140 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001141 elif pub == head:
1142 # All published commits are merged, and thus we are a
1143 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001144 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001145 syncbuf.later1(self, _doff)
1146 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001147
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001148 # Examine the local commits not in the remote. Find the
1149 # last one attributed to this user, if any.
1150 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001151 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001152 last_mine = None
1153 cnt_mine = 0
1154 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001155 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001156 if committer_email == self.UserEmail:
1157 last_mine = commit_id
1158 cnt_mine += 1
1159
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001160 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001161 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162
1163 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001164 syncbuf.fail(self, _DirtyError())
1165 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001166
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001167 # If the upstream switched on us, warn the user.
1168 #
1169 if branch.merge != self.revisionExpr:
1170 if branch.merge and self.revisionExpr:
1171 syncbuf.info(self,
1172 'manifest switched %s...%s',
1173 branch.merge,
1174 self.revisionExpr)
1175 elif branch.merge:
1176 syncbuf.info(self,
1177 'manifest no longer tracks %s',
1178 branch.merge)
1179
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001180 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001181 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001182 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001183 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001184 syncbuf.info(self,
1185 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001186 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001188 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001189 if not ID_RE.match(self.revisionExpr):
1190 # in case of manifest sync the revisionExpr might be a SHA1
1191 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192 branch.Save()
1193
Mike Pontillod3153822012-02-28 11:53:24 -08001194 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001195 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001196 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001197 self._CopyFiles()
1198 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001199 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001201 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001202 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001203 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001204 syncbuf.fail(self, e)
1205 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001206 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001207 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001208
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001209 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001210 # dest should already be an absolute path, but src is project relative
1211 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001212 abssrc = os.path.join(self.worktree, src)
1213 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001214
James W. Mills24c13082012-04-12 15:04:13 -05001215 def AddAnnotation(self, name, value, keep):
1216 self.annotations.append(_Annotation(name, value, keep))
1217
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001218 def DownloadPatchSet(self, change_id, patch_id):
1219 """Download a single patch set of a single change to FETCH_HEAD.
1220 """
1221 remote = self.GetRemote(self.remote.name)
1222
1223 cmd = ['fetch', remote.name]
1224 cmd.append('refs/changes/%2.2d/%d/%d' \
1225 % (change_id % 100, change_id, patch_id))
David Pursehouse7e6dd2d2012-10-25 12:40:51 +09001226 cmd.extend(map(str, remote.fetch))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001227 if GitCommand(self, cmd, bare=True).Wait() != 0:
1228 return None
1229 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001230 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001231 change_id,
1232 patch_id,
1233 self.bare_git.rev_parse('FETCH_HEAD'))
1234
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001235
1236## Branch Management ##
1237
1238 def StartBranch(self, name):
1239 """Create a new branch off the manifest's revision.
1240 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001241 head = self.work_git.GetHead()
1242 if head == (R_HEADS + name):
1243 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001244
David Pursehouse8a68ff92012-09-24 12:15:13 +09001245 all_refs = self.bare_ref.all
1246 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001247 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001248 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001249 capture_stdout = True,
1250 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001251
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001252 branch = self.GetBranch(name)
1253 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001254 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001255 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001256
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001257 if head.startswith(R_HEADS):
1258 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001259 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001260 except KeyError:
1261 head = None
1262
1263 if revid and head and revid == head:
1264 ref = os.path.join(self.gitdir, R_HEADS + name)
1265 try:
1266 os.makedirs(os.path.dirname(ref))
1267 except OSError:
1268 pass
1269 _lwrite(ref, '%s\n' % revid)
1270 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1271 'ref: %s%s\n' % (R_HEADS, name))
1272 branch.Save()
1273 return True
1274
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001275 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001276 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001277 capture_stdout = True,
1278 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001279 branch.Save()
1280 return True
1281 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001282
Wink Saville02d79452009-04-10 13:01:24 -07001283 def CheckoutBranch(self, name):
1284 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001285
1286 Args:
1287 name: The name of the branch to checkout.
1288
1289 Returns:
1290 True if the checkout succeeded; False if it didn't; None if the branch
1291 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001292 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001293 rev = R_HEADS + name
1294 head = self.work_git.GetHead()
1295 if head == rev:
1296 # Already on the branch
1297 #
1298 return True
Wink Saville02d79452009-04-10 13:01:24 -07001299
David Pursehouse8a68ff92012-09-24 12:15:13 +09001300 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001301 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001302 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001303 except KeyError:
1304 # Branch does not exist in this project
1305 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001306 return None
Wink Saville02d79452009-04-10 13:01:24 -07001307
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001308 if head.startswith(R_HEADS):
1309 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001310 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001311 except KeyError:
1312 head = None
1313
1314 if head == revid:
1315 # Same revision; just update HEAD to point to the new
1316 # target branch, but otherwise take no other action.
1317 #
1318 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1319 'ref: %s%s\n' % (R_HEADS, name))
1320 return True
1321
1322 return GitCommand(self,
1323 ['checkout', name, '--'],
1324 capture_stdout = True,
1325 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001326
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001327 def AbandonBranch(self, name):
1328 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001329
1330 Args:
1331 name: The name of the branch to abandon.
1332
1333 Returns:
1334 True if the abandon succeeded; False if it didn't; None if the branch
1335 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001336 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001337 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001338 all_refs = self.bare_ref.all
1339 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001340 # Doesn't exist
1341 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001342
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001343 head = self.work_git.GetHead()
1344 if head == rev:
1345 # We can't destroy the branch while we are sitting
1346 # on it. Switch to a detached HEAD.
1347 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001348 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001349
David Pursehouse8a68ff92012-09-24 12:15:13 +09001350 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001351 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001352 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1353 '%s\n' % revid)
1354 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001355 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001356
1357 return GitCommand(self,
1358 ['branch', '-D', name],
1359 capture_stdout = True,
1360 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001361
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001362 def PruneHeads(self):
1363 """Prune any topic branches already merged into upstream.
1364 """
1365 cb = self.CurrentBranch
1366 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001367 left = self._allrefs
1368 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001369 if name.startswith(R_HEADS):
1370 name = name[len(R_HEADS):]
1371 if cb is None or name != cb:
1372 kill.append(name)
1373
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001374 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001375 if cb is not None \
1376 and not self._revlist(HEAD + '...' + rev) \
1377 and not self.IsDirty(consider_untracked = False):
1378 self.work_git.DetachHead(HEAD)
1379 kill.append(cb)
1380
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001382 old = self.bare_git.GetHead()
1383 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001384 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1385
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386 try:
1387 self.bare_git.DetachHead(rev)
1388
1389 b = ['branch', '-d']
1390 b.extend(kill)
1391 b = GitCommand(self, b, bare=True,
1392 capture_stdout=True,
1393 capture_stderr=True)
1394 b.Wait()
1395 finally:
1396 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001397 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001399 for branch in kill:
1400 if (R_HEADS + branch) not in left:
1401 self.CleanPublishedCache()
1402 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403
1404 if cb and cb not in kill:
1405 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001406 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407
1408 kept = []
1409 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001410 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001411 branch = self.GetBranch(branch)
1412 base = branch.LocalMerge
1413 if not base:
1414 base = rev
1415 kept.append(ReviewableBranch(self, branch, base))
1416 return kept
1417
1418
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001419## Submodule Management ##
1420
1421 def GetRegisteredSubprojects(self):
1422 result = []
1423 def rec(subprojects):
1424 if not subprojects:
1425 return
1426 result.extend(subprojects)
1427 for p in subprojects:
1428 rec(p.subprojects)
1429 rec(self.subprojects)
1430 return result
1431
1432 def _GetSubmodules(self):
1433 # Unfortunately we cannot call `git submodule status --recursive` here
1434 # because the working tree might not exist yet, and it cannot be used
1435 # without a working tree in its current implementation.
1436
1437 def get_submodules(gitdir, rev):
1438 # Parse .gitmodules for submodule sub_paths and sub_urls
1439 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1440 if not sub_paths:
1441 return []
1442 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1443 # revision of submodule repository
1444 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1445 submodules = []
1446 for sub_path, sub_url in zip(sub_paths, sub_urls):
1447 try:
1448 sub_rev = sub_revs[sub_path]
1449 except KeyError:
1450 # Ignore non-exist submodules
1451 continue
1452 submodules.append((sub_rev, sub_path, sub_url))
1453 return submodules
1454
1455 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1456 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1457 def parse_gitmodules(gitdir, rev):
1458 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1459 try:
1460 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1461 bare = True, gitdir = gitdir)
1462 except GitError:
1463 return [], []
1464 if p.Wait() != 0:
1465 return [], []
1466
1467 gitmodules_lines = []
1468 fd, temp_gitmodules_path = tempfile.mkstemp()
1469 try:
1470 os.write(fd, p.stdout)
1471 os.close(fd)
1472 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1473 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1474 bare = True, gitdir = gitdir)
1475 if p.Wait() != 0:
1476 return [], []
1477 gitmodules_lines = p.stdout.split('\n')
1478 except GitError:
1479 return [], []
1480 finally:
1481 os.remove(temp_gitmodules_path)
1482
1483 names = set()
1484 paths = {}
1485 urls = {}
1486 for line in gitmodules_lines:
1487 if not line:
1488 continue
1489 m = re_path.match(line)
1490 if m:
1491 names.add(m.group(1))
1492 paths[m.group(1)] = m.group(2)
1493 continue
1494 m = re_url.match(line)
1495 if m:
1496 names.add(m.group(1))
1497 urls[m.group(1)] = m.group(2)
1498 continue
1499 names = sorted(names)
1500 return ([paths.get(name, '') for name in names],
1501 [urls.get(name, '') for name in names])
1502
1503 def git_ls_tree(gitdir, rev, paths):
1504 cmd = ['ls-tree', rev, '--']
1505 cmd.extend(paths)
1506 try:
1507 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1508 bare = True, gitdir = gitdir)
1509 except GitError:
1510 return []
1511 if p.Wait() != 0:
1512 return []
1513 objects = {}
1514 for line in p.stdout.split('\n'):
1515 if not line.strip():
1516 continue
1517 object_rev, object_path = line.split()[2:4]
1518 objects[object_path] = object_rev
1519 return objects
1520
1521 try:
1522 rev = self.GetRevisionId()
1523 except GitError:
1524 return []
1525 return get_submodules(self.gitdir, rev)
1526
1527 def GetDerivedSubprojects(self):
1528 result = []
1529 if not self.Exists:
1530 # If git repo does not exist yet, querying its submodules will
1531 # mess up its states; so return here.
1532 return result
1533 for rev, path, url in self._GetSubmodules():
1534 name = self.manifest.GetSubprojectName(self, path)
1535 project = self.manifest.projects.get(name)
1536 if project:
1537 result.extend(project.GetDerivedSubprojects())
1538 continue
1539 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1540 remote = RemoteSpec(self.remote.name,
1541 url = url,
1542 review = self.remote.review)
1543 subproject = Project(manifest = self.manifest,
1544 name = name,
1545 remote = remote,
1546 gitdir = gitdir,
1547 worktree = worktree,
1548 relpath = relpath,
1549 revisionExpr = self.revisionExpr,
1550 revisionId = rev,
1551 rebase = self.rebase,
1552 groups = self.groups,
1553 sync_c = self.sync_c,
1554 sync_s = self.sync_s,
1555 parent = self,
1556 is_derived = True)
1557 result.append(subproject)
1558 result.extend(subproject.GetDerivedSubprojects())
1559 return result
1560
1561
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001562## Direct Git Commands ##
1563
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001564 def _RemoteFetch(self, name=None,
1565 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001566 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001567 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001568 alt_dir=None,
1569 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001570
1571 is_sha1 = False
1572 tag_name = None
1573
Brian Harring14a66742012-09-28 20:21:57 -07001574 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001575 try:
1576 # if revision (sha or tag) is not present then following function
1577 # throws an error.
1578 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1579 return True
1580 except GitError:
1581 # There is no such persistent revision. We have to fetch it.
1582 return False
Brian Harring14a66742012-09-28 20:21:57 -07001583
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001584 if current_branch_only:
1585 if ID_RE.match(self.revisionExpr) is not None:
1586 is_sha1 = True
1587 elif self.revisionExpr.startswith(R_TAGS):
1588 # this is a tag and its sha1 value should never change
1589 tag_name = self.revisionExpr[len(R_TAGS):]
1590
1591 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001592 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001593 return True
Brian Harring14a66742012-09-28 20:21:57 -07001594 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1595 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001596
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001597 if not name:
1598 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001599
1600 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001601 remote = self.GetRemote(name)
1602 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001603 ssh_proxy = True
1604
Shawn O. Pearce88443382010-10-08 10:02:09 +02001605 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001606 if alt_dir and 'objects' == os.path.basename(alt_dir):
1607 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001608 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1609 remote = self.GetRemote(name)
1610
David Pursehouse8a68ff92012-09-24 12:15:13 +09001611 all_refs = self.bare_ref.all
1612 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001613 tmp = set()
1614
David Pursehouse8a68ff92012-09-24 12:15:13 +09001615 for r, ref_id in GitRefs(ref_dir).all.iteritems():
1616 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001617 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001618 all_refs[r] = ref_id
1619 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001620 continue
1621
David Pursehouse8a68ff92012-09-24 12:15:13 +09001622 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001623 continue
1624
David Pursehouse8a68ff92012-09-24 12:15:13 +09001625 r = 'refs/_alt/%s' % ref_id
1626 all_refs[r] = ref_id
1627 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001628 tmp.add(r)
1629
David Pursehouse8a68ff92012-09-24 12:15:13 +09001630 ref_names = list(all_refs.keys())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001631 ref_names.sort()
1632
1633 tmp_packed = ''
1634 old_packed = ''
1635
1636 for r in ref_names:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001637 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001638 tmp_packed += line
1639 if r not in tmp:
1640 old_packed += line
1641
1642 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001643 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001644 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001645
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001646 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001647
1648 # The --depth option only affects the initial fetch; after that we'll do
1649 # full fetches of changes.
David Pursehouseede7f122012-11-27 22:25:30 +09001650 if self.clone_depth:
1651 depth = self.clone_depth
1652 else:
1653 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Doug Anderson30d45292011-05-04 15:01:04 -07001654 if depth and initial:
1655 cmd.append('--depth=%s' % depth)
1656
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001657 if quiet:
1658 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001659 if not self.worktree:
1660 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001661 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001662
Brian Harring14a66742012-09-28 20:21:57 -07001663 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001664 # Fetch whole repo
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001665 if no_tags:
1666 cmd.append('--no-tags')
1667 else:
1668 cmd.append('--tags')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001669 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1670 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001671 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001672 cmd.append(tag_name)
1673 else:
1674 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001675 if is_sha1:
1676 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001677 if branch.startswith(R_HEADS):
1678 branch = branch[len(R_HEADS):]
1679 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001680
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001681 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001682 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001683 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1684 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001685 ok = True
1686 break
Brian Harring14a66742012-09-28 20:21:57 -07001687 elif current_branch_only and is_sha1 and ret == 128:
1688 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1689 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1690 # abort the optimization attempt and do a full sync.
1691 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001692 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001693
1694 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001695 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001696 if old_packed != '':
1697 _lwrite(packed_refs, old_packed)
1698 else:
1699 os.remove(packed_refs)
1700 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001701
1702 if is_sha1 and current_branch_only and self.upstream:
1703 # We just synced the upstream given branch; verify we
1704 # got what we wanted, else trigger a second run of all
1705 # refs.
1706 if not CheckForSha1():
1707 return self._RemoteFetch(name=name, current_branch_only=False,
1708 initial=False, quiet=quiet, alt_dir=alt_dir)
1709
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001710 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001711
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001712 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001713 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001714 return False
1715
1716 remote = self.GetRemote(self.remote.name)
1717 bundle_url = remote.url + '/clone.bundle'
1718 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001719 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1720 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001721 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1722 return False
1723
1724 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1725 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1726
1727 exist_dst = os.path.exists(bundle_dst)
1728 exist_tmp = os.path.exists(bundle_tmp)
1729
1730 if not initial and not exist_dst and not exist_tmp:
1731 return False
1732
1733 if not exist_dst:
1734 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1735 if not exist_dst:
1736 return False
1737
1738 cmd = ['fetch']
1739 if quiet:
1740 cmd.append('--quiet')
1741 if not self.worktree:
1742 cmd.append('--update-head-ok')
1743 cmd.append(bundle_dst)
1744 for f in remote.fetch:
1745 cmd.append(str(f))
1746 cmd.append('refs/tags/*:refs/tags/*')
1747
1748 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001749 if os.path.exists(bundle_dst):
1750 os.remove(bundle_dst)
1751 if os.path.exists(bundle_tmp):
1752 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001753 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001754
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001755 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001756 if os.path.exists(dstPath):
1757 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001758
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001759 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001760 if quiet:
1761 cmd += ['--silent']
1762 if os.path.exists(tmpPath):
1763 size = os.stat(tmpPath).st_size
1764 if size >= 1024:
1765 cmd += ['--continue-at', '%d' % (size,)]
1766 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001767 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001768 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1769 cmd += ['--proxy', os.environ['http_proxy']]
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001770 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
1771 if cookiefile:
1772 cmd += ['--cookie', cookiefile]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001773 cmd += [srcUrl]
1774
1775 if IsTrace():
1776 Trace('%s', ' '.join(cmd))
1777 try:
1778 proc = subprocess.Popen(cmd)
1779 except OSError:
1780 return False
1781
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001782 curlret = proc.wait()
1783
1784 if curlret == 22:
1785 # From curl man page:
1786 # 22: HTTP page not retrieved. The requested url was not found or
1787 # returned another error with the HTTP error code being 400 or above.
1788 # This return code only appears if -f, --fail is used.
1789 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001790 print("Server does not provide clone.bundle; ignoring.",
1791 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001792 return False
1793
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001794 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001795 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001796 os.rename(tmpPath, dstPath)
1797 return True
1798 else:
1799 os.remove(tmpPath)
1800 return False
1801 else:
1802 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001803
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001804 def _Checkout(self, rev, quiet=False):
1805 cmd = ['checkout']
1806 if quiet:
1807 cmd.append('-q')
1808 cmd.append(rev)
1809 cmd.append('--')
1810 if GitCommand(self, cmd).Wait() != 0:
1811 if self._allrefs:
1812 raise GitError('%s checkout %s ' % (self.name, rev))
1813
Pierre Tardye5a21222011-03-24 16:28:18 +01001814 def _CherryPick(self, rev, quiet=False):
1815 cmd = ['cherry-pick']
1816 cmd.append(rev)
1817 cmd.append('--')
1818 if GitCommand(self, cmd).Wait() != 0:
1819 if self._allrefs:
1820 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1821
Erwan Mahea94f1622011-08-19 13:56:09 +02001822 def _Revert(self, rev, quiet=False):
1823 cmd = ['revert']
1824 cmd.append('--no-edit')
1825 cmd.append(rev)
1826 cmd.append('--')
1827 if GitCommand(self, cmd).Wait() != 0:
1828 if self._allrefs:
1829 raise GitError('%s revert %s ' % (self.name, rev))
1830
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001831 def _ResetHard(self, rev, quiet=True):
1832 cmd = ['reset', '--hard']
1833 if quiet:
1834 cmd.append('-q')
1835 cmd.append(rev)
1836 if GitCommand(self, cmd).Wait() != 0:
1837 raise GitError('%s reset --hard %s ' % (self.name, rev))
1838
1839 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001840 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001841 if onto is not None:
1842 cmd.extend(['--onto', onto])
1843 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001844 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001845 raise GitError('%s rebase %s ' % (self.name, upstream))
1846
Pierre Tardy3d125942012-05-04 12:18:12 +02001847 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001848 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001849 if ffonly:
1850 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001851 if GitCommand(self, cmd).Wait() != 0:
1852 raise GitError('%s merge %s ' % (self.name, head))
1853
1854 def _InitGitDir(self):
1855 if not os.path.exists(self.gitdir):
1856 os.makedirs(self.gitdir)
1857 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001858
Shawn O. Pearce88443382010-10-08 10:02:09 +02001859 mp = self.manifest.manifestProject
1860 ref_dir = mp.config.GetString('repo.reference')
1861
1862 if ref_dir:
1863 mirror_git = os.path.join(ref_dir, self.name + '.git')
1864 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1865 self.relpath + '.git')
1866
1867 if os.path.exists(mirror_git):
1868 ref_dir = mirror_git
1869
1870 elif os.path.exists(repo_git):
1871 ref_dir = repo_git
1872
1873 else:
1874 ref_dir = None
1875
1876 if ref_dir:
1877 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1878 os.path.join(ref_dir, 'objects') + '\n')
1879
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001880 if self.manifest.IsMirror:
1881 self.config.SetString('core.bare', 'true')
1882 else:
1883 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001884
1885 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001886 try:
1887 to_rm = os.listdir(hooks)
1888 except OSError:
1889 to_rm = []
1890 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001891 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001892 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001893
1894 m = self.manifest.manifestProject.config
1895 for key in ['user.name', 'user.email']:
1896 if m.Has(key, include_defaults = False):
1897 self.config.SetString(key, m.GetString(key))
1898
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001899 def _InitHooks(self):
1900 hooks = self._gitdir_path('hooks')
1901 if not os.path.exists(hooks):
1902 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001903 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001904 name = os.path.basename(stock_hook)
1905
Victor Boivie65e0f352011-04-18 11:23:29 +02001906 if name in ('commit-msg',) and not self.remote.review \
1907 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001908 # Don't install a Gerrit Code Review hook if this
1909 # project does not appear to use it for reviews.
1910 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001911 # Since the manifest project is one of those, but also
1912 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001913 continue
1914
1915 dst = os.path.join(hooks, name)
1916 if os.path.islink(dst):
1917 continue
1918 if os.path.exists(dst):
1919 if filecmp.cmp(stock_hook, dst, shallow=False):
1920 os.remove(dst)
1921 else:
1922 _error("%s: Not replacing %s hook", self.relpath, name)
1923 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001924 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001925 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001926 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001927 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001928 raise GitError('filesystem must support symlinks')
1929 else:
1930 raise
1931
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001932 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001933 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001934 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001935 remote.url = self.remote.url
1936 remote.review = self.remote.review
1937 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001938
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001939 if self.worktree:
1940 remote.ResetFetch(mirror=False)
1941 else:
1942 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001943 remote.Save()
1944
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001945 def _InitMRef(self):
1946 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001947 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001948
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001949 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001950 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001951
1952 def _InitAnyMRef(self, ref):
1953 cur = self.bare_ref.symref(ref)
1954
1955 if self.revisionId:
1956 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1957 msg = 'manifest set to %s' % self.revisionId
1958 dst = self.revisionId + '^0'
1959 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1960 else:
1961 remote = self.GetRemote(self.remote.name)
1962 dst = remote.ToLocal(self.revisionExpr)
1963 if cur != dst:
1964 msg = 'manifest set to %s' % self.revisionExpr
1965 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001966
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001967 def _InitWorkTree(self):
1968 dotgit = os.path.join(self.worktree, '.git')
1969 if not os.path.exists(dotgit):
1970 os.makedirs(dotgit)
1971
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001972 for name in ['config',
1973 'description',
1974 'hooks',
1975 'info',
1976 'logs',
1977 'objects',
1978 'packed-refs',
1979 'refs',
1980 'rr-cache',
1981 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001982 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001983 src = os.path.join(self.gitdir, name)
1984 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001985 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001986 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001987 else:
1988 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001989 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001990 if e.errno == errno.EPERM:
1991 raise GitError('filesystem must support symlinks')
1992 else:
1993 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001994
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001995 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001996
1997 cmd = ['read-tree', '--reset', '-u']
1998 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001999 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002000 if GitCommand(self, cmd).Wait() != 0:
2001 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002002
2003 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2004 if not os.path.exists(rr_cache):
2005 os.makedirs(rr_cache)
2006
Shawn O. Pearce93609662009-04-21 10:50:33 -07002007 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002008
2009 def _gitdir_path(self, path):
2010 return os.path.join(self.gitdir, path)
2011
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002012 def _revlist(self, *args, **kw):
2013 a = []
2014 a.extend(args)
2015 a.append('--')
2016 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002017
2018 @property
2019 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002020 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002021
2022 class _GitGetByExec(object):
2023 def __init__(self, project, bare):
2024 self._project = project
2025 self._bare = bare
2026
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002027 def LsOthers(self):
2028 p = GitCommand(self._project,
2029 ['ls-files',
2030 '-z',
2031 '--others',
2032 '--exclude-standard'],
2033 bare = False,
2034 capture_stdout = True,
2035 capture_stderr = True)
2036 if p.Wait() == 0:
2037 out = p.stdout
2038 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002039 return out[:-1].split('\0') # pylint: disable=W1401
2040 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002041 return []
2042
2043 def DiffZ(self, name, *args):
2044 cmd = [name]
2045 cmd.append('-z')
2046 cmd.extend(args)
2047 p = GitCommand(self._project,
2048 cmd,
2049 bare = False,
2050 capture_stdout = True,
2051 capture_stderr = True)
2052 try:
2053 out = p.process.stdout.read()
2054 r = {}
2055 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002056 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002057 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002058 try:
2059 info = out.next()
2060 path = out.next()
2061 except StopIteration:
2062 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002063
2064 class _Info(object):
2065 def __init__(self, path, omode, nmode, oid, nid, state):
2066 self.path = path
2067 self.src_path = None
2068 self.old_mode = omode
2069 self.new_mode = nmode
2070 self.old_id = oid
2071 self.new_id = nid
2072
2073 if len(state) == 1:
2074 self.status = state
2075 self.level = None
2076 else:
2077 self.status = state[:1]
2078 self.level = state[1:]
2079 while self.level.startswith('0'):
2080 self.level = self.level[1:]
2081
2082 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002083 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002084 if info.status in ('R', 'C'):
2085 info.src_path = info.path
2086 info.path = out.next()
2087 r[info.path] = info
2088 return r
2089 finally:
2090 p.Wait()
2091
2092 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002093 if self._bare:
2094 path = os.path.join(self._project.gitdir, HEAD)
2095 else:
2096 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002097 try:
2098 fd = open(path, 'rb')
2099 except IOError:
2100 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002101 try:
2102 line = fd.read()
2103 finally:
2104 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002105 if line.startswith('ref: '):
2106 return line[5:-1]
2107 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002108
2109 def SetHead(self, ref, message=None):
2110 cmdv = []
2111 if message is not None:
2112 cmdv.extend(['-m', message])
2113 cmdv.append(HEAD)
2114 cmdv.append(ref)
2115 self.symbolic_ref(*cmdv)
2116
2117 def DetachHead(self, new, message=None):
2118 cmdv = ['--no-deref']
2119 if message is not None:
2120 cmdv.extend(['-m', message])
2121 cmdv.append(HEAD)
2122 cmdv.append(new)
2123 self.update_ref(*cmdv)
2124
2125 def UpdateRef(self, name, new, old=None,
2126 message=None,
2127 detach=False):
2128 cmdv = []
2129 if message is not None:
2130 cmdv.extend(['-m', message])
2131 if detach:
2132 cmdv.append('--no-deref')
2133 cmdv.append(name)
2134 cmdv.append(new)
2135 if old is not None:
2136 cmdv.append(old)
2137 self.update_ref(*cmdv)
2138
2139 def DeleteRef(self, name, old=None):
2140 if not old:
2141 old = self.rev_parse(name)
2142 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002143 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002144
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002145 def rev_list(self, *args, **kw):
2146 if 'format' in kw:
2147 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2148 else:
2149 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002150 cmdv.extend(args)
2151 p = GitCommand(self._project,
2152 cmdv,
2153 bare = self._bare,
2154 capture_stdout = True,
2155 capture_stderr = True)
2156 r = []
2157 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002158 if line[-1] == '\n':
2159 line = line[:-1]
2160 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002161 if p.Wait() != 0:
2162 raise GitError('%s rev-list %s: %s' % (
2163 self._project.name,
2164 str(args),
2165 p.stderr))
2166 return r
2167
2168 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002169 """Allow arbitrary git commands using pythonic syntax.
2170
2171 This allows you to do things like:
2172 git_obj.rev_parse('HEAD')
2173
2174 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2175 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002176 Any other positional arguments will be passed to the git command, and the
2177 following keyword arguments are supported:
2178 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002179
2180 Args:
2181 name: The name of the git command to call. Any '_' characters will
2182 be replaced with '-'.
2183
2184 Returns:
2185 A callable object that will try to call git with the named command.
2186 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002187 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002188 def runner(*args, **kwargs):
2189 cmdv = []
2190 config = kwargs.pop('config', None)
2191 for k in kwargs:
2192 raise TypeError('%s() got an unexpected keyword argument %r'
2193 % (name, k))
2194 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002195 if not git_require((1, 7, 2)):
2196 raise ValueError('cannot set config on command line for %s()'
2197 % name)
Dave Borowitz091f8932012-10-23 17:01:04 -07002198 for k, v in config.iteritems():
2199 cmdv.append('-c')
2200 cmdv.append('%s=%s' % (k, v))
2201 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002202 cmdv.extend(args)
2203 p = GitCommand(self._project,
2204 cmdv,
2205 bare = self._bare,
2206 capture_stdout = True,
2207 capture_stderr = True)
2208 if p.Wait() != 0:
2209 raise GitError('%s %s: %s' % (
2210 self._project.name,
2211 name,
2212 p.stderr))
2213 r = p.stdout
2214 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2215 return r[:-1]
2216 return r
2217 return runner
2218
2219
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002220class _PriorSyncFailedError(Exception):
2221 def __str__(self):
2222 return 'prior sync failed; rebase still in progress'
2223
2224class _DirtyError(Exception):
2225 def __str__(self):
2226 return 'contains uncommitted changes'
2227
2228class _InfoMessage(object):
2229 def __init__(self, project, text):
2230 self.project = project
2231 self.text = text
2232
2233 def Print(self, syncbuf):
2234 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2235 syncbuf.out.nl()
2236
2237class _Failure(object):
2238 def __init__(self, project, why):
2239 self.project = project
2240 self.why = why
2241
2242 def Print(self, syncbuf):
2243 syncbuf.out.fail('error: %s/: %s',
2244 self.project.relpath,
2245 str(self.why))
2246 syncbuf.out.nl()
2247
2248class _Later(object):
2249 def __init__(self, project, action):
2250 self.project = project
2251 self.action = action
2252
2253 def Run(self, syncbuf):
2254 out = syncbuf.out
2255 out.project('project %s/', self.project.relpath)
2256 out.nl()
2257 try:
2258 self.action()
2259 out.nl()
2260 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002261 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002262 out.nl()
2263 return False
2264
2265class _SyncColoring(Coloring):
2266 def __init__(self, config):
2267 Coloring.__init__(self, config, 'reposync')
2268 self.project = self.printer('header', attr = 'bold')
2269 self.info = self.printer('info')
2270 self.fail = self.printer('fail', fg='red')
2271
2272class SyncBuffer(object):
2273 def __init__(self, config, detach_head=False):
2274 self._messages = []
2275 self._failures = []
2276 self._later_queue1 = []
2277 self._later_queue2 = []
2278
2279 self.out = _SyncColoring(config)
2280 self.out.redirect(sys.stderr)
2281
2282 self.detach_head = detach_head
2283 self.clean = True
2284
2285 def info(self, project, fmt, *args):
2286 self._messages.append(_InfoMessage(project, fmt % args))
2287
2288 def fail(self, project, err=None):
2289 self._failures.append(_Failure(project, err))
2290 self.clean = False
2291
2292 def later1(self, project, what):
2293 self._later_queue1.append(_Later(project, what))
2294
2295 def later2(self, project, what):
2296 self._later_queue2.append(_Later(project, what))
2297
2298 def Finish(self):
2299 self._PrintMessages()
2300 self._RunLater()
2301 self._PrintMessages()
2302 return self.clean
2303
2304 def _RunLater(self):
2305 for q in ['_later_queue1', '_later_queue2']:
2306 if not self._RunQueue(q):
2307 return
2308
2309 def _RunQueue(self, queue):
2310 for m in getattr(self, queue):
2311 if not m.Run(self):
2312 self.clean = False
2313 return False
2314 setattr(self, queue, [])
2315 return True
2316
2317 def _PrintMessages(self):
2318 for m in self._messages:
2319 m.Print(self)
2320 for m in self._failures:
2321 m.Print(self)
2322
2323 self._messages = []
2324 self._failures = []
2325
2326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002327class MetaProject(Project):
2328 """A special project housed under .repo.
2329 """
2330 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002331 Project.__init__(self,
2332 manifest = manifest,
2333 name = name,
2334 gitdir = gitdir,
2335 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002336 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002337 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002338 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002339 revisionId = None,
2340 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002341
2342 def PreSync(self):
2343 if self.Exists:
2344 cb = self.CurrentBranch
2345 if cb:
2346 base = self.GetBranch(cb).merge
2347 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002348 self.revisionExpr = base
2349 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002350
Florian Vallee5d016502012-06-07 17:19:26 +02002351 def MetaBranchSwitch(self, target):
2352 """ Prepare MetaProject for manifest branch switch
2353 """
2354
2355 # detach and delete manifest branch, allowing a new
2356 # branch to take over
2357 syncbuf = SyncBuffer(self.config, detach_head = True)
2358 self.Sync_LocalHalf(syncbuf)
2359 syncbuf.Finish()
2360
2361 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002362 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002363 capture_stdout = True,
2364 capture_stderr = True).Wait() == 0
2365
2366
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002367 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002368 def LastFetch(self):
2369 try:
2370 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2371 return os.path.getmtime(fh)
2372 except OSError:
2373 return 0
2374
2375 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002376 def HasChanges(self):
2377 """Has the remote received new commits not yet checked out?
2378 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002379 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002380 return False
2381
David Pursehouse8a68ff92012-09-24 12:15:13 +09002382 all_refs = self.bare_ref.all
2383 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002384 head = self.work_git.GetHead()
2385 if head.startswith(R_HEADS):
2386 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002387 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002388 except KeyError:
2389 head = None
2390
2391 if revid == head:
2392 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002393 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002394 return True
2395 return False