blob: c5ee50fc65a89993428a4d1fb321c6dfa791c7f1 [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
Doug Anderson37282b42011-03-04 11:54:18 -080015import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
18import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070019import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import re
21import shutil
22import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070023import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import sys
Che-Liang Chiou69998b02012-01-11 11:28:42 +080025import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070026import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070027
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028from color import Coloring
29from git_command import GitCommand
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070030from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090031from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080032from error import ManifestInvalidRevisionError
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070033from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034
Shawn O. Pearced237b692009-04-17 18:49:50 -070035from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070037def _lwrite(path, content):
38 lock = '%s.lock' % path
39
40 fd = open(lock, 'wb')
41 try:
42 fd.write(content)
43 finally:
44 fd.close()
45
46 try:
47 os.rename(lock, path)
48 except OSError:
49 os.remove(lock)
50 raise
51
Shawn O. Pearce48244782009-04-16 08:25:57 -070052def _error(fmt, *args):
53 msg = fmt % args
54 print >>sys.stderr, 'error: %s' % msg
55
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070056def not_rev(r):
57 return '^' + r
58
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080059def sq(r):
60 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080061
Doug Anderson8ced8642011-01-10 14:16:30 -080062_project_hook_list = None
63def _ProjectHooks():
64 """List the hooks present in the 'hooks' directory.
65
66 These hooks are project hooks and are copied to the '.git/hooks' directory
67 of all subprojects.
68
69 This function caches the list of hooks (based on the contents of the
70 'repo/hooks' directory) on the first call.
71
72 Returns:
73 A list of absolute paths to all of the files in the hooks directory.
74 """
75 global _project_hook_list
76 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080077 d = os.path.abspath(os.path.dirname(__file__))
78 d = os.path.join(d , 'hooks')
Doug Anderson8ced8642011-01-10 14:16:30 -080079 _project_hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
80 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080081
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080082
Shawn O. Pearce632768b2008-10-23 11:58:52 -070083class DownloadedChange(object):
84 _commit_cache = None
85
86 def __init__(self, project, base, change_id, ps_id, commit):
87 self.project = project
88 self.base = base
89 self.change_id = change_id
90 self.ps_id = ps_id
91 self.commit = commit
92
93 @property
94 def commits(self):
95 if self._commit_cache is None:
96 self._commit_cache = self.project.bare_git.rev_list(
97 '--abbrev=8',
98 '--abbrev-commit',
99 '--pretty=oneline',
100 '--reverse',
101 '--date-order',
102 not_rev(self.base),
103 self.commit,
104 '--')
105 return self._commit_cache
106
107
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700108class ReviewableBranch(object):
109 _commit_cache = None
110
111 def __init__(self, project, branch, base):
112 self.project = project
113 self.branch = branch
114 self.base = base
115
116 @property
117 def name(self):
118 return self.branch.name
119
120 @property
121 def commits(self):
122 if self._commit_cache is None:
123 self._commit_cache = self.project.bare_git.rev_list(
124 '--abbrev=8',
125 '--abbrev-commit',
126 '--pretty=oneline',
127 '--reverse',
128 '--date-order',
129 not_rev(self.base),
130 R_HEADS + self.name,
131 '--')
132 return self._commit_cache
133
134 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800135 def unabbrev_commits(self):
136 r = dict()
137 for commit in self.project.bare_git.rev_list(
138 not_rev(self.base),
139 R_HEADS + self.name,
140 '--'):
141 r[commit[0:8]] = commit
142 return r
143
144 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700145 def date(self):
146 return self.project.bare_git.log(
147 '--pretty=format:%cd',
148 '-n', '1',
149 R_HEADS + self.name,
150 '--')
151
Brian Harring435370c2012-07-28 15:37:04 -0700152 def UploadForReview(self, people, auto_topic=False, draft=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800153 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700154 people,
Brian Harring435370c2012-07-28 15:37:04 -0700155 auto_topic=auto_topic,
156 draft=draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700157
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700158 def GetPublishedRefs(self):
159 refs = {}
160 output = self.project.bare_git.ls_remote(
161 self.branch.remote.SshReviewUrl(self.project.UserEmail),
162 'refs/changes/*')
163 for line in output.split('\n'):
164 try:
165 (sha, ref) = line.split()
166 refs[sha] = ref
167 except ValueError:
168 pass
169
170 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171
172class StatusColoring(Coloring):
173 def __init__(self, config):
174 Coloring.__init__(self, config, 'status')
175 self.project = self.printer('header', attr = 'bold')
176 self.branch = self.printer('header', attr = 'bold')
177 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700178 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700179
180 self.added = self.printer('added', fg = 'green')
181 self.changed = self.printer('changed', fg = 'red')
182 self.untracked = self.printer('untracked', fg = 'red')
183
184
185class DiffColoring(Coloring):
186 def __init__(self, config):
187 Coloring.__init__(self, config, 'diff')
188 self.project = self.printer('header', attr = 'bold')
189
James W. Mills24c13082012-04-12 15:04:13 -0500190class _Annotation:
191 def __init__(self, name, value, keep):
192 self.name = name
193 self.value = value
194 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195
196class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800197 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700198 self.src = src
199 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800200 self.abs_src = abssrc
201 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700202
203 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800204 src = self.abs_src
205 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206 # copy file if it does not exist or is out of date
207 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
208 try:
209 # remove existing file first, since it might be read-only
210 if os.path.exists(dest):
211 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400212 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200213 dest_dir = os.path.dirname(dest)
214 if not os.path.isdir(dest_dir):
215 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 shutil.copy(src, dest)
217 # make the file read-only
218 mode = os.stat(dest)[stat.ST_MODE]
219 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
220 os.chmod(dest, mode)
221 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700222 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700224class RemoteSpec(object):
225 def __init__(self,
226 name,
227 url = None,
228 review = None):
229 self.name = name
230 self.url = url
231 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232
Doug Anderson37282b42011-03-04 11:54:18 -0800233class RepoHook(object):
234 """A RepoHook contains information about a script to run as a hook.
235
236 Hooks are used to run a python script before running an upload (for instance,
237 to run presubmit checks). Eventually, we may have hooks for other actions.
238
239 This shouldn't be confused with files in the 'repo/hooks' directory. Those
240 files are copied into each '.git/hooks' folder for each project. Repo-level
241 hooks are associated instead with repo actions.
242
243 Hooks are always python. When a hook is run, we will load the hook into the
244 interpreter and execute its main() function.
245 """
246 def __init__(self,
247 hook_type,
248 hooks_project,
249 topdir,
250 abort_if_user_denies=False):
251 """RepoHook constructor.
252
253 Params:
254 hook_type: A string representing the type of hook. This is also used
255 to figure out the name of the file containing the hook. For
256 example: 'pre-upload'.
257 hooks_project: The project containing the repo hooks. If you have a
258 manifest, this is manifest.repo_hooks_project. OK if this is None,
259 which will make the hook a no-op.
260 topdir: Repo's top directory (the one containing the .repo directory).
261 Scripts will run with CWD as this directory. If you have a manifest,
262 this is manifest.topdir
263 abort_if_user_denies: If True, we'll throw a HookError() if the user
264 doesn't allow us to run the hook.
265 """
266 self._hook_type = hook_type
267 self._hooks_project = hooks_project
268 self._topdir = topdir
269 self._abort_if_user_denies = abort_if_user_denies
270
271 # Store the full path to the script for convenience.
272 if self._hooks_project:
273 self._script_fullpath = os.path.join(self._hooks_project.worktree,
274 self._hook_type + '.py')
275 else:
276 self._script_fullpath = None
277
278 def _GetHash(self):
279 """Return a hash of the contents of the hooks directory.
280
281 We'll just use git to do this. This hash has the property that if anything
282 changes in the directory we will return a different has.
283
284 SECURITY CONSIDERATION:
285 This hash only represents the contents of files in the hook directory, not
286 any other files imported or called by hooks. Changes to imported files
287 can change the script behavior without affecting the hash.
288
289 Returns:
290 A string representing the hash. This will always be ASCII so that it can
291 be printed to the user easily.
292 """
293 assert self._hooks_project, "Must have hooks to calculate their hash."
294
295 # We will use the work_git object rather than just calling GetRevisionId().
296 # That gives us a hash of the latest checked in version of the files that
297 # the user will actually be executing. Specifically, GetRevisionId()
298 # doesn't appear to change even if a user checks out a different version
299 # of the hooks repo (via git checkout) nor if a user commits their own revs.
300 #
301 # NOTE: Local (non-committed) changes will not be factored into this hash.
302 # I think this is OK, since we're really only worried about warning the user
303 # about upstream changes.
304 return self._hooks_project.work_git.rev_parse('HEAD')
305
306 def _GetMustVerb(self):
307 """Return 'must' if the hook is required; 'should' if not."""
308 if self._abort_if_user_denies:
309 return 'must'
310 else:
311 return 'should'
312
313 def _CheckForHookApproval(self):
314 """Check to see whether this hook has been approved.
315
316 We'll look at the hash of all of the hooks. If this matches the hash that
317 the user last approved, we're done. If it doesn't, we'll ask the user
318 about approval.
319
320 Note that we ask permission for each individual hook even though we use
321 the hash of all hooks when detecting changes. We'd like the user to be
322 able to approve / deny each hook individually. We only use the hash of all
323 hooks because there is no other easy way to detect changes to local imports.
324
325 Returns:
326 True if this hook is approved to run; False otherwise.
327
328 Raises:
329 HookError: Raised if the user doesn't approve and abort_if_user_denies
330 was passed to the consturctor.
331 """
Doug Anderson37282b42011-03-04 11:54:18 -0800332 hooks_config = self._hooks_project.config
333 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
334
335 # Get the last hash that the user approved for this hook; may be None.
336 old_hash = hooks_config.GetString(git_approval_key)
337
338 # Get the current hash so we can tell if scripts changed since approval.
339 new_hash = self._GetHash()
340
341 if old_hash is not None:
342 # User previously approved hook and asked not to be prompted again.
343 if new_hash == old_hash:
344 # Approval matched. We're done.
345 return True
346 else:
347 # Give the user a reason why we're prompting, since they last told
348 # us to "never ask again".
349 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
350 self._hook_type)
351 else:
352 prompt = ''
353
354 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
355 if sys.stdout.isatty():
356 prompt += ('Repo %s run the script:\n'
357 ' %s\n'
358 '\n'
359 'Do you want to allow this script to run '
360 '(yes/yes-never-ask-again/NO)? ') % (
361 self._GetMustVerb(), self._script_fullpath)
362 response = raw_input(prompt).lower()
363 print
364
365 # User is doing a one-time approval.
366 if response in ('y', 'yes'):
367 return True
368 elif response == 'yes-never-ask-again':
369 hooks_config.SetString(git_approval_key, new_hash)
370 return True
371
372 # For anything else, we'll assume no approval.
373 if self._abort_if_user_denies:
374 raise HookError('You must allow the %s hook or use --no-verify.' %
375 self._hook_type)
376
377 return False
378
379 def _ExecuteHook(self, **kwargs):
380 """Actually execute the given hook.
381
382 This will run the hook's 'main' function in our python interpreter.
383
384 Args:
385 kwargs: Keyword arguments to pass to the hook. These are often specific
386 to the hook type. For instance, pre-upload hooks will contain
387 a project_list.
388 """
389 # Keep sys.path and CWD stashed away so that we can always restore them
390 # upon function exit.
391 orig_path = os.getcwd()
392 orig_syspath = sys.path
393
394 try:
395 # Always run hooks with CWD as topdir.
396 os.chdir(self._topdir)
397
398 # Put the hook dir as the first item of sys.path so hooks can do
399 # relative imports. We want to replace the repo dir as [0] so
400 # hooks can't import repo files.
401 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
402
403 # Exec, storing global context in the context dict. We catch exceptions
404 # and convert to a HookError w/ just the failing traceback.
405 context = {}
406 try:
407 execfile(self._script_fullpath, context)
408 except Exception:
409 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
410 traceback.format_exc(), self._hook_type))
411
412 # Running the script should have defined a main() function.
413 if 'main' not in context:
414 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
415
416
417 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
418 # We don't actually want hooks to define their main with this argument--
419 # it's there to remind them that their hook should always take **kwargs.
420 # For instance, a pre-upload hook should be defined like:
421 # def main(project_list, **kwargs):
422 #
423 # This allows us to later expand the API without breaking old hooks.
424 kwargs = kwargs.copy()
425 kwargs['hook_should_take_kwargs'] = True
426
427 # Call the main function in the hook. If the hook should cause the
428 # build to fail, it will raise an Exception. We'll catch that convert
429 # to a HookError w/ just the failing traceback.
430 try:
431 context['main'](**kwargs)
432 except Exception:
433 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
434 'above.' % (
435 traceback.format_exc(), self._hook_type))
436 finally:
437 # Restore sys.path and CWD.
438 sys.path = orig_syspath
439 os.chdir(orig_path)
440
441 def Run(self, user_allows_all_hooks, **kwargs):
442 """Run the hook.
443
444 If the hook doesn't exist (because there is no hooks project or because
445 this particular hook is not enabled), this is a no-op.
446
447 Args:
448 user_allows_all_hooks: If True, we will never prompt about running the
449 hook--we'll just assume it's OK to run it.
450 kwargs: Keyword arguments to pass to the hook. These are often specific
451 to the hook type. For instance, pre-upload hooks will contain
452 a project_list.
453
454 Raises:
455 HookError: If there was a problem finding the hook or the user declined
456 to run a required hook (from _CheckForHookApproval).
457 """
458 # No-op if there is no hooks project or if hook is disabled.
459 if ((not self._hooks_project) or
460 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
461 return
462
463 # Bail with a nice error if we can't find the hook.
464 if not os.path.isfile(self._script_fullpath):
465 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
466
467 # Make sure the user is OK with running the hook.
468 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
469 return
470
471 # Run the hook with the same version of python we're using.
472 self._ExecuteHook(**kwargs)
473
474
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700475class Project(object):
476 def __init__(self,
477 manifest,
478 name,
479 remote,
480 gitdir,
481 worktree,
482 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700483 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800484 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700485 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700486 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700487 sync_c = False,
Che-Liang Chiou69998b02012-01-11 11:28:42 +0800488 upstream = None,
489 parent = None,
490 is_derived = False):
491 """Init a Project object.
492
493 Args:
494 manifest: The XmlManifest object.
495 name: The `name` attribute of manifest.xml's project element.
496 remote: RemoteSpec object specifying its remote's properties.
497 gitdir: Absolute path of git directory.
498 worktree: Absolute path of git working tree.
499 relpath: Relative path of git working tree to repo's top directory.
500 revisionExpr: The `revision` attribute of manifest.xml's project element.
501 revisionId: git commit id for checking out.
502 rebase: The `rebase` attribute of manifest.xml's project element.
503 groups: The `groups` attribute of manifest.xml's project element.
504 sync_c: The `sync-c` attribute of manifest.xml's project element.
505 upstream: The `upstream` attribute of manifest.xml's project element.
506 parent: The parent Project object.
507 is_derived: False if the project was explicitly defined in the manifest;
508 True if the project is a discovered submodule.
509 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700510 self.manifest = manifest
511 self.name = name
512 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800513 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800514 if worktree:
515 self.worktree = worktree.replace('\\', '/')
516 else:
517 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700518 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700519 self.revisionExpr = revisionExpr
520
521 if revisionId is None \
522 and revisionExpr \
523 and IsId(revisionExpr):
524 self.revisionId = revisionExpr
525 else:
526 self.revisionId = revisionId
527
Mike Pontillod3153822012-02-28 11:53:24 -0800528 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700529 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700530 self.sync_c = sync_c
Brian Harring14a66742012-09-28 20:21:57 -0700531 self.upstream = upstream
Che-Liang Chiou69998b02012-01-11 11:28:42 +0800532 self.parent = parent
533 self.is_derived = is_derived
534 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800535
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700536 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700537 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500538 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700539 self.config = GitConfig.ForRepository(
540 gitdir = self.gitdir,
541 defaults = self.manifest.globalConfig)
542
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800543 if self.worktree:
544 self.work_git = self._GitGetByExec(self, bare=False)
545 else:
546 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700547 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700548 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700549
Doug Anderson37282b42011-03-04 11:54:18 -0800550 # This will be filled in if a project is later identified to be the
551 # project containing repo hooks.
552 self.enabled_repo_hooks = []
553
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700554 @property
Che-Liang Chiou69998b02012-01-11 11:28:42 +0800555 def Registered(self):
556 return self.parent and not self.is_derived
557
558 @property
559 def Derived(self):
560 return self.is_derived
561
562 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700563 def Exists(self):
564 return os.path.isdir(self.gitdir)
565
566 @property
567 def CurrentBranch(self):
568 """Obtain the name of the currently checked out branch.
569 The branch name omits the 'refs/heads/' prefix.
570 None is returned if the project is on a detached HEAD.
571 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700572 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700573 if b.startswith(R_HEADS):
574 return b[len(R_HEADS):]
575 return None
576
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700577 def IsRebaseInProgress(self):
578 w = self.worktree
579 g = os.path.join(w, '.git')
580 return os.path.exists(os.path.join(g, 'rebase-apply')) \
581 or os.path.exists(os.path.join(g, 'rebase-merge')) \
582 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200583
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700584 def IsDirty(self, consider_untracked=True):
585 """Is the working directory modified in some way?
586 """
587 self.work_git.update_index('-q',
588 '--unmerged',
589 '--ignore-missing',
590 '--refresh')
591 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
592 return True
593 if self.work_git.DiffZ('diff-files'):
594 return True
595 if consider_untracked and self.work_git.LsOthers():
596 return True
597 return False
598
599 _userident_name = None
600 _userident_email = None
601
602 @property
603 def UserName(self):
604 """Obtain the user's personal name.
605 """
606 if self._userident_name is None:
607 self._LoadUserIdentity()
608 return self._userident_name
609
610 @property
611 def UserEmail(self):
612 """Obtain the user's email address. This is very likely
613 to be their Gerrit login.
614 """
615 if self._userident_email is None:
616 self._LoadUserIdentity()
617 return self._userident_email
618
619 def _LoadUserIdentity(self):
620 u = self.bare_git.var('GIT_COMMITTER_IDENT')
621 m = re.compile("^(.*) <([^>]*)> ").match(u)
622 if m:
623 self._userident_name = m.group(1)
624 self._userident_email = m.group(2)
625 else:
626 self._userident_name = ''
627 self._userident_email = ''
628
629 def GetRemote(self, name):
630 """Get the configuration for a single remote.
631 """
632 return self.config.GetRemote(name)
633
634 def GetBranch(self, name):
635 """Get the configuration for a single branch.
636 """
637 return self.config.GetBranch(name)
638
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700639 def GetBranches(self):
640 """Get all existing local branches.
641 """
642 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900643 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700644 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700645
David Pursehouse8a68ff92012-09-24 12:15:13 +0900646 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700647 if name.startswith(R_HEADS):
648 name = name[len(R_HEADS):]
649 b = self.GetBranch(name)
650 b.current = name == current
651 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900652 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700653 heads[name] = b
654
David Pursehouse8a68ff92012-09-24 12:15:13 +0900655 for name, ref_id in all_refs.iteritems():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700656 if name.startswith(R_PUB):
657 name = name[len(R_PUB):]
658 b = heads.get(name)
659 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900660 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700661
662 return heads
663
Colin Cross5acde752012-03-28 20:15:45 -0700664 def MatchesGroups(self, manifest_groups):
665 """Returns true if the manifest groups specified at init should cause
666 this project to be synced.
667 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700668 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700669
670 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700671 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700672 manifest_groups: "-group1,group2"
673 the project will be matched.
Colin Cross5acde752012-03-28 20:15:45 -0700674 """
Conley Owensbb1b5f52012-08-13 13:11:18 -0700675 expanded_manifest_groups = manifest_groups or ['all', '-notdefault']
676 expanded_project_groups = ['all'] + (self.groups or [])
677
Conley Owens971de8e2012-04-16 10:36:08 -0700678 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700679 for group in expanded_manifest_groups:
680 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700681 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700682 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700683 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700684
Conley Owens971de8e2012-04-16 10:36:08 -0700685 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700686
687## Status Display ##
688
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500689 def HasChanges(self):
690 """Returns true if there are uncommitted changes.
691 """
692 self.work_git.update_index('-q',
693 '--unmerged',
694 '--ignore-missing',
695 '--refresh')
696 if self.IsRebaseInProgress():
697 return True
698
699 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
700 return True
701
702 if self.work_git.DiffZ('diff-files'):
703 return True
704
705 if self.work_git.LsOthers():
706 return True
707
708 return False
709
Terence Haddock4655e812011-03-31 12:33:34 +0200710 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700711 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200712
713 Args:
714 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700715 """
716 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200717 if output_redir == None:
718 output_redir = sys.stdout
719 print >>output_redir, ''
720 print >>output_redir, 'project %s/' % self.relpath
721 print >>output_redir, ' missing (run "repo sync")'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700722 return
723
724 self.work_git.update_index('-q',
725 '--unmerged',
726 '--ignore-missing',
727 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700728 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700729 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
730 df = self.work_git.DiffZ('diff-files')
731 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100732 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700733 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700734
735 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200736 if not output_redir == None:
737 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700738 out.project('project %-40s', self.relpath + '/')
739
740 branch = self.CurrentBranch
741 if branch is None:
742 out.nobranch('(*** NO BRANCH ***)')
743 else:
744 out.branch('branch %s', branch)
745 out.nl()
746
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700747 if rb:
748 out.important('prior sync failed; rebase still in progress')
749 out.nl()
750
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751 paths = list()
752 paths.extend(di.keys())
753 paths.extend(df.keys())
754 paths.extend(do)
755
756 paths = list(set(paths))
757 paths.sort()
758
759 for p in paths:
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900760 try:
761 i = di[p]
762 except KeyError:
763 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700764
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900765 try:
766 f = df[p]
767 except KeyError:
768 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200769
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900770 if i:
771 i_status = i.status.upper()
772 else:
773 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700774
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900775 if f:
776 f_status = f.status.lower()
777 else:
778 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700779
780 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800781 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782 i.src_path, p, i.level)
783 else:
784 line = ' %s%s\t%s' % (i_status, f_status, p)
785
786 if i and not f:
787 out.added('%s', line)
788 elif (i and f) or (not i and f):
789 out.changed('%s', line)
790 elif not i and not f:
791 out.untracked('%s', line)
792 else:
793 out.write('%s', line)
794 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200795
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700796 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700797
pelyad67872d2012-03-28 14:49:58 +0300798 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700799 """Prints the status of the repository to stdout.
800 """
801 out = DiffColoring(self.config)
802 cmd = ['diff']
803 if out.is_on:
804 cmd.append('--color')
805 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300806 if absolute_paths:
807 cmd.append('--src-prefix=a/%s/' % self.relpath)
808 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809 cmd.append('--')
810 p = GitCommand(self,
811 cmd,
812 capture_stdout = True,
813 capture_stderr = True)
814 has_diff = False
815 for line in p.process.stdout:
816 if not has_diff:
817 out.nl()
818 out.project('project %s/' % self.relpath)
819 out.nl()
820 has_diff = True
821 print line[:-1]
822 p.Wait()
823
824
825## Publish / Upload ##
826
David Pursehouse8a68ff92012-09-24 12:15:13 +0900827 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828 """Was the branch published (uploaded) for code review?
829 If so, returns the SHA-1 hash of the last published
830 state for the branch.
831 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700832 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900833 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700834 try:
835 return self.bare_git.rev_parse(key)
836 except GitError:
837 return None
838 else:
839 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900840 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700841 except KeyError:
842 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843
David Pursehouse8a68ff92012-09-24 12:15:13 +0900844 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845 """Prunes any stale published refs.
846 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900847 if all_refs is None:
848 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849 heads = set()
850 canrm = {}
David Pursehouse8a68ff92012-09-24 12:15:13 +0900851 for name, ref_id in all_refs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700852 if name.startswith(R_HEADS):
853 heads.add(name)
854 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900855 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700856
David Pursehouse8a68ff92012-09-24 12:15:13 +0900857 for name, ref_id in canrm.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858 n = name[len(R_PUB):]
859 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900860 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700861
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700862 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700863 """List any branches which can be uploaded for review.
864 """
865 heads = {}
866 pubed = {}
867
David Pursehouse8a68ff92012-09-24 12:15:13 +0900868 for name, ref_id in self._allrefs.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900870 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900872 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700873
874 ready = []
David Pursehouse8a68ff92012-09-24 12:15:13 +0900875 for branch, ref_id in heads.iteritems():
876 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700878 if selected_branch and branch != selected_branch:
879 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700880
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800881 rb = self.GetUploadableBranch(branch)
882 if rb:
883 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700884 return ready
885
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800886 def GetUploadableBranch(self, branch_name):
887 """Get a single uploadable branch, or None.
888 """
889 branch = self.GetBranch(branch_name)
890 base = branch.LocalMerge
891 if branch.LocalMerge:
892 rb = ReviewableBranch(self, branch, base)
893 if rb.commits:
894 return rb
895 return None
896
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700897 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700898 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700899 auto_topic=False,
900 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901 """Uploads the named branch for code review.
902 """
903 if branch is None:
904 branch = self.CurrentBranch
905 if branch is None:
906 raise GitError('not currently on a branch')
907
908 branch = self.GetBranch(branch)
909 if not branch.LocalMerge:
910 raise GitError('branch %s does not track a remote' % branch.name)
911 if not branch.remote.review:
912 raise GitError('remote %s has no review url' % branch.remote.name)
913
914 dest_branch = branch.merge
915 if not dest_branch.startswith(R_HEADS):
916 dest_branch = R_HEADS + dest_branch
917
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800918 if not branch.remote.projectname:
919 branch.remote.projectname = self.name
920 branch.remote.Save()
921
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800922 url = branch.remote.ReviewUrl(self.UserEmail)
923 if url is None:
924 raise UploadError('review not configured')
925 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800926
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800927 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800928 rp = ['gerrit receive-pack']
929 for e in people[0]:
930 rp.append('--reviewer=%s' % sq(e))
931 for e in people[1]:
932 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800933 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700934
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800935 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800936
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800937 if dest_branch.startswith(R_HEADS):
938 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700939
940 upload_type = 'for'
941 if draft:
942 upload_type = 'drafts'
943
944 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
945 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800946 if auto_topic:
947 ref_spec = ref_spec + '/' + branch.name
948 cmd.append(ref_spec)
949
950 if GitCommand(self, cmd, bare = True).Wait() != 0:
951 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952
953 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
954 self.bare_git.UpdateRef(R_PUB + branch.name,
955 R_HEADS + branch.name,
956 message = msg)
957
958
959## Sync ##
960
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700961 def Sync_NetworkHalf(self,
962 quiet=False,
963 is_new=None,
964 current_branch_only=False,
965 clone_bundle=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966 """Perform only the network IO portion of the sync process.
967 Local working directory/branch state is not affected.
968 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700969 if is_new is None:
970 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200971 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 self._InitGitDir()
973 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700974
975 if is_new:
976 alt = os.path.join(self.gitdir, 'objects/info/alternates')
977 try:
978 fd = open(alt, 'rb')
979 try:
980 alt_dir = fd.readline().rstrip()
981 finally:
982 fd.close()
983 except IOError:
984 alt_dir = None
985 else:
986 alt_dir = None
987
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700988 if clone_bundle \
989 and alt_dir is None \
990 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700991 is_new = False
992
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -0700993 if not current_branch_only:
994 if self.sync_c:
995 current_branch_only = True
996 elif not self.manifest._loaded:
997 # Manifest cannot check defaults until it syncs.
998 current_branch_only = False
999 elif self.manifest.default.sync_c:
1000 current_branch_only = True
1001
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001002 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1003 current_branch_only=current_branch_only):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001004 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001005
1006 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001007 self._InitMRef()
1008 else:
1009 self._InitMirrorHead()
1010 try:
1011 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1012 except OSError:
1013 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001014 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001015
1016 def PostRepoUpgrade(self):
1017 self._InitHooks()
1018
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001019 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001020 for copyfile in self.copyfiles:
1021 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022
David Pursehouse8a68ff92012-09-24 12:15:13 +09001023 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001024 if self.revisionId:
1025 return self.revisionId
1026
1027 rem = self.GetRemote(self.remote.name)
1028 rev = rem.ToLocal(self.revisionExpr)
1029
David Pursehouse8a68ff92012-09-24 12:15:13 +09001030 if all_refs is not None and rev in all_refs:
1031 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001032
1033 try:
1034 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1035 except GitError:
1036 raise ManifestInvalidRevisionError(
1037 'revision %s in %s not found' % (self.revisionExpr,
1038 self.name))
1039
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001040 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041 """Perform only the local IO portion of the sync process.
1042 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001043 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001044 all_refs = self.bare_ref.all
1045 self.CleanPublishedCache(all_refs)
1046 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001047
1048 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001049 head = self.work_git.GetHead()
1050 if head.startswith(R_HEADS):
1051 branch = head[len(R_HEADS):]
1052 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001053 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001054 except KeyError:
1055 head = None
1056 else:
1057 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001059 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060 # Currently on a detached HEAD. The user is assumed to
1061 # not have any local modifications worth worrying about.
1062 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001063 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001064 syncbuf.fail(self, _PriorSyncFailedError())
1065 return
1066
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001067 if head == revid:
1068 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001069 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001070 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001071 if not syncbuf.detach_head:
1072 return
1073 else:
1074 lost = self._revlist(not_rev(revid), HEAD)
1075 if lost:
1076 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001077
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001079 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001080 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001081 syncbuf.fail(self, e)
1082 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001084 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001086 if head == revid:
1087 # No changes; don't do anything further.
1088 #
1089 return
1090
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001091 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001092
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001093 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001094 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001095 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001096 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001097 syncbuf.info(self,
1098 "leaving %s; does not track upstream",
1099 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001101 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001102 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001103 syncbuf.fail(self, e)
1104 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001105 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001106 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001107
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001108 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001109 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001110 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001111 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001112 if not_merged:
1113 if upstream_gain:
1114 # The user has published this branch and some of those
1115 # commits are not yet merged upstream. We do not want
1116 # to rewrite the published commits so we punt.
1117 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001118 syncbuf.fail(self,
1119 "branch %s is published (but not merged) and is now %d commits behind"
1120 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001121 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001122 elif pub == head:
1123 # All published commits are merged, and thus we are a
1124 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001125 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001126 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001127 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001128 self._CopyFiles()
1129 syncbuf.later1(self, _doff)
1130 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001131
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001132 # Examine the local commits not in the remote. Find the
1133 # last one attributed to this user, if any.
1134 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001135 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001136 last_mine = None
1137 cnt_mine = 0
1138 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001139 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001140 if committer_email == self.UserEmail:
1141 last_mine = commit_id
1142 cnt_mine += 1
1143
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001144 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001145 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146
1147 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001148 syncbuf.fail(self, _DirtyError())
1149 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001150
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001151 # If the upstream switched on us, warn the user.
1152 #
1153 if branch.merge != self.revisionExpr:
1154 if branch.merge and self.revisionExpr:
1155 syncbuf.info(self,
1156 'manifest switched %s...%s',
1157 branch.merge,
1158 self.revisionExpr)
1159 elif branch.merge:
1160 syncbuf.info(self,
1161 'manifest no longer tracks %s',
1162 branch.merge)
1163
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001164 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001165 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001166 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001168 syncbuf.info(self,
1169 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001170 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001171
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001172 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001173 if not ID_RE.match(self.revisionExpr):
1174 # in case of manifest sync the revisionExpr might be a SHA1
1175 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176 branch.Save()
1177
Mike Pontillod3153822012-02-28 11:53:24 -08001178 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001179 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001180 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001181 self._CopyFiles()
1182 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001183 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001184 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001185 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001186 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001187 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001188 syncbuf.fail(self, e)
1189 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001191 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001192 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001193 self._CopyFiles()
1194 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001196 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001197 # dest should already be an absolute path, but src is project relative
1198 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001199 abssrc = os.path.join(self.worktree, src)
1200 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201
James W. Mills24c13082012-04-12 15:04:13 -05001202 def AddAnnotation(self, name, value, keep):
1203 self.annotations.append(_Annotation(name, value, keep))
1204
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001205 def DownloadPatchSet(self, change_id, patch_id):
1206 """Download a single patch set of a single change to FETCH_HEAD.
1207 """
1208 remote = self.GetRemote(self.remote.name)
1209
1210 cmd = ['fetch', remote.name]
1211 cmd.append('refs/changes/%2.2d/%d/%d' \
1212 % (change_id % 100, change_id, patch_id))
1213 cmd.extend(map(lambda x: str(x), remote.fetch))
1214 if GitCommand(self, cmd, bare=True).Wait() != 0:
1215 return None
1216 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001217 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001218 change_id,
1219 patch_id,
1220 self.bare_git.rev_parse('FETCH_HEAD'))
1221
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001222
1223## Branch Management ##
1224
1225 def StartBranch(self, name):
1226 """Create a new branch off the manifest's revision.
1227 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001228 head = self.work_git.GetHead()
1229 if head == (R_HEADS + name):
1230 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001231
David Pursehouse8a68ff92012-09-24 12:15:13 +09001232 all_refs = self.bare_ref.all
1233 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001234 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001235 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001236 capture_stdout = True,
1237 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001238
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001239 branch = self.GetBranch(name)
1240 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001241 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001242 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001243
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001244 if head.startswith(R_HEADS):
1245 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001246 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001247 except KeyError:
1248 head = None
1249
1250 if revid and head and revid == head:
1251 ref = os.path.join(self.gitdir, R_HEADS + name)
1252 try:
1253 os.makedirs(os.path.dirname(ref))
1254 except OSError:
1255 pass
1256 _lwrite(ref, '%s\n' % revid)
1257 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1258 'ref: %s%s\n' % (R_HEADS, name))
1259 branch.Save()
1260 return True
1261
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001262 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001263 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001264 capture_stdout = True,
1265 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001266 branch.Save()
1267 return True
1268 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001269
Wink Saville02d79452009-04-10 13:01:24 -07001270 def CheckoutBranch(self, name):
1271 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001272
1273 Args:
1274 name: The name of the branch to checkout.
1275
1276 Returns:
1277 True if the checkout succeeded; False if it didn't; None if the branch
1278 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001279 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001280 rev = R_HEADS + name
1281 head = self.work_git.GetHead()
1282 if head == rev:
1283 # Already on the branch
1284 #
1285 return True
Wink Saville02d79452009-04-10 13:01:24 -07001286
David Pursehouse8a68ff92012-09-24 12:15:13 +09001287 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001288 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001289 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001290 except KeyError:
1291 # Branch does not exist in this project
1292 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001293 return None
Wink Saville02d79452009-04-10 13:01:24 -07001294
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001295 if head.startswith(R_HEADS):
1296 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001297 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001298 except KeyError:
1299 head = None
1300
1301 if head == revid:
1302 # Same revision; just update HEAD to point to the new
1303 # target branch, but otherwise take no other action.
1304 #
1305 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1306 'ref: %s%s\n' % (R_HEADS, name))
1307 return True
1308
1309 return GitCommand(self,
1310 ['checkout', name, '--'],
1311 capture_stdout = True,
1312 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001313
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001314 def AbandonBranch(self, name):
1315 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001316
1317 Args:
1318 name: The name of the branch to abandon.
1319
1320 Returns:
1321 True if the abandon succeeded; False if it didn't; None if the branch
1322 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001323 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001324 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001325 all_refs = self.bare_ref.all
1326 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001327 # Doesn't exist
1328 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001329
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001330 head = self.work_git.GetHead()
1331 if head == rev:
1332 # We can't destroy the branch while we are sitting
1333 # on it. Switch to a detached HEAD.
1334 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001335 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001336
David Pursehouse8a68ff92012-09-24 12:15:13 +09001337 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001338 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001339 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1340 '%s\n' % revid)
1341 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001342 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001343
1344 return GitCommand(self,
1345 ['branch', '-D', name],
1346 capture_stdout = True,
1347 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001348
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001349 def PruneHeads(self):
1350 """Prune any topic branches already merged into upstream.
1351 """
1352 cb = self.CurrentBranch
1353 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001354 left = self._allrefs
1355 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001356 if name.startswith(R_HEADS):
1357 name = name[len(R_HEADS):]
1358 if cb is None or name != cb:
1359 kill.append(name)
1360
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001361 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001362 if cb is not None \
1363 and not self._revlist(HEAD + '...' + rev) \
1364 and not self.IsDirty(consider_untracked = False):
1365 self.work_git.DetachHead(HEAD)
1366 kill.append(cb)
1367
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001369 old = self.bare_git.GetHead()
1370 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001371 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1372
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373 try:
1374 self.bare_git.DetachHead(rev)
1375
1376 b = ['branch', '-d']
1377 b.extend(kill)
1378 b = GitCommand(self, b, bare=True,
1379 capture_stdout=True,
1380 capture_stderr=True)
1381 b.Wait()
1382 finally:
1383 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001384 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001386 for branch in kill:
1387 if (R_HEADS + branch) not in left:
1388 self.CleanPublishedCache()
1389 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001390
1391 if cb and cb not in kill:
1392 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001393 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394
1395 kept = []
1396 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001397 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398 branch = self.GetBranch(branch)
1399 base = branch.LocalMerge
1400 if not base:
1401 base = rev
1402 kept.append(ReviewableBranch(self, branch, base))
1403 return kept
1404
1405
Che-Liang Chiou69998b02012-01-11 11:28:42 +08001406## Submodule Management ##
1407
1408 def GetRegisteredSubprojects(self):
1409 result = []
1410 def rec(subprojects):
1411 if not subprojects:
1412 return
1413 result.extend(subprojects)
1414 for p in subprojects:
1415 rec(p.subprojects)
1416 rec(self.subprojects)
1417 return result
1418
1419 def _GetSubmodules(self):
1420 # Unfortunately we cannot call `git submodule status --recursive` here
1421 # because the working tree might not exist yet, and it cannot be used
1422 # without a working tree in its current implementation.
1423
Che-Liang Chiouab8f9112012-10-25 13:44:11 -07001424 def get_submodules(gitdir, rev):
Che-Liang Chiou69998b02012-01-11 11:28:42 +08001425 # Parse .gitmodules for submodule sub_paths and sub_urls
1426 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1427 if not sub_paths:
1428 return []
1429 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1430 # revision of submodule repository
1431 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1432 submodules = []
1433 for sub_path, sub_url in zip(sub_paths, sub_urls):
1434 try:
1435 sub_rev = sub_revs[sub_path]
1436 except KeyError:
1437 # Ignore non-exist submodules
1438 continue
Che-Liang Chiou69998b02012-01-11 11:28:42 +08001439 submodules.append((sub_rev, sub_path, sub_url))
1440 return submodules
1441
1442 re_path = re.compile(r'submodule.(\w+).path')
1443 re_url = re.compile(r'submodule.(\w+).url')
1444 def parse_gitmodules(gitdir, rev):
1445 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1446 try:
1447 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1448 bare = True, gitdir = gitdir)
Che-Liang Chiouab8f9112012-10-25 13:44:11 -07001449 except GitError:
Che-Liang Chiou69998b02012-01-11 11:28:42 +08001450 return [], []
1451 if p.Wait() != 0:
1452 return [], []
1453
1454 gitmodules_lines = []
1455 fd, temp_gitmodules_path = tempfile.mkstemp()
1456 try:
1457 os.write(fd, p.stdout)
1458 os.close(fd)
1459 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1460 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1461 bare = True, gitdir = gitdir)
1462 if p.Wait() != 0:
1463 return [], []
1464 gitmodules_lines = p.stdout.split('\n')
Che-Liang Chiouab8f9112012-10-25 13:44:11 -07001465 except GitError:
Che-Liang Chiou69998b02012-01-11 11:28:42 +08001466 return [], []
1467 finally:
1468 os.remove(temp_gitmodules_path)
1469
1470 names = set()
1471 paths = {}
1472 urls = {}
1473 for line in gitmodules_lines:
1474 if not line:
1475 continue
1476 key, value = line.split('=')
1477 m = re_path.match(key)
1478 if m:
1479 names.add(m.group(1))
1480 paths[m.group(1)] = value
1481 continue
1482 m = re_url.match(key)
1483 if m:
1484 names.add(m.group(1))
1485 urls[m.group(1)] = value
1486 continue
1487 names = sorted(names)
1488 return [paths[name] for name in names], [urls[name] for name in names]
1489
1490 def git_ls_tree(gitdir, rev, paths):
1491 cmd = ['ls-tree', rev, '--']
1492 cmd.extend(paths)
1493 try:
1494 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1495 bare = True, gitdir = gitdir)
1496 except GitError:
1497 return []
1498 if p.Wait() != 0:
1499 return []
1500 objects = {}
1501 for line in p.stdout.split('\n'):
1502 if not line.strip():
1503 continue
1504 object_rev, object_path = line.split()[2:4]
1505 objects[object_path] = object_rev
1506 return objects
1507
1508 try:
1509 rev = self.GetRevisionId()
1510 except GitError:
1511 return []
Che-Liang Chiouab8f9112012-10-25 13:44:11 -07001512 return get_submodules(self.gitdir, rev)
Che-Liang Chiou69998b02012-01-11 11:28:42 +08001513
1514 def GetDerivedSubprojects(self):
1515 result = []
1516 if not self.Exists:
1517 # If git repo does not exist yet, querying its submodules will
1518 # mess up its states; so return here.
1519 return result
1520 for rev, path, url in self._GetSubmodules():
1521 name = self.manifest.GetSubprojectName(self, path)
1522 project = self.manifest.projects.get(name)
1523 if project and project.Registered:
1524 # If it has been registered, skip it because we are searching
1525 # derived subprojects, but search for its derived subprojects.
1526 result.extend(project.GetDerivedSubprojects())
1527 continue
1528 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1529 remote = RemoteSpec(self.remote.name,
1530 url = url,
1531 review = self.remote.review)
1532 subproject = Project(manifest = self.manifest,
1533 name = name,
1534 remote = remote,
1535 gitdir = gitdir,
1536 worktree = worktree,
1537 relpath = relpath,
1538 revisionExpr = self.revisionExpr,
1539 revisionId = rev,
1540 rebase = self.rebase,
1541 groups = self.groups,
1542 sync_c = self.sync_c,
1543 parent = self,
1544 is_derived = True)
1545 result.append(subproject)
1546 result.extend(subproject.GetDerivedSubprojects())
1547 return result
1548
1549
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001550## Direct Git Commands ##
1551
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001552 def _RemoteFetch(self, name=None,
1553 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001554 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001555 quiet=False,
1556 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001557
1558 is_sha1 = False
1559 tag_name = None
1560
Brian Harring14a66742012-09-28 20:21:57 -07001561 def CheckForSha1():
1562 try:
1563 # if revision (sha or tag) is not present then following function
1564 # throws an error.
1565 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1566 return True
1567 except GitError:
1568 # There is no such persistent revision. We have to fetch it.
1569 return False
1570
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001571 if current_branch_only:
1572 if ID_RE.match(self.revisionExpr) is not None:
1573 is_sha1 = True
1574 elif self.revisionExpr.startswith(R_TAGS):
1575 # this is a tag and its sha1 value should never change
1576 tag_name = self.revisionExpr[len(R_TAGS):]
1577
1578 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001579 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001580 return True
Brian Harring14a66742012-09-28 20:21:57 -07001581 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1582 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001583
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001584 if not name:
1585 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001586
1587 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001588 remote = self.GetRemote(name)
1589 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001590 ssh_proxy = True
1591
Shawn O. Pearce88443382010-10-08 10:02:09 +02001592 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001593 if alt_dir and 'objects' == os.path.basename(alt_dir):
1594 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001595 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1596 remote = self.GetRemote(name)
1597
David Pursehouse8a68ff92012-09-24 12:15:13 +09001598 all_refs = self.bare_ref.all
1599 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001600 tmp = set()
1601
David Pursehouse8a68ff92012-09-24 12:15:13 +09001602 for r, ref_id in GitRefs(ref_dir).all.iteritems():
1603 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001604 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001605 all_refs[r] = ref_id
1606 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001607 continue
1608
David Pursehouse8a68ff92012-09-24 12:15:13 +09001609 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001610 continue
1611
David Pursehouse8a68ff92012-09-24 12:15:13 +09001612 r = 'refs/_alt/%s' % ref_id
1613 all_refs[r] = ref_id
1614 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001615 tmp.add(r)
1616
David Pursehouse8a68ff92012-09-24 12:15:13 +09001617 ref_names = list(all_refs.keys())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001618 ref_names.sort()
1619
1620 tmp_packed = ''
1621 old_packed = ''
1622
1623 for r in ref_names:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001624 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001625 tmp_packed += line
1626 if r not in tmp:
1627 old_packed += line
1628
1629 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001630 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001631 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001632
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001633 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001634
1635 # The --depth option only affects the initial fetch; after that we'll do
1636 # full fetches of changes.
1637 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1638 if depth and initial:
1639 cmd.append('--depth=%s' % depth)
1640
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001641 if quiet:
1642 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001643 if not self.worktree:
1644 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001645 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001646
Brian Harring14a66742012-09-28 20:21:57 -07001647 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001648 # Fetch whole repo
1649 cmd.append('--tags')
1650 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1651 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001652 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001653 cmd.append(tag_name)
1654 else:
1655 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001656 if is_sha1:
1657 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001658 if branch.startswith(R_HEADS):
1659 branch = branch[len(R_HEADS):]
1660 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001661
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001662 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001663 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001664 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1665 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001666 ok = True
1667 break
Brian Harring14a66742012-09-28 20:21:57 -07001668 elif current_branch_only and is_sha1 and ret == 128:
1669 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1670 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1671 # abort the optimization attempt and do a full sync.
1672 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001673 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001674
1675 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001676 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001677 if old_packed != '':
1678 _lwrite(packed_refs, old_packed)
1679 else:
1680 os.remove(packed_refs)
1681 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001682
1683 if is_sha1 and current_branch_only and self.upstream:
1684 # We just synced the upstream given branch; verify we
1685 # got what we wanted, else trigger a second run of all
1686 # refs.
1687 if not CheckForSha1():
1688 return self._RemoteFetch(name=name, current_branch_only=False,
1689 initial=False, quiet=quiet, alt_dir=alt_dir)
1690
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001691 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001692
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001693 def _ApplyCloneBundle(self, initial=False, quiet=False):
1694 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1695 return False
1696
1697 remote = self.GetRemote(self.remote.name)
1698 bundle_url = remote.url + '/clone.bundle'
1699 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001700 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1701 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001702 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1703 return False
1704
1705 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1706 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1707
1708 exist_dst = os.path.exists(bundle_dst)
1709 exist_tmp = os.path.exists(bundle_tmp)
1710
1711 if not initial and not exist_dst and not exist_tmp:
1712 return False
1713
1714 if not exist_dst:
1715 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1716 if not exist_dst:
1717 return False
1718
1719 cmd = ['fetch']
1720 if quiet:
1721 cmd.append('--quiet')
1722 if not self.worktree:
1723 cmd.append('--update-head-ok')
1724 cmd.append(bundle_dst)
1725 for f in remote.fetch:
1726 cmd.append(str(f))
1727 cmd.append('refs/tags/*:refs/tags/*')
1728
1729 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001730 if os.path.exists(bundle_dst):
1731 os.remove(bundle_dst)
1732 if os.path.exists(bundle_tmp):
1733 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001734 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001735
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001736 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001737 if os.path.exists(dstPath):
1738 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001739
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001740 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001741 if quiet:
1742 cmd += ['--silent']
1743 if os.path.exists(tmpPath):
1744 size = os.stat(tmpPath).st_size
1745 if size >= 1024:
1746 cmd += ['--continue-at', '%d' % (size,)]
1747 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001748 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001749 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1750 cmd += ['--proxy', os.environ['http_proxy']]
1751 cmd += [srcUrl]
1752
1753 if IsTrace():
1754 Trace('%s', ' '.join(cmd))
1755 try:
1756 proc = subprocess.Popen(cmd)
1757 except OSError:
1758 return False
1759
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001760 curlret = proc.wait()
1761
1762 if curlret == 22:
1763 # From curl man page:
1764 # 22: HTTP page not retrieved. The requested url was not found or
1765 # returned another error with the HTTP error code being 400 or above.
1766 # This return code only appears if -f, --fail is used.
1767 if not quiet:
1768 print >> sys.stderr, "Server does not provide clone.bundle; ignoring."
1769 return False
1770
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001771 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001772 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001773 os.rename(tmpPath, dstPath)
1774 return True
1775 else:
1776 os.remove(tmpPath)
1777 return False
1778 else:
1779 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001780
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001781 def _Checkout(self, rev, quiet=False):
1782 cmd = ['checkout']
1783 if quiet:
1784 cmd.append('-q')
1785 cmd.append(rev)
1786 cmd.append('--')
1787 if GitCommand(self, cmd).Wait() != 0:
1788 if self._allrefs:
1789 raise GitError('%s checkout %s ' % (self.name, rev))
1790
Pierre Tardye5a21222011-03-24 16:28:18 +01001791 def _CherryPick(self, rev, quiet=False):
1792 cmd = ['cherry-pick']
1793 cmd.append(rev)
1794 cmd.append('--')
1795 if GitCommand(self, cmd).Wait() != 0:
1796 if self._allrefs:
1797 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1798
Erwan Mahea94f1622011-08-19 13:56:09 +02001799 def _Revert(self, rev, quiet=False):
1800 cmd = ['revert']
1801 cmd.append('--no-edit')
1802 cmd.append(rev)
1803 cmd.append('--')
1804 if GitCommand(self, cmd).Wait() != 0:
1805 if self._allrefs:
1806 raise GitError('%s revert %s ' % (self.name, rev))
1807
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001808 def _ResetHard(self, rev, quiet=True):
1809 cmd = ['reset', '--hard']
1810 if quiet:
1811 cmd.append('-q')
1812 cmd.append(rev)
1813 if GitCommand(self, cmd).Wait() != 0:
1814 raise GitError('%s reset --hard %s ' % (self.name, rev))
1815
1816 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001817 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001818 if onto is not None:
1819 cmd.extend(['--onto', onto])
1820 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001821 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001822 raise GitError('%s rebase %s ' % (self.name, upstream))
1823
Pierre Tardy3d125942012-05-04 12:18:12 +02001824 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001825 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001826 if ffonly:
1827 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001828 if GitCommand(self, cmd).Wait() != 0:
1829 raise GitError('%s merge %s ' % (self.name, head))
1830
1831 def _InitGitDir(self):
1832 if not os.path.exists(self.gitdir):
1833 os.makedirs(self.gitdir)
1834 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001835
Shawn O. Pearce88443382010-10-08 10:02:09 +02001836 mp = self.manifest.manifestProject
1837 ref_dir = mp.config.GetString('repo.reference')
1838
1839 if ref_dir:
1840 mirror_git = os.path.join(ref_dir, self.name + '.git')
1841 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1842 self.relpath + '.git')
1843
1844 if os.path.exists(mirror_git):
1845 ref_dir = mirror_git
1846
1847 elif os.path.exists(repo_git):
1848 ref_dir = repo_git
1849
1850 else:
1851 ref_dir = None
1852
1853 if ref_dir:
1854 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1855 os.path.join(ref_dir, 'objects') + '\n')
1856
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001857 if self.manifest.IsMirror:
1858 self.config.SetString('core.bare', 'true')
1859 else:
1860 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001861
1862 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001863 try:
1864 to_rm = os.listdir(hooks)
1865 except OSError:
1866 to_rm = []
1867 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001868 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001869 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001870
1871 m = self.manifest.manifestProject.config
1872 for key in ['user.name', 'user.email']:
1873 if m.Has(key, include_defaults = False):
1874 self.config.SetString(key, m.GetString(key))
1875
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001876 def _InitHooks(self):
1877 hooks = self._gitdir_path('hooks')
1878 if not os.path.exists(hooks):
1879 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001880 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001881 name = os.path.basename(stock_hook)
1882
Victor Boivie65e0f352011-04-18 11:23:29 +02001883 if name in ('commit-msg',) and not self.remote.review \
1884 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001885 # Don't install a Gerrit Code Review hook if this
1886 # project does not appear to use it for reviews.
1887 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001888 # Since the manifest project is one of those, but also
1889 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001890 continue
1891
1892 dst = os.path.join(hooks, name)
1893 if os.path.islink(dst):
1894 continue
1895 if os.path.exists(dst):
1896 if filecmp.cmp(stock_hook, dst, shallow=False):
1897 os.remove(dst)
1898 else:
1899 _error("%s: Not replacing %s hook", self.relpath, name)
1900 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001901 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001902 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001903 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001904 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001905 raise GitError('filesystem must support symlinks')
1906 else:
1907 raise
1908
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001909 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001910 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001911 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001912 remote.url = self.remote.url
1913 remote.review = self.remote.review
1914 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001915
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001916 if self.worktree:
1917 remote.ResetFetch(mirror=False)
1918 else:
1919 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001920 remote.Save()
1921
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001922 def _InitMRef(self):
1923 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001924 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001925
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001926 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001927 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001928
1929 def _InitAnyMRef(self, ref):
1930 cur = self.bare_ref.symref(ref)
1931
1932 if self.revisionId:
1933 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1934 msg = 'manifest set to %s' % self.revisionId
1935 dst = self.revisionId + '^0'
1936 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1937 else:
1938 remote = self.GetRemote(self.remote.name)
1939 dst = remote.ToLocal(self.revisionExpr)
1940 if cur != dst:
1941 msg = 'manifest set to %s' % self.revisionExpr
1942 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001943
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001944 def _InitWorkTree(self):
1945 dotgit = os.path.join(self.worktree, '.git')
1946 if not os.path.exists(dotgit):
1947 os.makedirs(dotgit)
1948
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001949 for name in ['config',
1950 'description',
1951 'hooks',
1952 'info',
1953 'logs',
1954 'objects',
1955 'packed-refs',
1956 'refs',
1957 'rr-cache',
1958 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001959 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001960 src = os.path.join(self.gitdir, name)
1961 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001962 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001963 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001964 else:
1965 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001966 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001967 if e.errno == errno.EPERM:
1968 raise GitError('filesystem must support symlinks')
1969 else:
1970 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001971
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001972 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001973
1974 cmd = ['read-tree', '--reset', '-u']
1975 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001976 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001977 if GitCommand(self, cmd).Wait() != 0:
1978 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001979
1980 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1981 if not os.path.exists(rr_cache):
1982 os.makedirs(rr_cache)
1983
Shawn O. Pearce93609662009-04-21 10:50:33 -07001984 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001985
1986 def _gitdir_path(self, path):
1987 return os.path.join(self.gitdir, path)
1988
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001989 def _revlist(self, *args, **kw):
1990 a = []
1991 a.extend(args)
1992 a.append('--')
1993 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001994
1995 @property
1996 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001997 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001998
1999 class _GitGetByExec(object):
2000 def __init__(self, project, bare):
2001 self._project = project
2002 self._bare = bare
2003
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002004 def LsOthers(self):
2005 p = GitCommand(self._project,
2006 ['ls-files',
2007 '-z',
2008 '--others',
2009 '--exclude-standard'],
2010 bare = False,
2011 capture_stdout = True,
2012 capture_stderr = True)
2013 if p.Wait() == 0:
2014 out = p.stdout
2015 if out:
2016 return out[:-1].split("\0")
2017 return []
2018
2019 def DiffZ(self, name, *args):
2020 cmd = [name]
2021 cmd.append('-z')
2022 cmd.extend(args)
2023 p = GitCommand(self._project,
2024 cmd,
2025 bare = False,
2026 capture_stdout = True,
2027 capture_stderr = True)
2028 try:
2029 out = p.process.stdout.read()
2030 r = {}
2031 if out:
2032 out = iter(out[:-1].split('\0'))
2033 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002034 try:
2035 info = out.next()
2036 path = out.next()
2037 except StopIteration:
2038 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002039
2040 class _Info(object):
2041 def __init__(self, path, omode, nmode, oid, nid, state):
2042 self.path = path
2043 self.src_path = None
2044 self.old_mode = omode
2045 self.new_mode = nmode
2046 self.old_id = oid
2047 self.new_id = nid
2048
2049 if len(state) == 1:
2050 self.status = state
2051 self.level = None
2052 else:
2053 self.status = state[:1]
2054 self.level = state[1:]
2055 while self.level.startswith('0'):
2056 self.level = self.level[1:]
2057
2058 info = info[1:].split(' ')
2059 info =_Info(path, *info)
2060 if info.status in ('R', 'C'):
2061 info.src_path = info.path
2062 info.path = out.next()
2063 r[info.path] = info
2064 return r
2065 finally:
2066 p.Wait()
2067
2068 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002069 if self._bare:
2070 path = os.path.join(self._project.gitdir, HEAD)
2071 else:
2072 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002073 fd = open(path, 'rb')
2074 try:
2075 line = fd.read()
2076 finally:
2077 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002078 if line.startswith('ref: '):
2079 return line[5:-1]
2080 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002081
2082 def SetHead(self, ref, message=None):
2083 cmdv = []
2084 if message is not None:
2085 cmdv.extend(['-m', message])
2086 cmdv.append(HEAD)
2087 cmdv.append(ref)
2088 self.symbolic_ref(*cmdv)
2089
2090 def DetachHead(self, new, message=None):
2091 cmdv = ['--no-deref']
2092 if message is not None:
2093 cmdv.extend(['-m', message])
2094 cmdv.append(HEAD)
2095 cmdv.append(new)
2096 self.update_ref(*cmdv)
2097
2098 def UpdateRef(self, name, new, old=None,
2099 message=None,
2100 detach=False):
2101 cmdv = []
2102 if message is not None:
2103 cmdv.extend(['-m', message])
2104 if detach:
2105 cmdv.append('--no-deref')
2106 cmdv.append(name)
2107 cmdv.append(new)
2108 if old is not None:
2109 cmdv.append(old)
2110 self.update_ref(*cmdv)
2111
2112 def DeleteRef(self, name, old=None):
2113 if not old:
2114 old = self.rev_parse(name)
2115 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002116 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002117
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002118 def rev_list(self, *args, **kw):
2119 if 'format' in kw:
2120 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2121 else:
2122 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002123 cmdv.extend(args)
2124 p = GitCommand(self._project,
2125 cmdv,
2126 bare = self._bare,
2127 capture_stdout = True,
2128 capture_stderr = True)
2129 r = []
2130 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002131 if line[-1] == '\n':
2132 line = line[:-1]
2133 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134 if p.Wait() != 0:
2135 raise GitError('%s rev-list %s: %s' % (
2136 self._project.name,
2137 str(args),
2138 p.stderr))
2139 return r
2140
2141 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002142 """Allow arbitrary git commands using pythonic syntax.
2143
2144 This allows you to do things like:
2145 git_obj.rev_parse('HEAD')
2146
2147 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2148 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002149 Any other positional arguments will be passed to the git command, and the
2150 following keyword arguments are supported:
2151 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002152
2153 Args:
2154 name: The name of the git command to call. Any '_' characters will
2155 be replaced with '-'.
2156
2157 Returns:
2158 A callable object that will try to call git with the named command.
2159 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002160 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002161 def runner(*args, **kwargs):
2162 cmdv = []
2163 config = kwargs.pop('config', None)
2164 for k in kwargs:
2165 raise TypeError('%s() got an unexpected keyword argument %r'
2166 % (name, k))
2167 if config is not None:
2168 for k, v in config.iteritems():
2169 cmdv.append('-c')
2170 cmdv.append('%s=%s' % (k, v))
2171 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002172 cmdv.extend(args)
2173 p = GitCommand(self._project,
2174 cmdv,
2175 bare = self._bare,
2176 capture_stdout = True,
2177 capture_stderr = True)
2178 if p.Wait() != 0:
2179 raise GitError('%s %s: %s' % (
2180 self._project.name,
2181 name,
2182 p.stderr))
2183 r = p.stdout
2184 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2185 return r[:-1]
2186 return r
2187 return runner
2188
2189
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002190class _PriorSyncFailedError(Exception):
2191 def __str__(self):
2192 return 'prior sync failed; rebase still in progress'
2193
2194class _DirtyError(Exception):
2195 def __str__(self):
2196 return 'contains uncommitted changes'
2197
2198class _InfoMessage(object):
2199 def __init__(self, project, text):
2200 self.project = project
2201 self.text = text
2202
2203 def Print(self, syncbuf):
2204 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2205 syncbuf.out.nl()
2206
2207class _Failure(object):
2208 def __init__(self, project, why):
2209 self.project = project
2210 self.why = why
2211
2212 def Print(self, syncbuf):
2213 syncbuf.out.fail('error: %s/: %s',
2214 self.project.relpath,
2215 str(self.why))
2216 syncbuf.out.nl()
2217
2218class _Later(object):
2219 def __init__(self, project, action):
2220 self.project = project
2221 self.action = action
2222
2223 def Run(self, syncbuf):
2224 out = syncbuf.out
2225 out.project('project %s/', self.project.relpath)
2226 out.nl()
2227 try:
2228 self.action()
2229 out.nl()
2230 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002231 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002232 out.nl()
2233 return False
2234
2235class _SyncColoring(Coloring):
2236 def __init__(self, config):
2237 Coloring.__init__(self, config, 'reposync')
2238 self.project = self.printer('header', attr = 'bold')
2239 self.info = self.printer('info')
2240 self.fail = self.printer('fail', fg='red')
2241
2242class SyncBuffer(object):
2243 def __init__(self, config, detach_head=False):
2244 self._messages = []
2245 self._failures = []
2246 self._later_queue1 = []
2247 self._later_queue2 = []
2248
2249 self.out = _SyncColoring(config)
2250 self.out.redirect(sys.stderr)
2251
2252 self.detach_head = detach_head
2253 self.clean = True
2254
2255 def info(self, project, fmt, *args):
2256 self._messages.append(_InfoMessage(project, fmt % args))
2257
2258 def fail(self, project, err=None):
2259 self._failures.append(_Failure(project, err))
2260 self.clean = False
2261
2262 def later1(self, project, what):
2263 self._later_queue1.append(_Later(project, what))
2264
2265 def later2(self, project, what):
2266 self._later_queue2.append(_Later(project, what))
2267
2268 def Finish(self):
2269 self._PrintMessages()
2270 self._RunLater()
2271 self._PrintMessages()
2272 return self.clean
2273
2274 def _RunLater(self):
2275 for q in ['_later_queue1', '_later_queue2']:
2276 if not self._RunQueue(q):
2277 return
2278
2279 def _RunQueue(self, queue):
2280 for m in getattr(self, queue):
2281 if not m.Run(self):
2282 self.clean = False
2283 return False
2284 setattr(self, queue, [])
2285 return True
2286
2287 def _PrintMessages(self):
2288 for m in self._messages:
2289 m.Print(self)
2290 for m in self._failures:
2291 m.Print(self)
2292
2293 self._messages = []
2294 self._failures = []
2295
2296
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002297class MetaProject(Project):
2298 """A special project housed under .repo.
2299 """
2300 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002301 Project.__init__(self,
2302 manifest = manifest,
2303 name = name,
2304 gitdir = gitdir,
2305 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002306 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002307 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002308 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002309 revisionId = None,
2310 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002311
2312 def PreSync(self):
2313 if self.Exists:
2314 cb = self.CurrentBranch
2315 if cb:
2316 base = self.GetBranch(cb).merge
2317 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002318 self.revisionExpr = base
2319 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002320
Florian Vallee5d016502012-06-07 17:19:26 +02002321 def MetaBranchSwitch(self, target):
2322 """ Prepare MetaProject for manifest branch switch
2323 """
2324
2325 # detach and delete manifest branch, allowing a new
2326 # branch to take over
2327 syncbuf = SyncBuffer(self.config, detach_head = True)
2328 self.Sync_LocalHalf(syncbuf)
2329 syncbuf.Finish()
2330
2331 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002332 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002333 capture_stdout = True,
2334 capture_stderr = True).Wait() == 0
2335
2336
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002337 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002338 def LastFetch(self):
2339 try:
2340 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2341 return os.path.getmtime(fh)
2342 except OSError:
2343 return 0
2344
2345 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002346 def HasChanges(self):
2347 """Has the remote received new commits not yet checked out?
2348 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002349 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002350 return False
2351
David Pursehouse8a68ff92012-09-24 12:15:13 +09002352 all_refs = self.bare_ref.all
2353 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002354 head = self.work_git.GetHead()
2355 if head.startswith(R_HEADS):
2356 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002357 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002358 except KeyError:
2359 head = None
2360
2361 if revid == head:
2362 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002363 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002364 return True
2365 return False