blob: f299297d7cb6698159d559231ab08e910266afb2 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Doug Anderson37282b42011-03-04 11:54:18 -080016import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080017import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import filecmp
19import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080026import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070027import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070028
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070029from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070030from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070031from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090032from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080033from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080034from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070035from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036
Shawn O. Pearced237b692009-04-17 18:49:50 -070037from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070038
David Pursehouse59bbb582013-05-17 10:49:33 +090039from pyversion import is_python3
40if not is_python3():
41 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053042 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090043 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053044
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070045def _lwrite(path, content):
46 lock = '%s.lock' % path
47
48 fd = open(lock, 'wb')
49 try:
50 fd.write(content)
51 finally:
52 fd.close()
53
54 try:
55 os.rename(lock, path)
56 except OSError:
57 os.remove(lock)
58 raise
59
Shawn O. Pearce48244782009-04-16 08:25:57 -070060def _error(fmt, *args):
61 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070062 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070063
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070064def not_rev(r):
65 return '^' + r
66
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080067def sq(r):
68 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080069
Doug Anderson8ced8642011-01-10 14:16:30 -080070_project_hook_list = None
71def _ProjectHooks():
72 """List the hooks present in the 'hooks' directory.
73
74 These hooks are project hooks and are copied to the '.git/hooks' directory
75 of all subprojects.
76
77 This function caches the list of hooks (based on the contents of the
78 'repo/hooks' directory) on the first call.
79
80 Returns:
81 A list of absolute paths to all of the files in the hooks directory.
82 """
83 global _project_hook_list
84 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080085 d = os.path.abspath(os.path.dirname(__file__))
86 d = os.path.join(d , 'hooks')
Chirayu Desai217ea7d2013-03-01 19:14:38 +053087 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
Doug Anderson8ced8642011-01-10 14:16:30 -080088 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080089
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080090
Shawn O. Pearce632768b2008-10-23 11:58:52 -070091class DownloadedChange(object):
92 _commit_cache = None
93
94 def __init__(self, project, base, change_id, ps_id, commit):
95 self.project = project
96 self.base = base
97 self.change_id = change_id
98 self.ps_id = ps_id
99 self.commit = commit
100
101 @property
102 def commits(self):
103 if self._commit_cache is None:
104 self._commit_cache = self.project.bare_git.rev_list(
105 '--abbrev=8',
106 '--abbrev-commit',
107 '--pretty=oneline',
108 '--reverse',
109 '--date-order',
110 not_rev(self.base),
111 self.commit,
112 '--')
113 return self._commit_cache
114
115
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700116class ReviewableBranch(object):
117 _commit_cache = None
118
119 def __init__(self, project, branch, base):
120 self.project = project
121 self.branch = branch
122 self.base = base
123
124 @property
125 def name(self):
126 return self.branch.name
127
128 @property
129 def commits(self):
130 if self._commit_cache is None:
131 self._commit_cache = self.project.bare_git.rev_list(
132 '--abbrev=8',
133 '--abbrev-commit',
134 '--pretty=oneline',
135 '--reverse',
136 '--date-order',
137 not_rev(self.base),
138 R_HEADS + self.name,
139 '--')
140 return self._commit_cache
141
142 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800143 def unabbrev_commits(self):
144 r = dict()
145 for commit in self.project.bare_git.rev_list(
146 not_rev(self.base),
147 R_HEADS + self.name,
148 '--'):
149 r[commit[0:8]] = commit
150 return r
151
152 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700153 def date(self):
154 return self.project.bare_git.log(
155 '--pretty=format:%cd',
156 '-n', '1',
157 R_HEADS + self.name,
158 '--')
159
Brian Harring435370c2012-07-28 15:37:04 -0700160 def UploadForReview(self, people, auto_topic=False, draft=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800161 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700162 people,
Brian Harring435370c2012-07-28 15:37:04 -0700163 auto_topic=auto_topic,
164 draft=draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700166 def GetPublishedRefs(self):
167 refs = {}
168 output = self.project.bare_git.ls_remote(
169 self.branch.remote.SshReviewUrl(self.project.UserEmail),
170 'refs/changes/*')
171 for line in output.split('\n'):
172 try:
173 (sha, ref) = line.split()
174 refs[sha] = ref
175 except ValueError:
176 pass
177
178 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700179
180class StatusColoring(Coloring):
181 def __init__(self, config):
182 Coloring.__init__(self, config, 'status')
183 self.project = self.printer('header', attr = 'bold')
184 self.branch = self.printer('header', attr = 'bold')
185 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700186 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700187
188 self.added = self.printer('added', fg = 'green')
189 self.changed = self.printer('changed', fg = 'red')
190 self.untracked = self.printer('untracked', fg = 'red')
191
192
193class DiffColoring(Coloring):
194 def __init__(self, config):
195 Coloring.__init__(self, config, 'diff')
196 self.project = self.printer('header', attr = 'bold')
197
James W. Mills24c13082012-04-12 15:04:13 -0500198class _Annotation:
199 def __init__(self, name, value, keep):
200 self.name = name
201 self.value = value
202 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203
204class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800205 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206 self.src = src
207 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800208 self.abs_src = abssrc
209 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210
211 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800212 src = self.abs_src
213 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214 # copy file if it does not exist or is out of date
215 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
216 try:
217 # remove existing file first, since it might be read-only
218 if os.path.exists(dest):
219 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400220 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200221 dest_dir = os.path.dirname(dest)
222 if not os.path.isdir(dest_dir):
223 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224 shutil.copy(src, dest)
225 # make the file read-only
226 mode = os.stat(dest)[stat.ST_MODE]
227 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
228 os.chmod(dest, mode)
229 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700230 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700232class RemoteSpec(object):
233 def __init__(self,
234 name,
235 url = None,
236 review = None):
237 self.name = name
238 self.url = url
239 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240
Doug Anderson37282b42011-03-04 11:54:18 -0800241class RepoHook(object):
242 """A RepoHook contains information about a script to run as a hook.
243
244 Hooks are used to run a python script before running an upload (for instance,
245 to run presubmit checks). Eventually, we may have hooks for other actions.
246
247 This shouldn't be confused with files in the 'repo/hooks' directory. Those
248 files are copied into each '.git/hooks' folder for each project. Repo-level
249 hooks are associated instead with repo actions.
250
251 Hooks are always python. When a hook is run, we will load the hook into the
252 interpreter and execute its main() function.
253 """
254 def __init__(self,
255 hook_type,
256 hooks_project,
257 topdir,
258 abort_if_user_denies=False):
259 """RepoHook constructor.
260
261 Params:
262 hook_type: A string representing the type of hook. This is also used
263 to figure out the name of the file containing the hook. For
264 example: 'pre-upload'.
265 hooks_project: The project containing the repo hooks. If you have a
266 manifest, this is manifest.repo_hooks_project. OK if this is None,
267 which will make the hook a no-op.
268 topdir: Repo's top directory (the one containing the .repo directory).
269 Scripts will run with CWD as this directory. If you have a manifest,
270 this is manifest.topdir
271 abort_if_user_denies: If True, we'll throw a HookError() if the user
272 doesn't allow us to run the hook.
273 """
274 self._hook_type = hook_type
275 self._hooks_project = hooks_project
276 self._topdir = topdir
277 self._abort_if_user_denies = abort_if_user_denies
278
279 # Store the full path to the script for convenience.
280 if self._hooks_project:
281 self._script_fullpath = os.path.join(self._hooks_project.worktree,
282 self._hook_type + '.py')
283 else:
284 self._script_fullpath = None
285
286 def _GetHash(self):
287 """Return a hash of the contents of the hooks directory.
288
289 We'll just use git to do this. This hash has the property that if anything
290 changes in the directory we will return a different has.
291
292 SECURITY CONSIDERATION:
293 This hash only represents the contents of files in the hook directory, not
294 any other files imported or called by hooks. Changes to imported files
295 can change the script behavior without affecting the hash.
296
297 Returns:
298 A string representing the hash. This will always be ASCII so that it can
299 be printed to the user easily.
300 """
301 assert self._hooks_project, "Must have hooks to calculate their hash."
302
303 # We will use the work_git object rather than just calling GetRevisionId().
304 # That gives us a hash of the latest checked in version of the files that
305 # the user will actually be executing. Specifically, GetRevisionId()
306 # doesn't appear to change even if a user checks out a different version
307 # of the hooks repo (via git checkout) nor if a user commits their own revs.
308 #
309 # NOTE: Local (non-committed) changes will not be factored into this hash.
310 # I think this is OK, since we're really only worried about warning the user
311 # about upstream changes.
312 return self._hooks_project.work_git.rev_parse('HEAD')
313
314 def _GetMustVerb(self):
315 """Return 'must' if the hook is required; 'should' if not."""
316 if self._abort_if_user_denies:
317 return 'must'
318 else:
319 return 'should'
320
321 def _CheckForHookApproval(self):
322 """Check to see whether this hook has been approved.
323
324 We'll look at the hash of all of the hooks. If this matches the hash that
325 the user last approved, we're done. If it doesn't, we'll ask the user
326 about approval.
327
328 Note that we ask permission for each individual hook even though we use
329 the hash of all hooks when detecting changes. We'd like the user to be
330 able to approve / deny each hook individually. We only use the hash of all
331 hooks because there is no other easy way to detect changes to local imports.
332
333 Returns:
334 True if this hook is approved to run; False otherwise.
335
336 Raises:
337 HookError: Raised if the user doesn't approve and abort_if_user_denies
338 was passed to the consturctor.
339 """
Doug Anderson37282b42011-03-04 11:54:18 -0800340 hooks_config = self._hooks_project.config
341 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
342
343 # Get the last hash that the user approved for this hook; may be None.
344 old_hash = hooks_config.GetString(git_approval_key)
345
346 # Get the current hash so we can tell if scripts changed since approval.
347 new_hash = self._GetHash()
348
349 if old_hash is not None:
350 # User previously approved hook and asked not to be prompted again.
351 if new_hash == old_hash:
352 # Approval matched. We're done.
353 return True
354 else:
355 # Give the user a reason why we're prompting, since they last told
356 # us to "never ask again".
357 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
358 self._hook_type)
359 else:
360 prompt = ''
361
362 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
363 if sys.stdout.isatty():
364 prompt += ('Repo %s run the script:\n'
365 ' %s\n'
366 '\n'
367 'Do you want to allow this script to run '
368 '(yes/yes-never-ask-again/NO)? ') % (
369 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530370 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900371 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800372
373 # User is doing a one-time approval.
374 if response in ('y', 'yes'):
375 return True
376 elif response == 'yes-never-ask-again':
377 hooks_config.SetString(git_approval_key, new_hash)
378 return True
379
380 # For anything else, we'll assume no approval.
381 if self._abort_if_user_denies:
382 raise HookError('You must allow the %s hook or use --no-verify.' %
383 self._hook_type)
384
385 return False
386
387 def _ExecuteHook(self, **kwargs):
388 """Actually execute the given hook.
389
390 This will run the hook's 'main' function in our python interpreter.
391
392 Args:
393 kwargs: Keyword arguments to pass to the hook. These are often specific
394 to the hook type. For instance, pre-upload hooks will contain
395 a project_list.
396 """
397 # Keep sys.path and CWD stashed away so that we can always restore them
398 # upon function exit.
399 orig_path = os.getcwd()
400 orig_syspath = sys.path
401
402 try:
403 # Always run hooks with CWD as topdir.
404 os.chdir(self._topdir)
405
406 # Put the hook dir as the first item of sys.path so hooks can do
407 # relative imports. We want to replace the repo dir as [0] so
408 # hooks can't import repo files.
409 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
410
411 # Exec, storing global context in the context dict. We catch exceptions
412 # and convert to a HookError w/ just the failing traceback.
413 context = {}
414 try:
415 execfile(self._script_fullpath, context)
416 except Exception:
417 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
418 traceback.format_exc(), self._hook_type))
419
420 # Running the script should have defined a main() function.
421 if 'main' not in context:
422 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
423
424
425 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
426 # We don't actually want hooks to define their main with this argument--
427 # it's there to remind them that their hook should always take **kwargs.
428 # For instance, a pre-upload hook should be defined like:
429 # def main(project_list, **kwargs):
430 #
431 # This allows us to later expand the API without breaking old hooks.
432 kwargs = kwargs.copy()
433 kwargs['hook_should_take_kwargs'] = True
434
435 # Call the main function in the hook. If the hook should cause the
436 # build to fail, it will raise an Exception. We'll catch that convert
437 # to a HookError w/ just the failing traceback.
438 try:
439 context['main'](**kwargs)
440 except Exception:
441 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
442 'above.' % (
443 traceback.format_exc(), self._hook_type))
444 finally:
445 # Restore sys.path and CWD.
446 sys.path = orig_syspath
447 os.chdir(orig_path)
448
449 def Run(self, user_allows_all_hooks, **kwargs):
450 """Run the hook.
451
452 If the hook doesn't exist (because there is no hooks project or because
453 this particular hook is not enabled), this is a no-op.
454
455 Args:
456 user_allows_all_hooks: If True, we will never prompt about running the
457 hook--we'll just assume it's OK to run it.
458 kwargs: Keyword arguments to pass to the hook. These are often specific
459 to the hook type. For instance, pre-upload hooks will contain
460 a project_list.
461
462 Raises:
463 HookError: If there was a problem finding the hook or the user declined
464 to run a required hook (from _CheckForHookApproval).
465 """
466 # No-op if there is no hooks project or if hook is disabled.
467 if ((not self._hooks_project) or
468 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
469 return
470
471 # Bail with a nice error if we can't find the hook.
472 if not os.path.isfile(self._script_fullpath):
473 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
474
475 # Make sure the user is OK with running the hook.
476 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
477 return
478
479 # Run the hook with the same version of python we're using.
480 self._ExecuteHook(**kwargs)
481
482
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700483class Project(object):
484 def __init__(self,
485 manifest,
486 name,
487 remote,
488 gitdir,
489 worktree,
490 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700491 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800492 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700493 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700494 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700495 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800496 sync_s = False,
David Pursehouseede7f122012-11-27 22:25:30 +0900497 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800498 upstream = None,
499 parent = None,
500 is_derived = False):
501 """Init a Project object.
502
503 Args:
504 manifest: The XmlManifest object.
505 name: The `name` attribute of manifest.xml's project element.
506 remote: RemoteSpec object specifying its remote's properties.
507 gitdir: Absolute path of git directory.
508 worktree: Absolute path of git working tree.
509 relpath: Relative path of git working tree to repo's top directory.
510 revisionExpr: The `revision` attribute of manifest.xml's project element.
511 revisionId: git commit id for checking out.
512 rebase: The `rebase` attribute of manifest.xml's project element.
513 groups: The `groups` attribute of manifest.xml's project element.
514 sync_c: The `sync-c` attribute of manifest.xml's project element.
515 sync_s: The `sync-s` attribute of manifest.xml's project element.
516 upstream: The `upstream` attribute of manifest.xml's project element.
517 parent: The parent Project object.
518 is_derived: False if the project was explicitly defined in the manifest;
519 True if the project is a discovered submodule.
520 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700521 self.manifest = manifest
522 self.name = name
523 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800524 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800525 if worktree:
526 self.worktree = worktree.replace('\\', '/')
527 else:
528 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700529 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700530 self.revisionExpr = revisionExpr
531
532 if revisionId is None \
533 and revisionExpr \
534 and IsId(revisionExpr):
535 self.revisionId = revisionExpr
536 else:
537 self.revisionId = revisionId
538
Mike Pontillod3153822012-02-28 11:53:24 -0800539 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700540 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700541 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800542 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900543 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700544 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800545 self.parent = parent
546 self.is_derived = is_derived
547 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800548
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700549 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700550 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500551 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552 self.config = GitConfig.ForRepository(
553 gitdir = self.gitdir,
554 defaults = self.manifest.globalConfig)
555
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800556 if self.worktree:
557 self.work_git = self._GitGetByExec(self, bare=False)
558 else:
559 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700560 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700561 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700562
Doug Anderson37282b42011-03-04 11:54:18 -0800563 # This will be filled in if a project is later identified to be the
564 # project containing repo hooks.
565 self.enabled_repo_hooks = []
566
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800568 def Derived(self):
569 return self.is_derived
570
571 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700572 def Exists(self):
573 return os.path.isdir(self.gitdir)
574
575 @property
576 def CurrentBranch(self):
577 """Obtain the name of the currently checked out branch.
578 The branch name omits the 'refs/heads/' prefix.
579 None is returned if the project is on a detached HEAD.
580 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700581 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700582 if b.startswith(R_HEADS):
583 return b[len(R_HEADS):]
584 return None
585
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700586 def IsRebaseInProgress(self):
587 w = self.worktree
588 g = os.path.join(w, '.git')
589 return os.path.exists(os.path.join(g, 'rebase-apply')) \
590 or os.path.exists(os.path.join(g, 'rebase-merge')) \
591 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200592
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700593 def IsDirty(self, consider_untracked=True):
594 """Is the working directory modified in some way?
595 """
596 self.work_git.update_index('-q',
597 '--unmerged',
598 '--ignore-missing',
599 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900600 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700601 return True
602 if self.work_git.DiffZ('diff-files'):
603 return True
604 if consider_untracked and self.work_git.LsOthers():
605 return True
606 return False
607
608 _userident_name = None
609 _userident_email = None
610
611 @property
612 def UserName(self):
613 """Obtain the user's personal name.
614 """
615 if self._userident_name is None:
616 self._LoadUserIdentity()
617 return self._userident_name
618
619 @property
620 def UserEmail(self):
621 """Obtain the user's email address. This is very likely
622 to be their Gerrit login.
623 """
624 if self._userident_email is None:
625 self._LoadUserIdentity()
626 return self._userident_email
627
628 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900629 u = self.bare_git.var('GIT_COMMITTER_IDENT')
630 m = re.compile("^(.*) <([^>]*)> ").match(u)
631 if m:
632 self._userident_name = m.group(1)
633 self._userident_email = m.group(2)
634 else:
635 self._userident_name = ''
636 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700637
638 def GetRemote(self, name):
639 """Get the configuration for a single remote.
640 """
641 return self.config.GetRemote(name)
642
643 def GetBranch(self, name):
644 """Get the configuration for a single branch.
645 """
646 return self.config.GetBranch(name)
647
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700648 def GetBranches(self):
649 """Get all existing local branches.
650 """
651 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900652 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700653 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700654
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530655 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700656 if name.startswith(R_HEADS):
657 name = name[len(R_HEADS):]
658 b = self.GetBranch(name)
659 b.current = name == current
660 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900661 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700662 heads[name] = b
663
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530664 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700665 if name.startswith(R_PUB):
666 name = name[len(R_PUB):]
667 b = heads.get(name)
668 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900669 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700670
671 return heads
672
Colin Cross5acde752012-03-28 20:15:45 -0700673 def MatchesGroups(self, manifest_groups):
674 """Returns true if the manifest groups specified at init should cause
675 this project to be synced.
676 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700677 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700678
679 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700680 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700681 manifest_groups: "-group1,group2"
682 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500683
684 The special manifest group "default" will match any project that
685 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700686 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500687 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700688 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500689 if not 'notdefault' in expanded_project_groups:
690 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700691
Conley Owens971de8e2012-04-16 10:36:08 -0700692 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700693 for group in expanded_manifest_groups:
694 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700695 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700696 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700697 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700698
Conley Owens971de8e2012-04-16 10:36:08 -0700699 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700700
701## Status Display ##
702
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500703 def HasChanges(self):
704 """Returns true if there are uncommitted changes.
705 """
706 self.work_git.update_index('-q',
707 '--unmerged',
708 '--ignore-missing',
709 '--refresh')
710 if self.IsRebaseInProgress():
711 return True
712
713 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
714 return True
715
716 if self.work_git.DiffZ('diff-files'):
717 return True
718
719 if self.work_git.LsOthers():
720 return True
721
722 return False
723
Terence Haddock4655e812011-03-31 12:33:34 +0200724 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700725 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200726
727 Args:
728 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700729 """
730 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200731 if output_redir == None:
732 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700733 print(file=output_redir)
734 print('project %s/' % self.relpath, file=output_redir)
735 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736 return
737
738 self.work_git.update_index('-q',
739 '--unmerged',
740 '--ignore-missing',
741 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700742 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
744 df = self.work_git.DiffZ('diff-files')
745 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100746 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700747 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748
749 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200750 if not output_redir == None:
751 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700752 out.project('project %-40s', self.relpath + '/')
753
754 branch = self.CurrentBranch
755 if branch is None:
756 out.nobranch('(*** NO BRANCH ***)')
757 else:
758 out.branch('branch %s', branch)
759 out.nl()
760
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700761 if rb:
762 out.important('prior sync failed; rebase still in progress')
763 out.nl()
764
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700765 paths = list()
766 paths.extend(di.keys())
767 paths.extend(df.keys())
768 paths.extend(do)
769
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530770 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900771 try:
772 i = di[p]
773 except KeyError:
774 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900776 try:
777 f = df[p]
778 except KeyError:
779 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200780
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900781 if i:
782 i_status = i.status.upper()
783 else:
784 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700785
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900786 if f:
787 f_status = f.status.lower()
788 else:
789 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700790
791 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800792 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700793 i.src_path, p, i.level)
794 else:
795 line = ' %s%s\t%s' % (i_status, f_status, p)
796
797 if i and not f:
798 out.added('%s', line)
799 elif (i and f) or (not i and f):
800 out.changed('%s', line)
801 elif not i and not f:
802 out.untracked('%s', line)
803 else:
804 out.write('%s', line)
805 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200806
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700807 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700808
pelyad67872d2012-03-28 14:49:58 +0300809 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810 """Prints the status of the repository to stdout.
811 """
812 out = DiffColoring(self.config)
813 cmd = ['diff']
814 if out.is_on:
815 cmd.append('--color')
816 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300817 if absolute_paths:
818 cmd.append('--src-prefix=a/%s/' % self.relpath)
819 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700820 cmd.append('--')
821 p = GitCommand(self,
822 cmd,
823 capture_stdout = True,
824 capture_stderr = True)
825 has_diff = False
826 for line in p.process.stdout:
827 if not has_diff:
828 out.nl()
829 out.project('project %s/' % self.relpath)
830 out.nl()
831 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700832 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700833 p.Wait()
834
835
836## Publish / Upload ##
837
David Pursehouse8a68ff92012-09-24 12:15:13 +0900838 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839 """Was the branch published (uploaded) for code review?
840 If so, returns the SHA-1 hash of the last published
841 state for the branch.
842 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700843 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900844 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700845 try:
846 return self.bare_git.rev_parse(key)
847 except GitError:
848 return None
849 else:
850 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900851 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700852 except KeyError:
853 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854
David Pursehouse8a68ff92012-09-24 12:15:13 +0900855 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700856 """Prunes any stale published refs.
857 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900858 if all_refs is None:
859 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700860 heads = set()
861 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530862 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700863 if name.startswith(R_HEADS):
864 heads.add(name)
865 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900866 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530868 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 n = name[len(R_PUB):]
870 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900871 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700873 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874 """List any branches which can be uploaded for review.
875 """
876 heads = {}
877 pubed = {}
878
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530879 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700880 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900881 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700882 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900883 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700884
885 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530886 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900887 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700889 if selected_branch and branch != selected_branch:
890 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700891
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800892 rb = self.GetUploadableBranch(branch)
893 if rb:
894 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700895 return ready
896
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800897 def GetUploadableBranch(self, branch_name):
898 """Get a single uploadable branch, or None.
899 """
900 branch = self.GetBranch(branch_name)
901 base = branch.LocalMerge
902 if branch.LocalMerge:
903 rb = ReviewableBranch(self, branch, base)
904 if rb.commits:
905 return rb
906 return None
907
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700908 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700909 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700910 auto_topic=False,
911 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700912 """Uploads the named branch for code review.
913 """
914 if branch is None:
915 branch = self.CurrentBranch
916 if branch is None:
917 raise GitError('not currently on a branch')
918
919 branch = self.GetBranch(branch)
920 if not branch.LocalMerge:
921 raise GitError('branch %s does not track a remote' % branch.name)
922 if not branch.remote.review:
923 raise GitError('remote %s has no review url' % branch.remote.name)
924
925 dest_branch = branch.merge
926 if not dest_branch.startswith(R_HEADS):
927 dest_branch = R_HEADS + dest_branch
928
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800929 if not branch.remote.projectname:
930 branch.remote.projectname = self.name
931 branch.remote.Save()
932
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800933 url = branch.remote.ReviewUrl(self.UserEmail)
934 if url is None:
935 raise UploadError('review not configured')
936 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800937
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800938 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800939 rp = ['gerrit receive-pack']
940 for e in people[0]:
941 rp.append('--reviewer=%s' % sq(e))
942 for e in people[1]:
943 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800944 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700945
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800946 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800947
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800948 if dest_branch.startswith(R_HEADS):
949 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700950
951 upload_type = 'for'
952 if draft:
953 upload_type = 'drafts'
954
955 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
956 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800957 if auto_topic:
958 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800959 if not url.startswith('ssh://'):
960 rp = ['r=%s' % p for p in people[0]] + \
961 ['cc=%s' % p for p in people[1]]
962 if rp:
963 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800964 cmd.append(ref_spec)
965
966 if GitCommand(self, cmd, bare = True).Wait() != 0:
967 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968
969 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
970 self.bare_git.UpdateRef(R_PUB + branch.name,
971 R_HEADS + branch.name,
972 message = msg)
973
974
975## Sync ##
976
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700977 def Sync_NetworkHalf(self,
978 quiet=False,
979 is_new=None,
980 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700981 clone_bundle=True,
982 no_tags=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 """Perform only the network IO portion of the sync process.
984 Local working directory/branch state is not affected.
985 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700986 if is_new is None:
987 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200988 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +0200990 else:
991 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700992 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700993
994 if is_new:
995 alt = os.path.join(self.gitdir, 'objects/info/alternates')
996 try:
997 fd = open(alt, 'rb')
998 try:
999 alt_dir = fd.readline().rstrip()
1000 finally:
1001 fd.close()
1002 except IOError:
1003 alt_dir = None
1004 else:
1005 alt_dir = None
1006
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001007 if clone_bundle \
1008 and alt_dir is None \
1009 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001010 is_new = False
1011
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001012 if not current_branch_only:
1013 if self.sync_c:
1014 current_branch_only = True
1015 elif not self.manifest._loaded:
1016 # Manifest cannot check defaults until it syncs.
1017 current_branch_only = False
1018 elif self.manifest.default.sync_c:
1019 current_branch_only = True
1020
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001021 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001022 current_branch_only=current_branch_only,
1023 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001024 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001025
1026 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001027 self._InitMRef()
1028 else:
1029 self._InitMirrorHead()
1030 try:
1031 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1032 except OSError:
1033 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001034 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001035
1036 def PostRepoUpgrade(self):
1037 self._InitHooks()
1038
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001040 for copyfile in self.copyfiles:
1041 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042
David Pursehouse8a68ff92012-09-24 12:15:13 +09001043 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001044 if self.revisionId:
1045 return self.revisionId
1046
1047 rem = self.GetRemote(self.remote.name)
1048 rev = rem.ToLocal(self.revisionExpr)
1049
David Pursehouse8a68ff92012-09-24 12:15:13 +09001050 if all_refs is not None and rev in all_refs:
1051 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001052
1053 try:
1054 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1055 except GitError:
1056 raise ManifestInvalidRevisionError(
1057 'revision %s in %s not found' % (self.revisionExpr,
1058 self.name))
1059
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001060 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061 """Perform only the local IO portion of the sync process.
1062 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001064 all_refs = self.bare_ref.all
1065 self.CleanPublishedCache(all_refs)
1066 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001067
David Pursehouse1d947b32012-10-25 12:23:11 +09001068 def _doff():
1069 self._FastForward(revid)
1070 self._CopyFiles()
1071
Skyler Kaufman835cd682011-03-08 12:14:41 -08001072 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001073 head = self.work_git.GetHead()
1074 if head.startswith(R_HEADS):
1075 branch = head[len(R_HEADS):]
1076 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001077 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001078 except KeyError:
1079 head = None
1080 else:
1081 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001083 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001084 # Currently on a detached HEAD. The user is assumed to
1085 # not have any local modifications worth worrying about.
1086 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001087 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001088 syncbuf.fail(self, _PriorSyncFailedError())
1089 return
1090
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001091 if head == revid:
1092 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001093 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001094 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001095 if not syncbuf.detach_head:
1096 return
1097 else:
1098 lost = self._revlist(not_rev(revid), HEAD)
1099 if lost:
1100 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001101
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001103 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001104 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001105 syncbuf.fail(self, e)
1106 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001107 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001108 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001109
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001110 if head == revid:
1111 # No changes; don't do anything further.
1112 #
1113 return
1114
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001115 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001116
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001117 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001118 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001119 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001120 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001121 syncbuf.info(self,
1122 "leaving %s; does not track upstream",
1123 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001124 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001125 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001126 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001127 syncbuf.fail(self, e)
1128 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001129 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001130 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001131
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001132 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001133 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001134 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001135 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136 if not_merged:
1137 if upstream_gain:
1138 # The user has published this branch and some of those
1139 # commits are not yet merged upstream. We do not want
1140 # to rewrite the published commits so we punt.
1141 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001142 syncbuf.fail(self,
1143 "branch %s is published (but not merged) and is now %d commits behind"
1144 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001145 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001146 elif pub == head:
1147 # All published commits are merged, and thus we are a
1148 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001149 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001150 syncbuf.later1(self, _doff)
1151 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001152
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001153 # Examine the local commits not in the remote. Find the
1154 # last one attributed to this user, if any.
1155 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001156 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001157 last_mine = None
1158 cnt_mine = 0
1159 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001160 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001161 if committer_email == self.UserEmail:
1162 last_mine = commit_id
1163 cnt_mine += 1
1164
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001165 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001166 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167
1168 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001169 syncbuf.fail(self, _DirtyError())
1170 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001171
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001172 # If the upstream switched on us, warn the user.
1173 #
1174 if branch.merge != self.revisionExpr:
1175 if branch.merge and self.revisionExpr:
1176 syncbuf.info(self,
1177 'manifest switched %s...%s',
1178 branch.merge,
1179 self.revisionExpr)
1180 elif branch.merge:
1181 syncbuf.info(self,
1182 'manifest no longer tracks %s',
1183 branch.merge)
1184
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001185 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001186 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001187 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001188 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001189 syncbuf.info(self,
1190 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001191 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001193 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001194 if not ID_RE.match(self.revisionExpr):
1195 # in case of manifest sync the revisionExpr might be a SHA1
1196 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001197 branch.Save()
1198
Mike Pontillod3153822012-02-28 11:53:24 -08001199 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001200 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001201 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001202 self._CopyFiles()
1203 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001204 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001205 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001206 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001207 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001208 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001209 syncbuf.fail(self, e)
1210 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001211 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001212 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001214 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001215 # dest should already be an absolute path, but src is project relative
1216 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001217 abssrc = os.path.join(self.worktree, src)
1218 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001219
James W. Mills24c13082012-04-12 15:04:13 -05001220 def AddAnnotation(self, name, value, keep):
1221 self.annotations.append(_Annotation(name, value, keep))
1222
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001223 def DownloadPatchSet(self, change_id, patch_id):
1224 """Download a single patch set of a single change to FETCH_HEAD.
1225 """
1226 remote = self.GetRemote(self.remote.name)
1227
1228 cmd = ['fetch', remote.name]
1229 cmd.append('refs/changes/%2.2d/%d/%d' \
1230 % (change_id % 100, change_id, patch_id))
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301231 cmd.extend(list(map(str, remote.fetch)))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001232 if GitCommand(self, cmd, bare=True).Wait() != 0:
1233 return None
1234 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001235 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001236 change_id,
1237 patch_id,
1238 self.bare_git.rev_parse('FETCH_HEAD'))
1239
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001240
1241## Branch Management ##
1242
1243 def StartBranch(self, name):
1244 """Create a new branch off the manifest's revision.
1245 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001246 head = self.work_git.GetHead()
1247 if head == (R_HEADS + name):
1248 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001249
David Pursehouse8a68ff92012-09-24 12:15:13 +09001250 all_refs = self.bare_ref.all
1251 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001252 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001253 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001254 capture_stdout = True,
1255 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001256
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001257 branch = self.GetBranch(name)
1258 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001259 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001260 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001261
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001262 if head.startswith(R_HEADS):
1263 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001264 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001265 except KeyError:
1266 head = None
1267
1268 if revid and head and revid == head:
1269 ref = os.path.join(self.gitdir, R_HEADS + name)
1270 try:
1271 os.makedirs(os.path.dirname(ref))
1272 except OSError:
1273 pass
1274 _lwrite(ref, '%s\n' % revid)
1275 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1276 'ref: %s%s\n' % (R_HEADS, name))
1277 branch.Save()
1278 return True
1279
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001280 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001281 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001282 capture_stdout = True,
1283 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001284 branch.Save()
1285 return True
1286 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001287
Wink Saville02d79452009-04-10 13:01:24 -07001288 def CheckoutBranch(self, name):
1289 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001290
1291 Args:
1292 name: The name of the branch to checkout.
1293
1294 Returns:
1295 True if the checkout succeeded; False if it didn't; None if the branch
1296 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001297 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001298 rev = R_HEADS + name
1299 head = self.work_git.GetHead()
1300 if head == rev:
1301 # Already on the branch
1302 #
1303 return True
Wink Saville02d79452009-04-10 13:01:24 -07001304
David Pursehouse8a68ff92012-09-24 12:15:13 +09001305 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001306 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001307 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001308 except KeyError:
1309 # Branch does not exist in this project
1310 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001311 return None
Wink Saville02d79452009-04-10 13:01:24 -07001312
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001313 if head.startswith(R_HEADS):
1314 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001315 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001316 except KeyError:
1317 head = None
1318
1319 if head == revid:
1320 # Same revision; just update HEAD to point to the new
1321 # target branch, but otherwise take no other action.
1322 #
1323 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1324 'ref: %s%s\n' % (R_HEADS, name))
1325 return True
1326
1327 return GitCommand(self,
1328 ['checkout', name, '--'],
1329 capture_stdout = True,
1330 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001331
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001332 def AbandonBranch(self, name):
1333 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001334
1335 Args:
1336 name: The name of the branch to abandon.
1337
1338 Returns:
1339 True if the abandon succeeded; False if it didn't; None if the branch
1340 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001341 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001342 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001343 all_refs = self.bare_ref.all
1344 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001345 # Doesn't exist
1346 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001347
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001348 head = self.work_git.GetHead()
1349 if head == rev:
1350 # We can't destroy the branch while we are sitting
1351 # on it. Switch to a detached HEAD.
1352 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001353 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001354
David Pursehouse8a68ff92012-09-24 12:15:13 +09001355 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001356 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001357 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1358 '%s\n' % revid)
1359 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001360 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001361
1362 return GitCommand(self,
1363 ['branch', '-D', name],
1364 capture_stdout = True,
1365 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001366
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367 def PruneHeads(self):
1368 """Prune any topic branches already merged into upstream.
1369 """
1370 cb = self.CurrentBranch
1371 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001372 left = self._allrefs
1373 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001374 if name.startswith(R_HEADS):
1375 name = name[len(R_HEADS):]
1376 if cb is None or name != cb:
1377 kill.append(name)
1378
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001379 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001380 if cb is not None \
1381 and not self._revlist(HEAD + '...' + rev) \
1382 and not self.IsDirty(consider_untracked = False):
1383 self.work_git.DetachHead(HEAD)
1384 kill.append(cb)
1385
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001387 old = self.bare_git.GetHead()
1388 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1390
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391 try:
1392 self.bare_git.DetachHead(rev)
1393
1394 b = ['branch', '-d']
1395 b.extend(kill)
1396 b = GitCommand(self, b, bare=True,
1397 capture_stdout=True,
1398 capture_stderr=True)
1399 b.Wait()
1400 finally:
1401 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001402 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001404 for branch in kill:
1405 if (R_HEADS + branch) not in left:
1406 self.CleanPublishedCache()
1407 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001408
1409 if cb and cb not in kill:
1410 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001411 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001412
1413 kept = []
1414 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001415 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001416 branch = self.GetBranch(branch)
1417 base = branch.LocalMerge
1418 if not base:
1419 base = rev
1420 kept.append(ReviewableBranch(self, branch, base))
1421 return kept
1422
1423
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001424## Submodule Management ##
1425
1426 def GetRegisteredSubprojects(self):
1427 result = []
1428 def rec(subprojects):
1429 if not subprojects:
1430 return
1431 result.extend(subprojects)
1432 for p in subprojects:
1433 rec(p.subprojects)
1434 rec(self.subprojects)
1435 return result
1436
1437 def _GetSubmodules(self):
1438 # Unfortunately we cannot call `git submodule status --recursive` here
1439 # because the working tree might not exist yet, and it cannot be used
1440 # without a working tree in its current implementation.
1441
1442 def get_submodules(gitdir, rev):
1443 # Parse .gitmodules for submodule sub_paths and sub_urls
1444 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1445 if not sub_paths:
1446 return []
1447 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1448 # revision of submodule repository
1449 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1450 submodules = []
1451 for sub_path, sub_url in zip(sub_paths, sub_urls):
1452 try:
1453 sub_rev = sub_revs[sub_path]
1454 except KeyError:
1455 # Ignore non-exist submodules
1456 continue
1457 submodules.append((sub_rev, sub_path, sub_url))
1458 return submodules
1459
1460 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1461 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1462 def parse_gitmodules(gitdir, rev):
1463 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1464 try:
1465 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1466 bare = True, gitdir = gitdir)
1467 except GitError:
1468 return [], []
1469 if p.Wait() != 0:
1470 return [], []
1471
1472 gitmodules_lines = []
1473 fd, temp_gitmodules_path = tempfile.mkstemp()
1474 try:
1475 os.write(fd, p.stdout)
1476 os.close(fd)
1477 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1478 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1479 bare = True, gitdir = gitdir)
1480 if p.Wait() != 0:
1481 return [], []
1482 gitmodules_lines = p.stdout.split('\n')
1483 except GitError:
1484 return [], []
1485 finally:
1486 os.remove(temp_gitmodules_path)
1487
1488 names = set()
1489 paths = {}
1490 urls = {}
1491 for line in gitmodules_lines:
1492 if not line:
1493 continue
1494 m = re_path.match(line)
1495 if m:
1496 names.add(m.group(1))
1497 paths[m.group(1)] = m.group(2)
1498 continue
1499 m = re_url.match(line)
1500 if m:
1501 names.add(m.group(1))
1502 urls[m.group(1)] = m.group(2)
1503 continue
1504 names = sorted(names)
1505 return ([paths.get(name, '') for name in names],
1506 [urls.get(name, '') for name in names])
1507
1508 def git_ls_tree(gitdir, rev, paths):
1509 cmd = ['ls-tree', rev, '--']
1510 cmd.extend(paths)
1511 try:
1512 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1513 bare = True, gitdir = gitdir)
1514 except GitError:
1515 return []
1516 if p.Wait() != 0:
1517 return []
1518 objects = {}
1519 for line in p.stdout.split('\n'):
1520 if not line.strip():
1521 continue
1522 object_rev, object_path = line.split()[2:4]
1523 objects[object_path] = object_rev
1524 return objects
1525
1526 try:
1527 rev = self.GetRevisionId()
1528 except GitError:
1529 return []
1530 return get_submodules(self.gitdir, rev)
1531
1532 def GetDerivedSubprojects(self):
1533 result = []
1534 if not self.Exists:
1535 # If git repo does not exist yet, querying its submodules will
1536 # mess up its states; so return here.
1537 return result
1538 for rev, path, url in self._GetSubmodules():
1539 name = self.manifest.GetSubprojectName(self, path)
1540 project = self.manifest.projects.get(name)
1541 if project:
1542 result.extend(project.GetDerivedSubprojects())
1543 continue
1544 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1545 remote = RemoteSpec(self.remote.name,
1546 url = url,
1547 review = self.remote.review)
1548 subproject = Project(manifest = self.manifest,
1549 name = name,
1550 remote = remote,
1551 gitdir = gitdir,
1552 worktree = worktree,
1553 relpath = relpath,
1554 revisionExpr = self.revisionExpr,
1555 revisionId = rev,
1556 rebase = self.rebase,
1557 groups = self.groups,
1558 sync_c = self.sync_c,
1559 sync_s = self.sync_s,
1560 parent = self,
1561 is_derived = True)
1562 result.append(subproject)
1563 result.extend(subproject.GetDerivedSubprojects())
1564 return result
1565
1566
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001567## Direct Git Commands ##
1568
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001569 def _RemoteFetch(self, name=None,
1570 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001571 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001572 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001573 alt_dir=None,
1574 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001575
1576 is_sha1 = False
1577 tag_name = None
1578
Brian Harring14a66742012-09-28 20:21:57 -07001579 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001580 try:
1581 # if revision (sha or tag) is not present then following function
1582 # throws an error.
1583 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1584 return True
1585 except GitError:
1586 # There is no such persistent revision. We have to fetch it.
1587 return False
Brian Harring14a66742012-09-28 20:21:57 -07001588
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001589 if current_branch_only:
1590 if ID_RE.match(self.revisionExpr) is not None:
1591 is_sha1 = True
1592 elif self.revisionExpr.startswith(R_TAGS):
1593 # this is a tag and its sha1 value should never change
1594 tag_name = self.revisionExpr[len(R_TAGS):]
1595
1596 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001597 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001598 return True
Brian Harring14a66742012-09-28 20:21:57 -07001599 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1600 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001601
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001602 if not name:
1603 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001604
1605 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001606 remote = self.GetRemote(name)
1607 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001608 ssh_proxy = True
1609
Shawn O. Pearce88443382010-10-08 10:02:09 +02001610 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001611 if alt_dir and 'objects' == os.path.basename(alt_dir):
1612 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001613 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1614 remote = self.GetRemote(name)
1615
David Pursehouse8a68ff92012-09-24 12:15:13 +09001616 all_refs = self.bare_ref.all
1617 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001618 tmp = set()
1619
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301620 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001621 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001622 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001623 all_refs[r] = ref_id
1624 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001625 continue
1626
David Pursehouse8a68ff92012-09-24 12:15:13 +09001627 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001628 continue
1629
David Pursehouse8a68ff92012-09-24 12:15:13 +09001630 r = 'refs/_alt/%s' % ref_id
1631 all_refs[r] = ref_id
1632 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001633 tmp.add(r)
1634
Shawn O. Pearce88443382010-10-08 10:02:09 +02001635 tmp_packed = ''
1636 old_packed = ''
1637
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301638 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001639 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001640 tmp_packed += line
1641 if r not in tmp:
1642 old_packed += line
1643
1644 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001645 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001646 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001647
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001648 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001649
1650 # The --depth option only affects the initial fetch; after that we'll do
1651 # full fetches of changes.
David Pursehouseede7f122012-11-27 22:25:30 +09001652 if self.clone_depth:
1653 depth = self.clone_depth
1654 else:
1655 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Doug Anderson30d45292011-05-04 15:01:04 -07001656 if depth and initial:
1657 cmd.append('--depth=%s' % depth)
1658
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001659 if quiet:
1660 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001661 if not self.worktree:
1662 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001663 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001664
Brian Harring14a66742012-09-28 20:21:57 -07001665 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001666 # Fetch whole repo
Jimmie Wester2f992cb2012-12-07 12:49:51 +01001667 # If using depth then we should not get all the tags since they may
1668 # be outside of the depth.
1669 if no_tags or depth:
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001670 cmd.append('--no-tags')
1671 else:
1672 cmd.append('--tags')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301673 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001674 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001675 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001676 cmd.append(tag_name)
1677 else:
1678 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001679 if is_sha1:
1680 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001681 if branch.startswith(R_HEADS):
1682 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301683 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001684
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001685 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001686 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001687 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1688 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001689 ok = True
1690 break
Brian Harring14a66742012-09-28 20:21:57 -07001691 elif current_branch_only and is_sha1 and ret == 128:
1692 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1693 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1694 # abort the optimization attempt and do a full sync.
1695 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001696 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001697
1698 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001699 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001700 if old_packed != '':
1701 _lwrite(packed_refs, old_packed)
1702 else:
1703 os.remove(packed_refs)
1704 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001705
1706 if is_sha1 and current_branch_only and self.upstream:
1707 # We just synced the upstream given branch; verify we
1708 # got what we wanted, else trigger a second run of all
1709 # refs.
1710 if not CheckForSha1():
1711 return self._RemoteFetch(name=name, current_branch_only=False,
1712 initial=False, quiet=quiet, alt_dir=alt_dir)
1713
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001714 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001715
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001716 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001717 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001718 return False
1719
1720 remote = self.GetRemote(self.remote.name)
1721 bundle_url = remote.url + '/clone.bundle'
1722 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001723 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1724 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001725 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1726 return False
1727
1728 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1729 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1730
1731 exist_dst = os.path.exists(bundle_dst)
1732 exist_tmp = os.path.exists(bundle_tmp)
1733
1734 if not initial and not exist_dst and not exist_tmp:
1735 return False
1736
1737 if not exist_dst:
1738 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1739 if not exist_dst:
1740 return False
1741
1742 cmd = ['fetch']
1743 if quiet:
1744 cmd.append('--quiet')
1745 if not self.worktree:
1746 cmd.append('--update-head-ok')
1747 cmd.append(bundle_dst)
1748 for f in remote.fetch:
1749 cmd.append(str(f))
1750 cmd.append('refs/tags/*:refs/tags/*')
1751
1752 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001753 if os.path.exists(bundle_dst):
1754 os.remove(bundle_dst)
1755 if os.path.exists(bundle_tmp):
1756 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001757 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001759 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001760 if os.path.exists(dstPath):
1761 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001762
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001763 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001764 if quiet:
1765 cmd += ['--silent']
1766 if os.path.exists(tmpPath):
1767 size = os.stat(tmpPath).st_size
1768 if size >= 1024:
1769 cmd += ['--continue-at', '%d' % (size,)]
1770 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001771 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001772 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1773 cmd += ['--proxy', os.environ['http_proxy']]
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001774 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
1775 if cookiefile:
1776 cmd += ['--cookie', cookiefile]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001777 cmd += [srcUrl]
1778
1779 if IsTrace():
1780 Trace('%s', ' '.join(cmd))
1781 try:
1782 proc = subprocess.Popen(cmd)
1783 except OSError:
1784 return False
1785
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001786 curlret = proc.wait()
1787
1788 if curlret == 22:
1789 # From curl man page:
1790 # 22: HTTP page not retrieved. The requested url was not found or
1791 # returned another error with the HTTP error code being 400 or above.
1792 # This return code only appears if -f, --fail is used.
1793 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001794 print("Server does not provide clone.bundle; ignoring.",
1795 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001796 return False
1797
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001798 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001799 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001800 os.rename(tmpPath, dstPath)
1801 return True
1802 else:
1803 os.remove(tmpPath)
1804 return False
1805 else:
1806 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001807
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001808 def _Checkout(self, rev, quiet=False):
1809 cmd = ['checkout']
1810 if quiet:
1811 cmd.append('-q')
1812 cmd.append(rev)
1813 cmd.append('--')
1814 if GitCommand(self, cmd).Wait() != 0:
1815 if self._allrefs:
1816 raise GitError('%s checkout %s ' % (self.name, rev))
1817
Pierre Tardye5a21222011-03-24 16:28:18 +01001818 def _CherryPick(self, rev, quiet=False):
1819 cmd = ['cherry-pick']
1820 cmd.append(rev)
1821 cmd.append('--')
1822 if GitCommand(self, cmd).Wait() != 0:
1823 if self._allrefs:
1824 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1825
Erwan Mahea94f1622011-08-19 13:56:09 +02001826 def _Revert(self, rev, quiet=False):
1827 cmd = ['revert']
1828 cmd.append('--no-edit')
1829 cmd.append(rev)
1830 cmd.append('--')
1831 if GitCommand(self, cmd).Wait() != 0:
1832 if self._allrefs:
1833 raise GitError('%s revert %s ' % (self.name, rev))
1834
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001835 def _ResetHard(self, rev, quiet=True):
1836 cmd = ['reset', '--hard']
1837 if quiet:
1838 cmd.append('-q')
1839 cmd.append(rev)
1840 if GitCommand(self, cmd).Wait() != 0:
1841 raise GitError('%s reset --hard %s ' % (self.name, rev))
1842
1843 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001844 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001845 if onto is not None:
1846 cmd.extend(['--onto', onto])
1847 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001848 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001849 raise GitError('%s rebase %s ' % (self.name, upstream))
1850
Pierre Tardy3d125942012-05-04 12:18:12 +02001851 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001852 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001853 if ffonly:
1854 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001855 if GitCommand(self, cmd).Wait() != 0:
1856 raise GitError('%s merge %s ' % (self.name, head))
1857
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001858 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001859 if not os.path.exists(self.gitdir):
1860 os.makedirs(self.gitdir)
1861 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001862
Shawn O. Pearce88443382010-10-08 10:02:09 +02001863 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001864 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02001865
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001866 if ref_dir or mirror_git:
1867 if not mirror_git:
1868 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001869 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1870 self.relpath + '.git')
1871
1872 if os.path.exists(mirror_git):
1873 ref_dir = mirror_git
1874
1875 elif os.path.exists(repo_git):
1876 ref_dir = repo_git
1877
1878 else:
1879 ref_dir = None
1880
1881 if ref_dir:
1882 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1883 os.path.join(ref_dir, 'objects') + '\n')
1884
Jimmie Westera0444582012-10-24 13:44:42 +02001885 self._UpdateHooks()
1886
1887 m = self.manifest.manifestProject.config
1888 for key in ['user.name', 'user.email']:
1889 if m.Has(key, include_defaults = False):
1890 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001891 if self.manifest.IsMirror:
1892 self.config.SetString('core.bare', 'true')
1893 else:
1894 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001895
Jimmie Westera0444582012-10-24 13:44:42 +02001896 def _UpdateHooks(self):
1897 if os.path.exists(self.gitdir):
1898 # Always recreate hooks since they can have been changed
1899 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001900 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001901 try:
1902 to_rm = os.listdir(hooks)
1903 except OSError:
1904 to_rm = []
1905 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001906 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001907 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001908
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001909 def _InitHooks(self):
1910 hooks = self._gitdir_path('hooks')
1911 if not os.path.exists(hooks):
1912 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001913 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001914 name = os.path.basename(stock_hook)
1915
Victor Boivie65e0f352011-04-18 11:23:29 +02001916 if name in ('commit-msg',) and not self.remote.review \
1917 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001918 # Don't install a Gerrit Code Review hook if this
1919 # project does not appear to use it for reviews.
1920 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001921 # Since the manifest project is one of those, but also
1922 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001923 continue
1924
1925 dst = os.path.join(hooks, name)
1926 if os.path.islink(dst):
1927 continue
1928 if os.path.exists(dst):
1929 if filecmp.cmp(stock_hook, dst, shallow=False):
1930 os.remove(dst)
1931 else:
1932 _error("%s: Not replacing %s hook", self.relpath, name)
1933 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001934 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001935 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001936 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001937 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001938 raise GitError('filesystem must support symlinks')
1939 else:
1940 raise
1941
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001942 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001943 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001944 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001945 remote.url = self.remote.url
1946 remote.review = self.remote.review
1947 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001948
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001949 if self.worktree:
1950 remote.ResetFetch(mirror=False)
1951 else:
1952 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001953 remote.Save()
1954
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001955 def _InitMRef(self):
1956 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001957 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001958
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001959 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001960 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001961
1962 def _InitAnyMRef(self, ref):
1963 cur = self.bare_ref.symref(ref)
1964
1965 if self.revisionId:
1966 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1967 msg = 'manifest set to %s' % self.revisionId
1968 dst = self.revisionId + '^0'
1969 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1970 else:
1971 remote = self.GetRemote(self.remote.name)
1972 dst = remote.ToLocal(self.revisionExpr)
1973 if cur != dst:
1974 msg = 'manifest set to %s' % self.revisionExpr
1975 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001976
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001977 def _InitWorkTree(self):
1978 dotgit = os.path.join(self.worktree, '.git')
1979 if not os.path.exists(dotgit):
1980 os.makedirs(dotgit)
1981
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001982 for name in ['config',
1983 'description',
1984 'hooks',
1985 'info',
1986 'logs',
1987 'objects',
1988 'packed-refs',
1989 'refs',
1990 'rr-cache',
1991 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001992 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001993 src = os.path.join(self.gitdir, name)
1994 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001995 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001996 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001997 else:
1998 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001999 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08002000 if e.errno == errno.EPERM:
2001 raise GitError('filesystem must support symlinks')
2002 else:
2003 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002004
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002005 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002006
2007 cmd = ['read-tree', '--reset', '-u']
2008 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002009 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002010 if GitCommand(self, cmd).Wait() != 0:
2011 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002012
2013 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2014 if not os.path.exists(rr_cache):
2015 os.makedirs(rr_cache)
2016
Shawn O. Pearce93609662009-04-21 10:50:33 -07002017 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002018
2019 def _gitdir_path(self, path):
2020 return os.path.join(self.gitdir, path)
2021
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002022 def _revlist(self, *args, **kw):
2023 a = []
2024 a.extend(args)
2025 a.append('--')
2026 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002027
2028 @property
2029 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002030 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002031
2032 class _GitGetByExec(object):
2033 def __init__(self, project, bare):
2034 self._project = project
2035 self._bare = bare
2036
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002037 def LsOthers(self):
2038 p = GitCommand(self._project,
2039 ['ls-files',
2040 '-z',
2041 '--others',
2042 '--exclude-standard'],
2043 bare = False,
2044 capture_stdout = True,
2045 capture_stderr = True)
2046 if p.Wait() == 0:
2047 out = p.stdout
2048 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002049 return out[:-1].split('\0') # pylint: disable=W1401
2050 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002051 return []
2052
2053 def DiffZ(self, name, *args):
2054 cmd = [name]
2055 cmd.append('-z')
2056 cmd.extend(args)
2057 p = GitCommand(self._project,
2058 cmd,
2059 bare = False,
2060 capture_stdout = True,
2061 capture_stderr = True)
2062 try:
2063 out = p.process.stdout.read()
2064 r = {}
2065 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002066 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002067 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002068 try:
2069 info = out.next()
2070 path = out.next()
2071 except StopIteration:
2072 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002073
2074 class _Info(object):
2075 def __init__(self, path, omode, nmode, oid, nid, state):
2076 self.path = path
2077 self.src_path = None
2078 self.old_mode = omode
2079 self.new_mode = nmode
2080 self.old_id = oid
2081 self.new_id = nid
2082
2083 if len(state) == 1:
2084 self.status = state
2085 self.level = None
2086 else:
2087 self.status = state[:1]
2088 self.level = state[1:]
2089 while self.level.startswith('0'):
2090 self.level = self.level[1:]
2091
2092 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002093 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002094 if info.status in ('R', 'C'):
2095 info.src_path = info.path
2096 info.path = out.next()
2097 r[info.path] = info
2098 return r
2099 finally:
2100 p.Wait()
2101
2102 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002103 if self._bare:
2104 path = os.path.join(self._project.gitdir, HEAD)
2105 else:
2106 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002107 try:
2108 fd = open(path, 'rb')
2109 except IOError:
2110 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002111 try:
2112 line = fd.read()
2113 finally:
2114 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302115 try:
2116 line = line.decode()
2117 except AttributeError:
2118 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002119 if line.startswith('ref: '):
2120 return line[5:-1]
2121 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002122
2123 def SetHead(self, ref, message=None):
2124 cmdv = []
2125 if message is not None:
2126 cmdv.extend(['-m', message])
2127 cmdv.append(HEAD)
2128 cmdv.append(ref)
2129 self.symbolic_ref(*cmdv)
2130
2131 def DetachHead(self, new, message=None):
2132 cmdv = ['--no-deref']
2133 if message is not None:
2134 cmdv.extend(['-m', message])
2135 cmdv.append(HEAD)
2136 cmdv.append(new)
2137 self.update_ref(*cmdv)
2138
2139 def UpdateRef(self, name, new, old=None,
2140 message=None,
2141 detach=False):
2142 cmdv = []
2143 if message is not None:
2144 cmdv.extend(['-m', message])
2145 if detach:
2146 cmdv.append('--no-deref')
2147 cmdv.append(name)
2148 cmdv.append(new)
2149 if old is not None:
2150 cmdv.append(old)
2151 self.update_ref(*cmdv)
2152
2153 def DeleteRef(self, name, old=None):
2154 if not old:
2155 old = self.rev_parse(name)
2156 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002157 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002158
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002159 def rev_list(self, *args, **kw):
2160 if 'format' in kw:
2161 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2162 else:
2163 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002164 cmdv.extend(args)
2165 p = GitCommand(self._project,
2166 cmdv,
2167 bare = self._bare,
2168 capture_stdout = True,
2169 capture_stderr = True)
2170 r = []
2171 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002172 if line[-1] == '\n':
2173 line = line[:-1]
2174 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002175 if p.Wait() != 0:
2176 raise GitError('%s rev-list %s: %s' % (
2177 self._project.name,
2178 str(args),
2179 p.stderr))
2180 return r
2181
2182 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002183 """Allow arbitrary git commands using pythonic syntax.
2184
2185 This allows you to do things like:
2186 git_obj.rev_parse('HEAD')
2187
2188 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2189 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002190 Any other positional arguments will be passed to the git command, and the
2191 following keyword arguments are supported:
2192 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002193
2194 Args:
2195 name: The name of the git command to call. Any '_' characters will
2196 be replaced with '-'.
2197
2198 Returns:
2199 A callable object that will try to call git with the named command.
2200 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002201 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002202 def runner(*args, **kwargs):
2203 cmdv = []
2204 config = kwargs.pop('config', None)
2205 for k in kwargs:
2206 raise TypeError('%s() got an unexpected keyword argument %r'
2207 % (name, k))
2208 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002209 if not git_require((1, 7, 2)):
2210 raise ValueError('cannot set config on command line for %s()'
2211 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302212 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002213 cmdv.append('-c')
2214 cmdv.append('%s=%s' % (k, v))
2215 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002216 cmdv.extend(args)
2217 p = GitCommand(self._project,
2218 cmdv,
2219 bare = self._bare,
2220 capture_stdout = True,
2221 capture_stderr = True)
2222 if p.Wait() != 0:
2223 raise GitError('%s %s: %s' % (
2224 self._project.name,
2225 name,
2226 p.stderr))
2227 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302228 try:
2229 r = r.decode()
2230 except AttributeError:
2231 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002232 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2233 return r[:-1]
2234 return r
2235 return runner
2236
2237
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002238class _PriorSyncFailedError(Exception):
2239 def __str__(self):
2240 return 'prior sync failed; rebase still in progress'
2241
2242class _DirtyError(Exception):
2243 def __str__(self):
2244 return 'contains uncommitted changes'
2245
2246class _InfoMessage(object):
2247 def __init__(self, project, text):
2248 self.project = project
2249 self.text = text
2250
2251 def Print(self, syncbuf):
2252 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2253 syncbuf.out.nl()
2254
2255class _Failure(object):
2256 def __init__(self, project, why):
2257 self.project = project
2258 self.why = why
2259
2260 def Print(self, syncbuf):
2261 syncbuf.out.fail('error: %s/: %s',
2262 self.project.relpath,
2263 str(self.why))
2264 syncbuf.out.nl()
2265
2266class _Later(object):
2267 def __init__(self, project, action):
2268 self.project = project
2269 self.action = action
2270
2271 def Run(self, syncbuf):
2272 out = syncbuf.out
2273 out.project('project %s/', self.project.relpath)
2274 out.nl()
2275 try:
2276 self.action()
2277 out.nl()
2278 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002279 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002280 out.nl()
2281 return False
2282
2283class _SyncColoring(Coloring):
2284 def __init__(self, config):
2285 Coloring.__init__(self, config, 'reposync')
2286 self.project = self.printer('header', attr = 'bold')
2287 self.info = self.printer('info')
2288 self.fail = self.printer('fail', fg='red')
2289
2290class SyncBuffer(object):
2291 def __init__(self, config, detach_head=False):
2292 self._messages = []
2293 self._failures = []
2294 self._later_queue1 = []
2295 self._later_queue2 = []
2296
2297 self.out = _SyncColoring(config)
2298 self.out.redirect(sys.stderr)
2299
2300 self.detach_head = detach_head
2301 self.clean = True
2302
2303 def info(self, project, fmt, *args):
2304 self._messages.append(_InfoMessage(project, fmt % args))
2305
2306 def fail(self, project, err=None):
2307 self._failures.append(_Failure(project, err))
2308 self.clean = False
2309
2310 def later1(self, project, what):
2311 self._later_queue1.append(_Later(project, what))
2312
2313 def later2(self, project, what):
2314 self._later_queue2.append(_Later(project, what))
2315
2316 def Finish(self):
2317 self._PrintMessages()
2318 self._RunLater()
2319 self._PrintMessages()
2320 return self.clean
2321
2322 def _RunLater(self):
2323 for q in ['_later_queue1', '_later_queue2']:
2324 if not self._RunQueue(q):
2325 return
2326
2327 def _RunQueue(self, queue):
2328 for m in getattr(self, queue):
2329 if not m.Run(self):
2330 self.clean = False
2331 return False
2332 setattr(self, queue, [])
2333 return True
2334
2335 def _PrintMessages(self):
2336 for m in self._messages:
2337 m.Print(self)
2338 for m in self._failures:
2339 m.Print(self)
2340
2341 self._messages = []
2342 self._failures = []
2343
2344
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002345class MetaProject(Project):
2346 """A special project housed under .repo.
2347 """
2348 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002349 Project.__init__(self,
2350 manifest = manifest,
2351 name = name,
2352 gitdir = gitdir,
2353 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002354 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002355 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002356 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002357 revisionId = None,
2358 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002359
2360 def PreSync(self):
2361 if self.Exists:
2362 cb = self.CurrentBranch
2363 if cb:
2364 base = self.GetBranch(cb).merge
2365 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002366 self.revisionExpr = base
2367 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002368
Florian Vallee5d016502012-06-07 17:19:26 +02002369 def MetaBranchSwitch(self, target):
2370 """ Prepare MetaProject for manifest branch switch
2371 """
2372
2373 # detach and delete manifest branch, allowing a new
2374 # branch to take over
2375 syncbuf = SyncBuffer(self.config, detach_head = True)
2376 self.Sync_LocalHalf(syncbuf)
2377 syncbuf.Finish()
2378
2379 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002380 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002381 capture_stdout = True,
2382 capture_stderr = True).Wait() == 0
2383
2384
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002385 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002386 def LastFetch(self):
2387 try:
2388 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2389 return os.path.getmtime(fh)
2390 except OSError:
2391 return 0
2392
2393 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002394 def HasChanges(self):
2395 """Has the remote received new commits not yet checked out?
2396 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002397 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002398 return False
2399
David Pursehouse8a68ff92012-09-24 12:15:13 +09002400 all_refs = self.bare_ref.all
2401 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002402 head = self.work_git.GetHead()
2403 if head.startswith(R_HEADS):
2404 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002405 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002406 except KeyError:
2407 head = None
2408
2409 if revid == head:
2410 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002411 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002412 return True
2413 return False