blob: 2989d3809441a23f519e7254011be658bc65c843 [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)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001080 except GitError, e:
1081 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)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001102 except GitError, e:
1103 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()
1187 except GitError, e:
1188 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
1424 def get_submodules(gitdir, rev, path):
1425 # 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
1439 sub_gitdir = self.manifest.GetSubprojectPaths(self, sub_path)[2]
1440 submodules.append((sub_rev, sub_path, sub_url))
1441 return submodules
1442
1443 re_path = re.compile(r'submodule.(\w+).path')
1444 re_url = re.compile(r'submodule.(\w+).url')
1445 def parse_gitmodules(gitdir, rev):
1446 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1447 try:
1448 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1449 bare = True, gitdir = gitdir)
1450 except GitError as e:
1451 return [], []
1452 if p.Wait() != 0:
1453 return [], []
1454
1455 gitmodules_lines = []
1456 fd, temp_gitmodules_path = tempfile.mkstemp()
1457 try:
1458 os.write(fd, p.stdout)
1459 os.close(fd)
1460 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1461 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1462 bare = True, gitdir = gitdir)
1463 if p.Wait() != 0:
1464 return [], []
1465 gitmodules_lines = p.stdout.split('\n')
1466 except GitError as e:
1467 return [], []
1468 finally:
1469 os.remove(temp_gitmodules_path)
1470
1471 names = set()
1472 paths = {}
1473 urls = {}
1474 for line in gitmodules_lines:
1475 if not line:
1476 continue
1477 key, value = line.split('=')
1478 m = re_path.match(key)
1479 if m:
1480 names.add(m.group(1))
1481 paths[m.group(1)] = value
1482 continue
1483 m = re_url.match(key)
1484 if m:
1485 names.add(m.group(1))
1486 urls[m.group(1)] = value
1487 continue
1488 names = sorted(names)
1489 return [paths[name] for name in names], [urls[name] for name in names]
1490
1491 def git_ls_tree(gitdir, rev, paths):
1492 cmd = ['ls-tree', rev, '--']
1493 cmd.extend(paths)
1494 try:
1495 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1496 bare = True, gitdir = gitdir)
1497 except GitError:
1498 return []
1499 if p.Wait() != 0:
1500 return []
1501 objects = {}
1502 for line in p.stdout.split('\n'):
1503 if not line.strip():
1504 continue
1505 object_rev, object_path = line.split()[2:4]
1506 objects[object_path] = object_rev
1507 return objects
1508
1509 try:
1510 rev = self.GetRevisionId()
1511 except GitError:
1512 return []
1513 return get_submodules(self.gitdir, rev, '')
1514
1515 def GetDerivedSubprojects(self):
1516 result = []
1517 if not self.Exists:
1518 # If git repo does not exist yet, querying its submodules will
1519 # mess up its states; so return here.
1520 return result
1521 for rev, path, url in self._GetSubmodules():
1522 name = self.manifest.GetSubprojectName(self, path)
1523 project = self.manifest.projects.get(name)
1524 if project and project.Registered:
1525 # If it has been registered, skip it because we are searching
1526 # derived subprojects, but search for its derived subprojects.
1527 result.extend(project.GetDerivedSubprojects())
1528 continue
1529 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1530 remote = RemoteSpec(self.remote.name,
1531 url = url,
1532 review = self.remote.review)
1533 subproject = Project(manifest = self.manifest,
1534 name = name,
1535 remote = remote,
1536 gitdir = gitdir,
1537 worktree = worktree,
1538 relpath = relpath,
1539 revisionExpr = self.revisionExpr,
1540 revisionId = rev,
1541 rebase = self.rebase,
1542 groups = self.groups,
1543 sync_c = self.sync_c,
1544 parent = self,
1545 is_derived = True)
1546 result.append(subproject)
1547 result.extend(subproject.GetDerivedSubprojects())
1548 return result
1549
1550
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001551## Direct Git Commands ##
1552
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001553 def _RemoteFetch(self, name=None,
1554 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001555 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001556 quiet=False,
1557 alt_dir=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001558
1559 is_sha1 = False
1560 tag_name = None
1561
Brian Harring14a66742012-09-28 20:21:57 -07001562 def CheckForSha1():
1563 try:
1564 # if revision (sha or tag) is not present then following function
1565 # throws an error.
1566 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1567 return True
1568 except GitError:
1569 # There is no such persistent revision. We have to fetch it.
1570 return False
1571
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001572 if current_branch_only:
1573 if ID_RE.match(self.revisionExpr) is not None:
1574 is_sha1 = True
1575 elif self.revisionExpr.startswith(R_TAGS):
1576 # this is a tag and its sha1 value should never change
1577 tag_name = self.revisionExpr[len(R_TAGS):]
1578
1579 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001580 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001581 return True
Brian Harring14a66742012-09-28 20:21:57 -07001582 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1583 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001584
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001585 if not name:
1586 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001587
1588 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001589 remote = self.GetRemote(name)
1590 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001591 ssh_proxy = True
1592
Shawn O. Pearce88443382010-10-08 10:02:09 +02001593 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001594 if alt_dir and 'objects' == os.path.basename(alt_dir):
1595 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001596 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1597 remote = self.GetRemote(name)
1598
David Pursehouse8a68ff92012-09-24 12:15:13 +09001599 all_refs = self.bare_ref.all
1600 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001601 tmp = set()
1602
David Pursehouse8a68ff92012-09-24 12:15:13 +09001603 for r, ref_id in GitRefs(ref_dir).all.iteritems():
1604 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001605 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001606 all_refs[r] = ref_id
1607 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001608 continue
1609
David Pursehouse8a68ff92012-09-24 12:15:13 +09001610 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001611 continue
1612
David Pursehouse8a68ff92012-09-24 12:15:13 +09001613 r = 'refs/_alt/%s' % ref_id
1614 all_refs[r] = ref_id
1615 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001616 tmp.add(r)
1617
David Pursehouse8a68ff92012-09-24 12:15:13 +09001618 ref_names = list(all_refs.keys())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001619 ref_names.sort()
1620
1621 tmp_packed = ''
1622 old_packed = ''
1623
1624 for r in ref_names:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001625 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001626 tmp_packed += line
1627 if r not in tmp:
1628 old_packed += line
1629
1630 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001631 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001632 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001633
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001634 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001635
1636 # The --depth option only affects the initial fetch; after that we'll do
1637 # full fetches of changes.
1638 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1639 if depth and initial:
1640 cmd.append('--depth=%s' % depth)
1641
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001642 if quiet:
1643 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001644 if not self.worktree:
1645 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001646 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001647
Brian Harring14a66742012-09-28 20:21:57 -07001648 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001649 # Fetch whole repo
1650 cmd.append('--tags')
1651 cmd.append((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))
1652 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001653 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001654 cmd.append(tag_name)
1655 else:
1656 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001657 if is_sha1:
1658 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001659 if branch.startswith(R_HEADS):
1660 branch = branch[len(R_HEADS):]
1661 cmd.append((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001662
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001663 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001664 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001665 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1666 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001667 ok = True
1668 break
Brian Harring14a66742012-09-28 20:21:57 -07001669 elif current_branch_only and is_sha1 and ret == 128:
1670 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1671 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1672 # abort the optimization attempt and do a full sync.
1673 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001674 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001675
1676 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001677 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001678 if old_packed != '':
1679 _lwrite(packed_refs, old_packed)
1680 else:
1681 os.remove(packed_refs)
1682 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001683
1684 if is_sha1 and current_branch_only and self.upstream:
1685 # We just synced the upstream given branch; verify we
1686 # got what we wanted, else trigger a second run of all
1687 # refs.
1688 if not CheckForSha1():
1689 return self._RemoteFetch(name=name, current_branch_only=False,
1690 initial=False, quiet=quiet, alt_dir=alt_dir)
1691
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001692 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001693
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001694 def _ApplyCloneBundle(self, initial=False, quiet=False):
1695 if initial and self.manifest.manifestProject.config.GetString('repo.depth'):
1696 return False
1697
1698 remote = self.GetRemote(self.remote.name)
1699 bundle_url = remote.url + '/clone.bundle'
1700 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001701 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1702 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001703 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1704 return False
1705
1706 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1707 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1708
1709 exist_dst = os.path.exists(bundle_dst)
1710 exist_tmp = os.path.exists(bundle_tmp)
1711
1712 if not initial and not exist_dst and not exist_tmp:
1713 return False
1714
1715 if not exist_dst:
1716 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1717 if not exist_dst:
1718 return False
1719
1720 cmd = ['fetch']
1721 if quiet:
1722 cmd.append('--quiet')
1723 if not self.worktree:
1724 cmd.append('--update-head-ok')
1725 cmd.append(bundle_dst)
1726 for f in remote.fetch:
1727 cmd.append(str(f))
1728 cmd.append('refs/tags/*:refs/tags/*')
1729
1730 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001731 if os.path.exists(bundle_dst):
1732 os.remove(bundle_dst)
1733 if os.path.exists(bundle_tmp):
1734 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001735 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001736
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001737 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001738 if os.path.exists(dstPath):
1739 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001740
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001741 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001742 if quiet:
1743 cmd += ['--silent']
1744 if os.path.exists(tmpPath):
1745 size = os.stat(tmpPath).st_size
1746 if size >= 1024:
1747 cmd += ['--continue-at', '%d' % (size,)]
1748 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001749 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001750 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1751 cmd += ['--proxy', os.environ['http_proxy']]
1752 cmd += [srcUrl]
1753
1754 if IsTrace():
1755 Trace('%s', ' '.join(cmd))
1756 try:
1757 proc = subprocess.Popen(cmd)
1758 except OSError:
1759 return False
1760
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001761 curlret = proc.wait()
1762
1763 if curlret == 22:
1764 # From curl man page:
1765 # 22: HTTP page not retrieved. The requested url was not found or
1766 # returned another error with the HTTP error code being 400 or above.
1767 # This return code only appears if -f, --fail is used.
1768 if not quiet:
1769 print >> sys.stderr, "Server does not provide clone.bundle; ignoring."
1770 return False
1771
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001772 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001773 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001774 os.rename(tmpPath, dstPath)
1775 return True
1776 else:
1777 os.remove(tmpPath)
1778 return False
1779 else:
1780 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001781
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001782 def _Checkout(self, rev, quiet=False):
1783 cmd = ['checkout']
1784 if quiet:
1785 cmd.append('-q')
1786 cmd.append(rev)
1787 cmd.append('--')
1788 if GitCommand(self, cmd).Wait() != 0:
1789 if self._allrefs:
1790 raise GitError('%s checkout %s ' % (self.name, rev))
1791
Pierre Tardye5a21222011-03-24 16:28:18 +01001792 def _CherryPick(self, rev, quiet=False):
1793 cmd = ['cherry-pick']
1794 cmd.append(rev)
1795 cmd.append('--')
1796 if GitCommand(self, cmd).Wait() != 0:
1797 if self._allrefs:
1798 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1799
Erwan Mahea94f1622011-08-19 13:56:09 +02001800 def _Revert(self, rev, quiet=False):
1801 cmd = ['revert']
1802 cmd.append('--no-edit')
1803 cmd.append(rev)
1804 cmd.append('--')
1805 if GitCommand(self, cmd).Wait() != 0:
1806 if self._allrefs:
1807 raise GitError('%s revert %s ' % (self.name, rev))
1808
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001809 def _ResetHard(self, rev, quiet=True):
1810 cmd = ['reset', '--hard']
1811 if quiet:
1812 cmd.append('-q')
1813 cmd.append(rev)
1814 if GitCommand(self, cmd).Wait() != 0:
1815 raise GitError('%s reset --hard %s ' % (self.name, rev))
1816
1817 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001818 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001819 if onto is not None:
1820 cmd.extend(['--onto', onto])
1821 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001822 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001823 raise GitError('%s rebase %s ' % (self.name, upstream))
1824
Pierre Tardy3d125942012-05-04 12:18:12 +02001825 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001826 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001827 if ffonly:
1828 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001829 if GitCommand(self, cmd).Wait() != 0:
1830 raise GitError('%s merge %s ' % (self.name, head))
1831
1832 def _InitGitDir(self):
1833 if not os.path.exists(self.gitdir):
1834 os.makedirs(self.gitdir)
1835 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001836
Shawn O. Pearce88443382010-10-08 10:02:09 +02001837 mp = self.manifest.manifestProject
1838 ref_dir = mp.config.GetString('repo.reference')
1839
1840 if ref_dir:
1841 mirror_git = os.path.join(ref_dir, self.name + '.git')
1842 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1843 self.relpath + '.git')
1844
1845 if os.path.exists(mirror_git):
1846 ref_dir = mirror_git
1847
1848 elif os.path.exists(repo_git):
1849 ref_dir = repo_git
1850
1851 else:
1852 ref_dir = None
1853
1854 if ref_dir:
1855 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1856 os.path.join(ref_dir, 'objects') + '\n')
1857
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001858 if self.manifest.IsMirror:
1859 self.config.SetString('core.bare', 'true')
1860 else:
1861 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001862
1863 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001864 try:
1865 to_rm = os.listdir(hooks)
1866 except OSError:
1867 to_rm = []
1868 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001869 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001870 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001871
1872 m = self.manifest.manifestProject.config
1873 for key in ['user.name', 'user.email']:
1874 if m.Has(key, include_defaults = False):
1875 self.config.SetString(key, m.GetString(key))
1876
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001877 def _InitHooks(self):
1878 hooks = self._gitdir_path('hooks')
1879 if not os.path.exists(hooks):
1880 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001881 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001882 name = os.path.basename(stock_hook)
1883
Victor Boivie65e0f352011-04-18 11:23:29 +02001884 if name in ('commit-msg',) and not self.remote.review \
1885 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001886 # Don't install a Gerrit Code Review hook if this
1887 # project does not appear to use it for reviews.
1888 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001889 # Since the manifest project is one of those, but also
1890 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001891 continue
1892
1893 dst = os.path.join(hooks, name)
1894 if os.path.islink(dst):
1895 continue
1896 if os.path.exists(dst):
1897 if filecmp.cmp(stock_hook, dst, shallow=False):
1898 os.remove(dst)
1899 else:
1900 _error("%s: Not replacing %s hook", self.relpath, name)
1901 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001902 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001903 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001904 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001905 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001906 raise GitError('filesystem must support symlinks')
1907 else:
1908 raise
1909
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001910 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001911 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001912 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001913 remote.url = self.remote.url
1914 remote.review = self.remote.review
1915 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001916
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001917 if self.worktree:
1918 remote.ResetFetch(mirror=False)
1919 else:
1920 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001921 remote.Save()
1922
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001923 def _InitMRef(self):
1924 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001925 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001926
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001927 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001928 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001929
1930 def _InitAnyMRef(self, ref):
1931 cur = self.bare_ref.symref(ref)
1932
1933 if self.revisionId:
1934 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1935 msg = 'manifest set to %s' % self.revisionId
1936 dst = self.revisionId + '^0'
1937 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1938 else:
1939 remote = self.GetRemote(self.remote.name)
1940 dst = remote.ToLocal(self.revisionExpr)
1941 if cur != dst:
1942 msg = 'manifest set to %s' % self.revisionExpr
1943 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001944
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001945 def _InitWorkTree(self):
1946 dotgit = os.path.join(self.worktree, '.git')
1947 if not os.path.exists(dotgit):
1948 os.makedirs(dotgit)
1949
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001950 for name in ['config',
1951 'description',
1952 'hooks',
1953 'info',
1954 'logs',
1955 'objects',
1956 'packed-refs',
1957 'refs',
1958 'rr-cache',
1959 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001960 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001961 src = os.path.join(self.gitdir, name)
1962 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001963 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001964 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001965 else:
1966 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001967 except OSError, e:
1968 if e.errno == errno.EPERM:
1969 raise GitError('filesystem must support symlinks')
1970 else:
1971 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001972
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001973 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001974
1975 cmd = ['read-tree', '--reset', '-u']
1976 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001977 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001978 if GitCommand(self, cmd).Wait() != 0:
1979 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01001980
1981 rr_cache = os.path.join(self.gitdir, 'rr-cache')
1982 if not os.path.exists(rr_cache):
1983 os.makedirs(rr_cache)
1984
Shawn O. Pearce93609662009-04-21 10:50:33 -07001985 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001986
1987 def _gitdir_path(self, path):
1988 return os.path.join(self.gitdir, path)
1989
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001990 def _revlist(self, *args, **kw):
1991 a = []
1992 a.extend(args)
1993 a.append('--')
1994 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001995
1996 @property
1997 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001998 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001999
2000 class _GitGetByExec(object):
2001 def __init__(self, project, bare):
2002 self._project = project
2003 self._bare = bare
2004
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002005 def LsOthers(self):
2006 p = GitCommand(self._project,
2007 ['ls-files',
2008 '-z',
2009 '--others',
2010 '--exclude-standard'],
2011 bare = False,
2012 capture_stdout = True,
2013 capture_stderr = True)
2014 if p.Wait() == 0:
2015 out = p.stdout
2016 if out:
2017 return out[:-1].split("\0")
2018 return []
2019
2020 def DiffZ(self, name, *args):
2021 cmd = [name]
2022 cmd.append('-z')
2023 cmd.extend(args)
2024 p = GitCommand(self._project,
2025 cmd,
2026 bare = False,
2027 capture_stdout = True,
2028 capture_stderr = True)
2029 try:
2030 out = p.process.stdout.read()
2031 r = {}
2032 if out:
2033 out = iter(out[:-1].split('\0'))
2034 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002035 try:
2036 info = out.next()
2037 path = out.next()
2038 except StopIteration:
2039 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002040
2041 class _Info(object):
2042 def __init__(self, path, omode, nmode, oid, nid, state):
2043 self.path = path
2044 self.src_path = None
2045 self.old_mode = omode
2046 self.new_mode = nmode
2047 self.old_id = oid
2048 self.new_id = nid
2049
2050 if len(state) == 1:
2051 self.status = state
2052 self.level = None
2053 else:
2054 self.status = state[:1]
2055 self.level = state[1:]
2056 while self.level.startswith('0'):
2057 self.level = self.level[1:]
2058
2059 info = info[1:].split(' ')
2060 info =_Info(path, *info)
2061 if info.status in ('R', 'C'):
2062 info.src_path = info.path
2063 info.path = out.next()
2064 r[info.path] = info
2065 return r
2066 finally:
2067 p.Wait()
2068
2069 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002070 if self._bare:
2071 path = os.path.join(self._project.gitdir, HEAD)
2072 else:
2073 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002074 fd = open(path, 'rb')
2075 try:
2076 line = fd.read()
2077 finally:
2078 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002079 if line.startswith('ref: '):
2080 return line[5:-1]
2081 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002082
2083 def SetHead(self, ref, message=None):
2084 cmdv = []
2085 if message is not None:
2086 cmdv.extend(['-m', message])
2087 cmdv.append(HEAD)
2088 cmdv.append(ref)
2089 self.symbolic_ref(*cmdv)
2090
2091 def DetachHead(self, new, message=None):
2092 cmdv = ['--no-deref']
2093 if message is not None:
2094 cmdv.extend(['-m', message])
2095 cmdv.append(HEAD)
2096 cmdv.append(new)
2097 self.update_ref(*cmdv)
2098
2099 def UpdateRef(self, name, new, old=None,
2100 message=None,
2101 detach=False):
2102 cmdv = []
2103 if message is not None:
2104 cmdv.extend(['-m', message])
2105 if detach:
2106 cmdv.append('--no-deref')
2107 cmdv.append(name)
2108 cmdv.append(new)
2109 if old is not None:
2110 cmdv.append(old)
2111 self.update_ref(*cmdv)
2112
2113 def DeleteRef(self, name, old=None):
2114 if not old:
2115 old = self.rev_parse(name)
2116 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002117 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002118
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002119 def rev_list(self, *args, **kw):
2120 if 'format' in kw:
2121 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2122 else:
2123 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002124 cmdv.extend(args)
2125 p = GitCommand(self._project,
2126 cmdv,
2127 bare = self._bare,
2128 capture_stdout = True,
2129 capture_stderr = True)
2130 r = []
2131 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002132 if line[-1] == '\n':
2133 line = line[:-1]
2134 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002135 if p.Wait() != 0:
2136 raise GitError('%s rev-list %s: %s' % (
2137 self._project.name,
2138 str(args),
2139 p.stderr))
2140 return r
2141
2142 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002143 """Allow arbitrary git commands using pythonic syntax.
2144
2145 This allows you to do things like:
2146 git_obj.rev_parse('HEAD')
2147
2148 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2149 run. We'll replace the '_' with a '-' and try to run a git command.
2150 Any other arguments will be passed to the git command.
2151
2152 Args:
2153 name: The name of the git command to call. Any '_' characters will
2154 be replaced with '-'.
2155
2156 Returns:
2157 A callable object that will try to call git with the named command.
2158 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002159 name = name.replace('_', '-')
2160 def runner(*args):
2161 cmdv = [name]
2162 cmdv.extend(args)
2163 p = GitCommand(self._project,
2164 cmdv,
2165 bare = self._bare,
2166 capture_stdout = True,
2167 capture_stderr = True)
2168 if p.Wait() != 0:
2169 raise GitError('%s %s: %s' % (
2170 self._project.name,
2171 name,
2172 p.stderr))
2173 r = p.stdout
2174 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2175 return r[:-1]
2176 return r
2177 return runner
2178
2179
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002180class _PriorSyncFailedError(Exception):
2181 def __str__(self):
2182 return 'prior sync failed; rebase still in progress'
2183
2184class _DirtyError(Exception):
2185 def __str__(self):
2186 return 'contains uncommitted changes'
2187
2188class _InfoMessage(object):
2189 def __init__(self, project, text):
2190 self.project = project
2191 self.text = text
2192
2193 def Print(self, syncbuf):
2194 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2195 syncbuf.out.nl()
2196
2197class _Failure(object):
2198 def __init__(self, project, why):
2199 self.project = project
2200 self.why = why
2201
2202 def Print(self, syncbuf):
2203 syncbuf.out.fail('error: %s/: %s',
2204 self.project.relpath,
2205 str(self.why))
2206 syncbuf.out.nl()
2207
2208class _Later(object):
2209 def __init__(self, project, action):
2210 self.project = project
2211 self.action = action
2212
2213 def Run(self, syncbuf):
2214 out = syncbuf.out
2215 out.project('project %s/', self.project.relpath)
2216 out.nl()
2217 try:
2218 self.action()
2219 out.nl()
2220 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002221 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002222 out.nl()
2223 return False
2224
2225class _SyncColoring(Coloring):
2226 def __init__(self, config):
2227 Coloring.__init__(self, config, 'reposync')
2228 self.project = self.printer('header', attr = 'bold')
2229 self.info = self.printer('info')
2230 self.fail = self.printer('fail', fg='red')
2231
2232class SyncBuffer(object):
2233 def __init__(self, config, detach_head=False):
2234 self._messages = []
2235 self._failures = []
2236 self._later_queue1 = []
2237 self._later_queue2 = []
2238
2239 self.out = _SyncColoring(config)
2240 self.out.redirect(sys.stderr)
2241
2242 self.detach_head = detach_head
2243 self.clean = True
2244
2245 def info(self, project, fmt, *args):
2246 self._messages.append(_InfoMessage(project, fmt % args))
2247
2248 def fail(self, project, err=None):
2249 self._failures.append(_Failure(project, err))
2250 self.clean = False
2251
2252 def later1(self, project, what):
2253 self._later_queue1.append(_Later(project, what))
2254
2255 def later2(self, project, what):
2256 self._later_queue2.append(_Later(project, what))
2257
2258 def Finish(self):
2259 self._PrintMessages()
2260 self._RunLater()
2261 self._PrintMessages()
2262 return self.clean
2263
2264 def _RunLater(self):
2265 for q in ['_later_queue1', '_later_queue2']:
2266 if not self._RunQueue(q):
2267 return
2268
2269 def _RunQueue(self, queue):
2270 for m in getattr(self, queue):
2271 if not m.Run(self):
2272 self.clean = False
2273 return False
2274 setattr(self, queue, [])
2275 return True
2276
2277 def _PrintMessages(self):
2278 for m in self._messages:
2279 m.Print(self)
2280 for m in self._failures:
2281 m.Print(self)
2282
2283 self._messages = []
2284 self._failures = []
2285
2286
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002287class MetaProject(Project):
2288 """A special project housed under .repo.
2289 """
2290 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002291 Project.__init__(self,
2292 manifest = manifest,
2293 name = name,
2294 gitdir = gitdir,
2295 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002296 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002297 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002298 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002299 revisionId = None,
2300 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002301
2302 def PreSync(self):
2303 if self.Exists:
2304 cb = self.CurrentBranch
2305 if cb:
2306 base = self.GetBranch(cb).merge
2307 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002308 self.revisionExpr = base
2309 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002310
Florian Vallee5d016502012-06-07 17:19:26 +02002311 def MetaBranchSwitch(self, target):
2312 """ Prepare MetaProject for manifest branch switch
2313 """
2314
2315 # detach and delete manifest branch, allowing a new
2316 # branch to take over
2317 syncbuf = SyncBuffer(self.config, detach_head = True)
2318 self.Sync_LocalHalf(syncbuf)
2319 syncbuf.Finish()
2320
2321 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002322 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002323 capture_stdout = True,
2324 capture_stderr = True).Wait() == 0
2325
2326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002327 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002328 def LastFetch(self):
2329 try:
2330 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2331 return os.path.getmtime(fh)
2332 except OSError:
2333 return 0
2334
2335 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002336 def HasChanges(self):
2337 """Has the remote received new commits not yet checked out?
2338 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002339 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002340 return False
2341
David Pursehouse8a68ff92012-09-24 12:15:13 +09002342 all_refs = self.bare_ref.all
2343 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002344 head = self.work_git.GetHead()
2345 if head.startswith(R_HEADS):
2346 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002347 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002348 except KeyError:
2349 head = None
2350
2351 if revid == head:
2352 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002353 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002354 return True
2355 return False