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