blob: 3ff306f95beded13e97800f79a435e067a7ac95a [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
Chirayu Desai217ea7d2013-03-01 19:14:38 +053039try:
40 input = raw_input
41except NameError:
42 pass
43
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070044def _lwrite(path, content):
45 lock = '%s.lock' % path
46
47 fd = open(lock, 'wb')
48 try:
49 fd.write(content)
50 finally:
51 fd.close()
52
53 try:
54 os.rename(lock, path)
55 except OSError:
56 os.remove(lock)
57 raise
58
Shawn O. Pearce48244782009-04-16 08:25:57 -070059def _error(fmt, *args):
60 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070061 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070062
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070063def not_rev(r):
64 return '^' + r
65
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080066def sq(r):
67 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080068
Doug Anderson8ced8642011-01-10 14:16:30 -080069_project_hook_list = None
70def _ProjectHooks():
71 """List the hooks present in the 'hooks' directory.
72
73 These hooks are project hooks and are copied to the '.git/hooks' directory
74 of all subprojects.
75
76 This function caches the list of hooks (based on the contents of the
77 'repo/hooks' directory) on the first call.
78
79 Returns:
80 A list of absolute paths to all of the files in the hooks directory.
81 """
82 global _project_hook_list
83 if _project_hook_list is None:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080084 d = os.path.abspath(os.path.dirname(__file__))
85 d = os.path.join(d , 'hooks')
Chirayu Desai217ea7d2013-03-01 19:14:38 +053086 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
Doug Anderson8ced8642011-01-10 14:16:30 -080087 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080088
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080089
Shawn O. Pearce632768b2008-10-23 11:58:52 -070090class DownloadedChange(object):
91 _commit_cache = None
92
93 def __init__(self, project, base, change_id, ps_id, commit):
94 self.project = project
95 self.base = base
96 self.change_id = change_id
97 self.ps_id = ps_id
98 self.commit = commit
99
100 @property
101 def commits(self):
102 if self._commit_cache is None:
103 self._commit_cache = self.project.bare_git.rev_list(
104 '--abbrev=8',
105 '--abbrev-commit',
106 '--pretty=oneline',
107 '--reverse',
108 '--date-order',
109 not_rev(self.base),
110 self.commit,
111 '--')
112 return self._commit_cache
113
114
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700115class ReviewableBranch(object):
116 _commit_cache = None
117
118 def __init__(self, project, branch, base):
119 self.project = project
120 self.branch = branch
121 self.base = base
122
123 @property
124 def name(self):
125 return self.branch.name
126
127 @property
128 def commits(self):
129 if self._commit_cache is None:
130 self._commit_cache = self.project.bare_git.rev_list(
131 '--abbrev=8',
132 '--abbrev-commit',
133 '--pretty=oneline',
134 '--reverse',
135 '--date-order',
136 not_rev(self.base),
137 R_HEADS + self.name,
138 '--')
139 return self._commit_cache
140
141 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800142 def unabbrev_commits(self):
143 r = dict()
144 for commit in self.project.bare_git.rev_list(
145 not_rev(self.base),
146 R_HEADS + self.name,
147 '--'):
148 r[commit[0:8]] = commit
149 return r
150
151 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700152 def date(self):
153 return self.project.bare_git.log(
154 '--pretty=format:%cd',
155 '-n', '1',
156 R_HEADS + self.name,
157 '--')
158
Brian Harring435370c2012-07-28 15:37:04 -0700159 def UploadForReview(self, people, auto_topic=False, draft=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800160 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700161 people,
Brian Harring435370c2012-07-28 15:37:04 -0700162 auto_topic=auto_topic,
163 draft=draft)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700164
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700165 def GetPublishedRefs(self):
166 refs = {}
167 output = self.project.bare_git.ls_remote(
168 self.branch.remote.SshReviewUrl(self.project.UserEmail),
169 'refs/changes/*')
170 for line in output.split('\n'):
171 try:
172 (sha, ref) = line.split()
173 refs[sha] = ref
174 except ValueError:
175 pass
176
177 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178
179class StatusColoring(Coloring):
180 def __init__(self, config):
181 Coloring.__init__(self, config, 'status')
182 self.project = self.printer('header', attr = 'bold')
183 self.branch = self.printer('header', attr = 'bold')
184 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700185 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186
187 self.added = self.printer('added', fg = 'green')
188 self.changed = self.printer('changed', fg = 'red')
189 self.untracked = self.printer('untracked', fg = 'red')
190
191
192class DiffColoring(Coloring):
193 def __init__(self, config):
194 Coloring.__init__(self, config, 'diff')
195 self.project = self.printer('header', attr = 'bold')
196
James W. Mills24c13082012-04-12 15:04:13 -0500197class _Annotation:
198 def __init__(self, name, value, keep):
199 self.name = name
200 self.value = value
201 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700202
203class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800204 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205 self.src = src
206 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800207 self.abs_src = abssrc
208 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700209
210 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800211 src = self.abs_src
212 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213 # copy file if it does not exist or is out of date
214 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
215 try:
216 # remove existing file first, since it might be read-only
217 if os.path.exists(dest):
218 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400219 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200220 dest_dir = os.path.dirname(dest)
221 if not os.path.isdir(dest_dir):
222 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223 shutil.copy(src, dest)
224 # make the file read-only
225 mode = os.stat(dest)[stat.ST_MODE]
226 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
227 os.chmod(dest, mode)
228 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700229 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700231class RemoteSpec(object):
232 def __init__(self,
233 name,
234 url = None,
235 review = None):
236 self.name = name
237 self.url = url
238 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239
Doug Anderson37282b42011-03-04 11:54:18 -0800240class RepoHook(object):
241 """A RepoHook contains information about a script to run as a hook.
242
243 Hooks are used to run a python script before running an upload (for instance,
244 to run presubmit checks). Eventually, we may have hooks for other actions.
245
246 This shouldn't be confused with files in the 'repo/hooks' directory. Those
247 files are copied into each '.git/hooks' folder for each project. Repo-level
248 hooks are associated instead with repo actions.
249
250 Hooks are always python. When a hook is run, we will load the hook into the
251 interpreter and execute its main() function.
252 """
253 def __init__(self,
254 hook_type,
255 hooks_project,
256 topdir,
257 abort_if_user_denies=False):
258 """RepoHook constructor.
259
260 Params:
261 hook_type: A string representing the type of hook. This is also used
262 to figure out the name of the file containing the hook. For
263 example: 'pre-upload'.
264 hooks_project: The project containing the repo hooks. If you have a
265 manifest, this is manifest.repo_hooks_project. OK if this is None,
266 which will make the hook a no-op.
267 topdir: Repo's top directory (the one containing the .repo directory).
268 Scripts will run with CWD as this directory. If you have a manifest,
269 this is manifest.topdir
270 abort_if_user_denies: If True, we'll throw a HookError() if the user
271 doesn't allow us to run the hook.
272 """
273 self._hook_type = hook_type
274 self._hooks_project = hooks_project
275 self._topdir = topdir
276 self._abort_if_user_denies = abort_if_user_denies
277
278 # Store the full path to the script for convenience.
279 if self._hooks_project:
280 self._script_fullpath = os.path.join(self._hooks_project.worktree,
281 self._hook_type + '.py')
282 else:
283 self._script_fullpath = None
284
285 def _GetHash(self):
286 """Return a hash of the contents of the hooks directory.
287
288 We'll just use git to do this. This hash has the property that if anything
289 changes in the directory we will return a different has.
290
291 SECURITY CONSIDERATION:
292 This hash only represents the contents of files in the hook directory, not
293 any other files imported or called by hooks. Changes to imported files
294 can change the script behavior without affecting the hash.
295
296 Returns:
297 A string representing the hash. This will always be ASCII so that it can
298 be printed to the user easily.
299 """
300 assert self._hooks_project, "Must have hooks to calculate their hash."
301
302 # We will use the work_git object rather than just calling GetRevisionId().
303 # That gives us a hash of the latest checked in version of the files that
304 # the user will actually be executing. Specifically, GetRevisionId()
305 # doesn't appear to change even if a user checks out a different version
306 # of the hooks repo (via git checkout) nor if a user commits their own revs.
307 #
308 # NOTE: Local (non-committed) changes will not be factored into this hash.
309 # I think this is OK, since we're really only worried about warning the user
310 # about upstream changes.
311 return self._hooks_project.work_git.rev_parse('HEAD')
312
313 def _GetMustVerb(self):
314 """Return 'must' if the hook is required; 'should' if not."""
315 if self._abort_if_user_denies:
316 return 'must'
317 else:
318 return 'should'
319
320 def _CheckForHookApproval(self):
321 """Check to see whether this hook has been approved.
322
323 We'll look at the hash of all of the hooks. If this matches the hash that
324 the user last approved, we're done. If it doesn't, we'll ask the user
325 about approval.
326
327 Note that we ask permission for each individual hook even though we use
328 the hash of all hooks when detecting changes. We'd like the user to be
329 able to approve / deny each hook individually. We only use the hash of all
330 hooks because there is no other easy way to detect changes to local imports.
331
332 Returns:
333 True if this hook is approved to run; False otherwise.
334
335 Raises:
336 HookError: Raised if the user doesn't approve and abort_if_user_denies
337 was passed to the consturctor.
338 """
Doug Anderson37282b42011-03-04 11:54:18 -0800339 hooks_config = self._hooks_project.config
340 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
341
342 # Get the last hash that the user approved for this hook; may be None.
343 old_hash = hooks_config.GetString(git_approval_key)
344
345 # Get the current hash so we can tell if scripts changed since approval.
346 new_hash = self._GetHash()
347
348 if old_hash is not None:
349 # User previously approved hook and asked not to be prompted again.
350 if new_hash == old_hash:
351 # Approval matched. We're done.
352 return True
353 else:
354 # Give the user a reason why we're prompting, since they last told
355 # us to "never ask again".
356 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
357 self._hook_type)
358 else:
359 prompt = ''
360
361 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
362 if sys.stdout.isatty():
363 prompt += ('Repo %s run the script:\n'
364 ' %s\n'
365 '\n'
366 'Do you want to allow this script to run '
367 '(yes/yes-never-ask-again/NO)? ') % (
368 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530369 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900370 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800371
372 # User is doing a one-time approval.
373 if response in ('y', 'yes'):
374 return True
375 elif response == 'yes-never-ask-again':
376 hooks_config.SetString(git_approval_key, new_hash)
377 return True
378
379 # For anything else, we'll assume no approval.
380 if self._abort_if_user_denies:
381 raise HookError('You must allow the %s hook or use --no-verify.' %
382 self._hook_type)
383
384 return False
385
386 def _ExecuteHook(self, **kwargs):
387 """Actually execute the given hook.
388
389 This will run the hook's 'main' function in our python interpreter.
390
391 Args:
392 kwargs: Keyword arguments to pass to the hook. These are often specific
393 to the hook type. For instance, pre-upload hooks will contain
394 a project_list.
395 """
396 # Keep sys.path and CWD stashed away so that we can always restore them
397 # upon function exit.
398 orig_path = os.getcwd()
399 orig_syspath = sys.path
400
401 try:
402 # Always run hooks with CWD as topdir.
403 os.chdir(self._topdir)
404
405 # Put the hook dir as the first item of sys.path so hooks can do
406 # relative imports. We want to replace the repo dir as [0] so
407 # hooks can't import repo files.
408 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
409
410 # Exec, storing global context in the context dict. We catch exceptions
411 # and convert to a HookError w/ just the failing traceback.
412 context = {}
413 try:
414 execfile(self._script_fullpath, context)
415 except Exception:
416 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
417 traceback.format_exc(), self._hook_type))
418
419 # Running the script should have defined a main() function.
420 if 'main' not in context:
421 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
422
423
424 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
425 # We don't actually want hooks to define their main with this argument--
426 # it's there to remind them that their hook should always take **kwargs.
427 # For instance, a pre-upload hook should be defined like:
428 # def main(project_list, **kwargs):
429 #
430 # This allows us to later expand the API without breaking old hooks.
431 kwargs = kwargs.copy()
432 kwargs['hook_should_take_kwargs'] = True
433
434 # Call the main function in the hook. If the hook should cause the
435 # build to fail, it will raise an Exception. We'll catch that convert
436 # to a HookError w/ just the failing traceback.
437 try:
438 context['main'](**kwargs)
439 except Exception:
440 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
441 'above.' % (
442 traceback.format_exc(), self._hook_type))
443 finally:
444 # Restore sys.path and CWD.
445 sys.path = orig_syspath
446 os.chdir(orig_path)
447
448 def Run(self, user_allows_all_hooks, **kwargs):
449 """Run the hook.
450
451 If the hook doesn't exist (because there is no hooks project or because
452 this particular hook is not enabled), this is a no-op.
453
454 Args:
455 user_allows_all_hooks: If True, we will never prompt about running the
456 hook--we'll just assume it's OK to run it.
457 kwargs: Keyword arguments to pass to the hook. These are often specific
458 to the hook type. For instance, pre-upload hooks will contain
459 a project_list.
460
461 Raises:
462 HookError: If there was a problem finding the hook or the user declined
463 to run a required hook (from _CheckForHookApproval).
464 """
465 # No-op if there is no hooks project or if hook is disabled.
466 if ((not self._hooks_project) or
467 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
468 return
469
470 # Bail with a nice error if we can't find the hook.
471 if not os.path.isfile(self._script_fullpath):
472 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
473
474 # Make sure the user is OK with running the hook.
475 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
476 return
477
478 # Run the hook with the same version of python we're using.
479 self._ExecuteHook(**kwargs)
480
481
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700482class Project(object):
483 def __init__(self,
484 manifest,
485 name,
486 remote,
487 gitdir,
488 worktree,
489 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700490 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800491 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700492 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700493 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700494 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800495 sync_s = False,
David Pursehouseede7f122012-11-27 22:25:30 +0900496 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800497 upstream = None,
498 parent = None,
499 is_derived = False):
500 """Init a Project object.
501
502 Args:
503 manifest: The XmlManifest object.
504 name: The `name` attribute of manifest.xml's project element.
505 remote: RemoteSpec object specifying its remote's properties.
506 gitdir: Absolute path of git directory.
507 worktree: Absolute path of git working tree.
508 relpath: Relative path of git working tree to repo's top directory.
509 revisionExpr: The `revision` attribute of manifest.xml's project element.
510 revisionId: git commit id for checking out.
511 rebase: The `rebase` attribute of manifest.xml's project element.
512 groups: The `groups` attribute of manifest.xml's project element.
513 sync_c: The `sync-c` attribute of manifest.xml's project element.
514 sync_s: The `sync-s` attribute of manifest.xml's project element.
515 upstream: The `upstream` attribute of manifest.xml's project element.
516 parent: The parent Project object.
517 is_derived: False if the project was explicitly defined in the manifest;
518 True if the project is a discovered submodule.
519 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700520 self.manifest = manifest
521 self.name = name
522 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800523 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800524 if worktree:
525 self.worktree = worktree.replace('\\', '/')
526 else:
527 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700528 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700529 self.revisionExpr = revisionExpr
530
531 if revisionId is None \
532 and revisionExpr \
533 and IsId(revisionExpr):
534 self.revisionId = revisionExpr
535 else:
536 self.revisionId = revisionId
537
Mike Pontillod3153822012-02-28 11:53:24 -0800538 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700539 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700540 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800541 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900542 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700543 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800544 self.parent = parent
545 self.is_derived = is_derived
546 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800547
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700548 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700549 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500550 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700551 self.config = GitConfig.ForRepository(
552 gitdir = self.gitdir,
553 defaults = self.manifest.globalConfig)
554
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800555 if self.worktree:
556 self.work_git = self._GitGetByExec(self, bare=False)
557 else:
558 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700559 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700560 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700561
Doug Anderson37282b42011-03-04 11:54:18 -0800562 # This will be filled in if a project is later identified to be the
563 # project containing repo hooks.
564 self.enabled_repo_hooks = []
565
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700566 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800567 def Derived(self):
568 return self.is_derived
569
570 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700571 def Exists(self):
572 return os.path.isdir(self.gitdir)
573
574 @property
575 def CurrentBranch(self):
576 """Obtain the name of the currently checked out branch.
577 The branch name omits the 'refs/heads/' prefix.
578 None is returned if the project is on a detached HEAD.
579 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700580 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700581 if b.startswith(R_HEADS):
582 return b[len(R_HEADS):]
583 return None
584
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700585 def IsRebaseInProgress(self):
586 w = self.worktree
587 g = os.path.join(w, '.git')
588 return os.path.exists(os.path.join(g, 'rebase-apply')) \
589 or os.path.exists(os.path.join(g, 'rebase-merge')) \
590 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200591
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700592 def IsDirty(self, consider_untracked=True):
593 """Is the working directory modified in some way?
594 """
595 self.work_git.update_index('-q',
596 '--unmerged',
597 '--ignore-missing',
598 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900599 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700600 return True
601 if self.work_git.DiffZ('diff-files'):
602 return True
603 if consider_untracked and self.work_git.LsOthers():
604 return True
605 return False
606
607 _userident_name = None
608 _userident_email = None
609
610 @property
611 def UserName(self):
612 """Obtain the user's personal name.
613 """
614 if self._userident_name is None:
615 self._LoadUserIdentity()
616 return self._userident_name
617
618 @property
619 def UserEmail(self):
620 """Obtain the user's email address. This is very likely
621 to be their Gerrit login.
622 """
623 if self._userident_email is None:
624 self._LoadUserIdentity()
625 return self._userident_email
626
627 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900628 u = self.bare_git.var('GIT_COMMITTER_IDENT')
629 m = re.compile("^(.*) <([^>]*)> ").match(u)
630 if m:
631 self._userident_name = m.group(1)
632 self._userident_email = m.group(2)
633 else:
634 self._userident_name = ''
635 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700636
637 def GetRemote(self, name):
638 """Get the configuration for a single remote.
639 """
640 return self.config.GetRemote(name)
641
642 def GetBranch(self, name):
643 """Get the configuration for a single branch.
644 """
645 return self.config.GetBranch(name)
646
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700647 def GetBranches(self):
648 """Get all existing local branches.
649 """
650 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900651 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700652 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700653
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530654 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700655 if name.startswith(R_HEADS):
656 name = name[len(R_HEADS):]
657 b = self.GetBranch(name)
658 b.current = name == current
659 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900660 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700661 heads[name] = b
662
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530663 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700664 if name.startswith(R_PUB):
665 name = name[len(R_PUB):]
666 b = heads.get(name)
667 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900668 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700669
670 return heads
671
Colin Cross5acde752012-03-28 20:15:45 -0700672 def MatchesGroups(self, manifest_groups):
673 """Returns true if the manifest groups specified at init should cause
674 this project to be synced.
675 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700676 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700677
678 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700679 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700680 manifest_groups: "-group1,group2"
681 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500682
683 The special manifest group "default" will match any project that
684 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700685 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500686 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700687 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500688 if not 'notdefault' in expanded_project_groups:
689 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700690
Conley Owens971de8e2012-04-16 10:36:08 -0700691 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700692 for group in expanded_manifest_groups:
693 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700694 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700695 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700696 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700697
Conley Owens971de8e2012-04-16 10:36:08 -0700698 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700699
700## Status Display ##
701
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500702 def HasChanges(self):
703 """Returns true if there are uncommitted changes.
704 """
705 self.work_git.update_index('-q',
706 '--unmerged',
707 '--ignore-missing',
708 '--refresh')
709 if self.IsRebaseInProgress():
710 return True
711
712 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
713 return True
714
715 if self.work_git.DiffZ('diff-files'):
716 return True
717
718 if self.work_git.LsOthers():
719 return True
720
721 return False
722
Terence Haddock4655e812011-03-31 12:33:34 +0200723 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700724 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200725
726 Args:
727 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728 """
729 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200730 if output_redir == None:
731 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700732 print(file=output_redir)
733 print('project %s/' % self.relpath, file=output_redir)
734 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735 return
736
737 self.work_git.update_index('-q',
738 '--unmerged',
739 '--ignore-missing',
740 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700741 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
743 df = self.work_git.DiffZ('diff-files')
744 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100745 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700746 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700747
748 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200749 if not output_redir == None:
750 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751 out.project('project %-40s', self.relpath + '/')
752
753 branch = self.CurrentBranch
754 if branch is None:
755 out.nobranch('(*** NO BRANCH ***)')
756 else:
757 out.branch('branch %s', branch)
758 out.nl()
759
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700760 if rb:
761 out.important('prior sync failed; rebase still in progress')
762 out.nl()
763
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700764 paths = list()
765 paths.extend(di.keys())
766 paths.extend(df.keys())
767 paths.extend(do)
768
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530769 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900770 try:
771 i = di[p]
772 except KeyError:
773 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700774
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900775 try:
776 f = df[p]
777 except KeyError:
778 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200779
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900780 if i:
781 i_status = i.status.upper()
782 else:
783 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900785 if f:
786 f_status = f.status.lower()
787 else:
788 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700789
790 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800791 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700792 i.src_path, p, i.level)
793 else:
794 line = ' %s%s\t%s' % (i_status, f_status, p)
795
796 if i and not f:
797 out.added('%s', line)
798 elif (i and f) or (not i and f):
799 out.changed('%s', line)
800 elif not i and not f:
801 out.untracked('%s', line)
802 else:
803 out.write('%s', line)
804 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200805
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700806 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807
pelyad67872d2012-03-28 14:49:58 +0300808 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809 """Prints the status of the repository to stdout.
810 """
811 out = DiffColoring(self.config)
812 cmd = ['diff']
813 if out.is_on:
814 cmd.append('--color')
815 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300816 if absolute_paths:
817 cmd.append('--src-prefix=a/%s/' % self.relpath)
818 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700819 cmd.append('--')
820 p = GitCommand(self,
821 cmd,
822 capture_stdout = True,
823 capture_stderr = True)
824 has_diff = False
825 for line in p.process.stdout:
826 if not has_diff:
827 out.nl()
828 out.project('project %s/' % self.relpath)
829 out.nl()
830 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700831 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 p.Wait()
833
834
835## Publish / Upload ##
836
David Pursehouse8a68ff92012-09-24 12:15:13 +0900837 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 """Was the branch published (uploaded) for code review?
839 If so, returns the SHA-1 hash of the last published
840 state for the branch.
841 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700842 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900843 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700844 try:
845 return self.bare_git.rev_parse(key)
846 except GitError:
847 return None
848 else:
849 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900850 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700851 except KeyError:
852 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853
David Pursehouse8a68ff92012-09-24 12:15:13 +0900854 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700855 """Prunes any stale published refs.
856 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900857 if all_refs is None:
858 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700859 heads = set()
860 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530861 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862 if name.startswith(R_HEADS):
863 heads.add(name)
864 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900865 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700866
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530867 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868 n = name[len(R_PUB):]
869 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900870 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700872 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700873 """List any branches which can be uploaded for review.
874 """
875 heads = {}
876 pubed = {}
877
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530878 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900880 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900882 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700883
884 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530885 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900886 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700887 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700888 if selected_branch and branch != selected_branch:
889 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800891 rb = self.GetUploadableBranch(branch)
892 if rb:
893 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894 return ready
895
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800896 def GetUploadableBranch(self, branch_name):
897 """Get a single uploadable branch, or None.
898 """
899 branch = self.GetBranch(branch_name)
900 base = branch.LocalMerge
901 if branch.LocalMerge:
902 rb = ReviewableBranch(self, branch, base)
903 if rb.commits:
904 return rb
905 return None
906
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700907 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700908 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700909 auto_topic=False,
910 draft=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911 """Uploads the named branch for code review.
912 """
913 if branch is None:
914 branch = self.CurrentBranch
915 if branch is None:
916 raise GitError('not currently on a branch')
917
918 branch = self.GetBranch(branch)
919 if not branch.LocalMerge:
920 raise GitError('branch %s does not track a remote' % branch.name)
921 if not branch.remote.review:
922 raise GitError('remote %s has no review url' % branch.remote.name)
923
924 dest_branch = branch.merge
925 if not dest_branch.startswith(R_HEADS):
926 dest_branch = R_HEADS + dest_branch
927
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800928 if not branch.remote.projectname:
929 branch.remote.projectname = self.name
930 branch.remote.Save()
931
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800932 url = branch.remote.ReviewUrl(self.UserEmail)
933 if url is None:
934 raise UploadError('review not configured')
935 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800936
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800937 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800938 rp = ['gerrit receive-pack']
939 for e in people[0]:
940 rp.append('--reviewer=%s' % sq(e))
941 for e in people[1]:
942 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800943 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700944
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800945 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800946
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800947 if dest_branch.startswith(R_HEADS):
948 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700949
950 upload_type = 'for'
951 if draft:
952 upload_type = 'drafts'
953
954 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
955 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800956 if auto_topic:
957 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800958 if not url.startswith('ssh://'):
959 rp = ['r=%s' % p for p in people[0]] + \
960 ['cc=%s' % p for p in people[1]]
961 if rp:
962 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800963 cmd.append(ref_spec)
964
965 if GitCommand(self, cmd, bare = True).Wait() != 0:
966 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967
968 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
969 self.bare_git.UpdateRef(R_PUB + branch.name,
970 R_HEADS + branch.name,
971 message = msg)
972
973
974## Sync ##
975
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700976 def Sync_NetworkHalf(self,
977 quiet=False,
978 is_new=None,
979 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700980 clone_bundle=True,
981 no_tags=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982 """Perform only the network IO portion of the sync process.
983 Local working directory/branch state is not affected.
984 """
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700985 if is_new is None:
986 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +0200987 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700988 self._InitGitDir()
989 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700990
991 if is_new:
992 alt = os.path.join(self.gitdir, 'objects/info/alternates')
993 try:
994 fd = open(alt, 'rb')
995 try:
996 alt_dir = fd.readline().rstrip()
997 finally:
998 fd.close()
999 except IOError:
1000 alt_dir = None
1001 else:
1002 alt_dir = None
1003
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001004 if clone_bundle \
1005 and alt_dir is None \
1006 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001007 is_new = False
1008
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001009 if not current_branch_only:
1010 if self.sync_c:
1011 current_branch_only = True
1012 elif not self.manifest._loaded:
1013 # Manifest cannot check defaults until it syncs.
1014 current_branch_only = False
1015 elif self.manifest.default.sync_c:
1016 current_branch_only = True
1017
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001018 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001019 current_branch_only=current_branch_only,
1020 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001021 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001022
1023 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001024 self._InitMRef()
1025 else:
1026 self._InitMirrorHead()
1027 try:
1028 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1029 except OSError:
1030 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001032
1033 def PostRepoUpgrade(self):
1034 self._InitHooks()
1035
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001037 for copyfile in self.copyfiles:
1038 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039
David Pursehouse8a68ff92012-09-24 12:15:13 +09001040 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001041 if self.revisionId:
1042 return self.revisionId
1043
1044 rem = self.GetRemote(self.remote.name)
1045 rev = rem.ToLocal(self.revisionExpr)
1046
David Pursehouse8a68ff92012-09-24 12:15:13 +09001047 if all_refs is not None and rev in all_refs:
1048 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001049
1050 try:
1051 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1052 except GitError:
1053 raise ManifestInvalidRevisionError(
1054 'revision %s in %s not found' % (self.revisionExpr,
1055 self.name))
1056
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001057 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058 """Perform only the local IO portion of the sync process.
1059 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001061 all_refs = self.bare_ref.all
1062 self.CleanPublishedCache(all_refs)
1063 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001064
David Pursehouse1d947b32012-10-25 12:23:11 +09001065 def _doff():
1066 self._FastForward(revid)
1067 self._CopyFiles()
1068
Skyler Kaufman835cd682011-03-08 12:14:41 -08001069 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001070 head = self.work_git.GetHead()
1071 if head.startswith(R_HEADS):
1072 branch = head[len(R_HEADS):]
1073 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001074 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001075 except KeyError:
1076 head = None
1077 else:
1078 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001080 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081 # Currently on a detached HEAD. The user is assumed to
1082 # not have any local modifications worth worrying about.
1083 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001084 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001085 syncbuf.fail(self, _PriorSyncFailedError())
1086 return
1087
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001088 if head == revid:
1089 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001090 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001091 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001092 if not syncbuf.detach_head:
1093 return
1094 else:
1095 lost = self._revlist(not_rev(revid), HEAD)
1096 if lost:
1097 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001098
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001099 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001100 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001101 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001102 syncbuf.fail(self, e)
1103 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001104 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001105 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001107 if head == revid:
1108 # No changes; don't do anything further.
1109 #
1110 return
1111
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001112 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001113
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001114 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001115 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001116 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001118 syncbuf.info(self,
1119 "leaving %s; does not track upstream",
1120 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001122 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001123 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001124 syncbuf.fail(self, e)
1125 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001127 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001128
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001129 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001130 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001131 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001132 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001133 if not_merged:
1134 if upstream_gain:
1135 # The user has published this branch and some of those
1136 # commits are not yet merged upstream. We do not want
1137 # to rewrite the published commits so we punt.
1138 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001139 syncbuf.fail(self,
1140 "branch %s is published (but not merged) and is now %d commits behind"
1141 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001142 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001143 elif pub == head:
1144 # All published commits are merged, and thus we are a
1145 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001146 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001147 syncbuf.later1(self, _doff)
1148 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001150 # Examine the local commits not in the remote. Find the
1151 # last one attributed to this user, if any.
1152 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001153 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001154 last_mine = None
1155 cnt_mine = 0
1156 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001157 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001158 if committer_email == self.UserEmail:
1159 last_mine = commit_id
1160 cnt_mine += 1
1161
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001162 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001163 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001164
1165 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001166 syncbuf.fail(self, _DirtyError())
1167 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001168
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001169 # If the upstream switched on us, warn the user.
1170 #
1171 if branch.merge != self.revisionExpr:
1172 if branch.merge and self.revisionExpr:
1173 syncbuf.info(self,
1174 'manifest switched %s...%s',
1175 branch.merge,
1176 self.revisionExpr)
1177 elif branch.merge:
1178 syncbuf.info(self,
1179 'manifest no longer tracks %s',
1180 branch.merge)
1181
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001182 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001183 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001184 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001185 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001186 syncbuf.info(self,
1187 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001188 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001189
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001190 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001191 if not ID_RE.match(self.revisionExpr):
1192 # in case of manifest sync the revisionExpr might be a SHA1
1193 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194 branch.Save()
1195
Mike Pontillod3153822012-02-28 11:53:24 -08001196 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001197 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001198 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001199 self._CopyFiles()
1200 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001201 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001202 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001203 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001204 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001205 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001206 syncbuf.fail(self, e)
1207 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001208 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001209 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001210
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001211 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001212 # dest should already be an absolute path, but src is project relative
1213 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001214 abssrc = os.path.join(self.worktree, src)
1215 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001216
James W. Mills24c13082012-04-12 15:04:13 -05001217 def AddAnnotation(self, name, value, keep):
1218 self.annotations.append(_Annotation(name, value, keep))
1219
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001220 def DownloadPatchSet(self, change_id, patch_id):
1221 """Download a single patch set of a single change to FETCH_HEAD.
1222 """
1223 remote = self.GetRemote(self.remote.name)
1224
1225 cmd = ['fetch', remote.name]
1226 cmd.append('refs/changes/%2.2d/%d/%d' \
1227 % (change_id % 100, change_id, patch_id))
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301228 cmd.extend(list(map(str, remote.fetch)))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001229 if GitCommand(self, cmd, bare=True).Wait() != 0:
1230 return None
1231 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001232 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001233 change_id,
1234 patch_id,
1235 self.bare_git.rev_parse('FETCH_HEAD'))
1236
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001237
1238## Branch Management ##
1239
1240 def StartBranch(self, name):
1241 """Create a new branch off the manifest's revision.
1242 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001243 head = self.work_git.GetHead()
1244 if head == (R_HEADS + name):
1245 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001246
David Pursehouse8a68ff92012-09-24 12:15:13 +09001247 all_refs = self.bare_ref.all
1248 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001249 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001250 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001251 capture_stdout = True,
1252 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001253
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001254 branch = self.GetBranch(name)
1255 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001256 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001257 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001258
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001259 if head.startswith(R_HEADS):
1260 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001261 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001262 except KeyError:
1263 head = None
1264
1265 if revid and head and revid == head:
1266 ref = os.path.join(self.gitdir, R_HEADS + name)
1267 try:
1268 os.makedirs(os.path.dirname(ref))
1269 except OSError:
1270 pass
1271 _lwrite(ref, '%s\n' % revid)
1272 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1273 'ref: %s%s\n' % (R_HEADS, name))
1274 branch.Save()
1275 return True
1276
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001277 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001278 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001279 capture_stdout = True,
1280 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001281 branch.Save()
1282 return True
1283 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001284
Wink Saville02d79452009-04-10 13:01:24 -07001285 def CheckoutBranch(self, name):
1286 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001287
1288 Args:
1289 name: The name of the branch to checkout.
1290
1291 Returns:
1292 True if the checkout succeeded; False if it didn't; None if the branch
1293 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001294 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001295 rev = R_HEADS + name
1296 head = self.work_git.GetHead()
1297 if head == rev:
1298 # Already on the branch
1299 #
1300 return True
Wink Saville02d79452009-04-10 13:01:24 -07001301
David Pursehouse8a68ff92012-09-24 12:15:13 +09001302 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001303 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001304 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001305 except KeyError:
1306 # Branch does not exist in this project
1307 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001308 return None
Wink Saville02d79452009-04-10 13:01:24 -07001309
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001310 if head.startswith(R_HEADS):
1311 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001312 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001313 except KeyError:
1314 head = None
1315
1316 if head == revid:
1317 # Same revision; just update HEAD to point to the new
1318 # target branch, but otherwise take no other action.
1319 #
1320 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1321 'ref: %s%s\n' % (R_HEADS, name))
1322 return True
1323
1324 return GitCommand(self,
1325 ['checkout', name, '--'],
1326 capture_stdout = True,
1327 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001328
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001329 def AbandonBranch(self, name):
1330 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001331
1332 Args:
1333 name: The name of the branch to abandon.
1334
1335 Returns:
1336 True if the abandon succeeded; False if it didn't; None if the branch
1337 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001338 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001339 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001340 all_refs = self.bare_ref.all
1341 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001342 # Doesn't exist
1343 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001344
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001345 head = self.work_git.GetHead()
1346 if head == rev:
1347 # We can't destroy the branch while we are sitting
1348 # on it. Switch to a detached HEAD.
1349 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001350 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001351
David Pursehouse8a68ff92012-09-24 12:15:13 +09001352 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001353 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001354 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1355 '%s\n' % revid)
1356 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001357 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001358
1359 return GitCommand(self,
1360 ['branch', '-D', name],
1361 capture_stdout = True,
1362 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001363
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 def PruneHeads(self):
1365 """Prune any topic branches already merged into upstream.
1366 """
1367 cb = self.CurrentBranch
1368 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001369 left = self._allrefs
1370 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001371 if name.startswith(R_HEADS):
1372 name = name[len(R_HEADS):]
1373 if cb is None or name != cb:
1374 kill.append(name)
1375
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001376 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001377 if cb is not None \
1378 and not self._revlist(HEAD + '...' + rev) \
1379 and not self.IsDirty(consider_untracked = False):
1380 self.work_git.DetachHead(HEAD)
1381 kill.append(cb)
1382
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001383 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001384 old = self.bare_git.GetHead()
1385 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1387
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001388 try:
1389 self.bare_git.DetachHead(rev)
1390
1391 b = ['branch', '-d']
1392 b.extend(kill)
1393 b = GitCommand(self, b, bare=True,
1394 capture_stdout=True,
1395 capture_stderr=True)
1396 b.Wait()
1397 finally:
1398 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001399 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001400
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001401 for branch in kill:
1402 if (R_HEADS + branch) not in left:
1403 self.CleanPublishedCache()
1404 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001405
1406 if cb and cb not in kill:
1407 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001408 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001409
1410 kept = []
1411 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001412 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001413 branch = self.GetBranch(branch)
1414 base = branch.LocalMerge
1415 if not base:
1416 base = rev
1417 kept.append(ReviewableBranch(self, branch, base))
1418 return kept
1419
1420
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001421## Submodule Management ##
1422
1423 def GetRegisteredSubprojects(self):
1424 result = []
1425 def rec(subprojects):
1426 if not subprojects:
1427 return
1428 result.extend(subprojects)
1429 for p in subprojects:
1430 rec(p.subprojects)
1431 rec(self.subprojects)
1432 return result
1433
1434 def _GetSubmodules(self):
1435 # Unfortunately we cannot call `git submodule status --recursive` here
1436 # because the working tree might not exist yet, and it cannot be used
1437 # without a working tree in its current implementation.
1438
1439 def get_submodules(gitdir, rev):
1440 # Parse .gitmodules for submodule sub_paths and sub_urls
1441 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1442 if not sub_paths:
1443 return []
1444 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1445 # revision of submodule repository
1446 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1447 submodules = []
1448 for sub_path, sub_url in zip(sub_paths, sub_urls):
1449 try:
1450 sub_rev = sub_revs[sub_path]
1451 except KeyError:
1452 # Ignore non-exist submodules
1453 continue
1454 submodules.append((sub_rev, sub_path, sub_url))
1455 return submodules
1456
1457 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1458 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1459 def parse_gitmodules(gitdir, rev):
1460 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1461 try:
1462 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1463 bare = True, gitdir = gitdir)
1464 except GitError:
1465 return [], []
1466 if p.Wait() != 0:
1467 return [], []
1468
1469 gitmodules_lines = []
1470 fd, temp_gitmodules_path = tempfile.mkstemp()
1471 try:
1472 os.write(fd, p.stdout)
1473 os.close(fd)
1474 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1475 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1476 bare = True, gitdir = gitdir)
1477 if p.Wait() != 0:
1478 return [], []
1479 gitmodules_lines = p.stdout.split('\n')
1480 except GitError:
1481 return [], []
1482 finally:
1483 os.remove(temp_gitmodules_path)
1484
1485 names = set()
1486 paths = {}
1487 urls = {}
1488 for line in gitmodules_lines:
1489 if not line:
1490 continue
1491 m = re_path.match(line)
1492 if m:
1493 names.add(m.group(1))
1494 paths[m.group(1)] = m.group(2)
1495 continue
1496 m = re_url.match(line)
1497 if m:
1498 names.add(m.group(1))
1499 urls[m.group(1)] = m.group(2)
1500 continue
1501 names = sorted(names)
1502 return ([paths.get(name, '') for name in names],
1503 [urls.get(name, '') for name in names])
1504
1505 def git_ls_tree(gitdir, rev, paths):
1506 cmd = ['ls-tree', rev, '--']
1507 cmd.extend(paths)
1508 try:
1509 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1510 bare = True, gitdir = gitdir)
1511 except GitError:
1512 return []
1513 if p.Wait() != 0:
1514 return []
1515 objects = {}
1516 for line in p.stdout.split('\n'):
1517 if not line.strip():
1518 continue
1519 object_rev, object_path = line.split()[2:4]
1520 objects[object_path] = object_rev
1521 return objects
1522
1523 try:
1524 rev = self.GetRevisionId()
1525 except GitError:
1526 return []
1527 return get_submodules(self.gitdir, rev)
1528
1529 def GetDerivedSubprojects(self):
1530 result = []
1531 if not self.Exists:
1532 # If git repo does not exist yet, querying its submodules will
1533 # mess up its states; so return here.
1534 return result
1535 for rev, path, url in self._GetSubmodules():
1536 name = self.manifest.GetSubprojectName(self, path)
1537 project = self.manifest.projects.get(name)
1538 if project:
1539 result.extend(project.GetDerivedSubprojects())
1540 continue
1541 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1542 remote = RemoteSpec(self.remote.name,
1543 url = url,
1544 review = self.remote.review)
1545 subproject = Project(manifest = self.manifest,
1546 name = name,
1547 remote = remote,
1548 gitdir = gitdir,
1549 worktree = worktree,
1550 relpath = relpath,
1551 revisionExpr = self.revisionExpr,
1552 revisionId = rev,
1553 rebase = self.rebase,
1554 groups = self.groups,
1555 sync_c = self.sync_c,
1556 sync_s = self.sync_s,
1557 parent = self,
1558 is_derived = True)
1559 result.append(subproject)
1560 result.extend(subproject.GetDerivedSubprojects())
1561 return result
1562
1563
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001564## Direct Git Commands ##
1565
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001566 def _RemoteFetch(self, name=None,
1567 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001568 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001569 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001570 alt_dir=None,
1571 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001572
1573 is_sha1 = False
1574 tag_name = None
1575
Brian Harring14a66742012-09-28 20:21:57 -07001576 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001577 try:
1578 # if revision (sha or tag) is not present then following function
1579 # throws an error.
1580 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1581 return True
1582 except GitError:
1583 # There is no such persistent revision. We have to fetch it.
1584 return False
Brian Harring14a66742012-09-28 20:21:57 -07001585
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001586 if current_branch_only:
1587 if ID_RE.match(self.revisionExpr) is not None:
1588 is_sha1 = True
1589 elif self.revisionExpr.startswith(R_TAGS):
1590 # this is a tag and its sha1 value should never change
1591 tag_name = self.revisionExpr[len(R_TAGS):]
1592
1593 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001594 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001595 return True
Brian Harring14a66742012-09-28 20:21:57 -07001596 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1597 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001598
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001599 if not name:
1600 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001601
1602 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001603 remote = self.GetRemote(name)
1604 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001605 ssh_proxy = True
1606
Shawn O. Pearce88443382010-10-08 10:02:09 +02001607 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001608 if alt_dir and 'objects' == os.path.basename(alt_dir):
1609 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001610 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1611 remote = self.GetRemote(name)
1612
David Pursehouse8a68ff92012-09-24 12:15:13 +09001613 all_refs = self.bare_ref.all
1614 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001615 tmp = set()
1616
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301617 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001618 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001619 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001620 all_refs[r] = ref_id
1621 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001622 continue
1623
David Pursehouse8a68ff92012-09-24 12:15:13 +09001624 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001625 continue
1626
David Pursehouse8a68ff92012-09-24 12:15:13 +09001627 r = 'refs/_alt/%s' % ref_id
1628 all_refs[r] = ref_id
1629 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001630 tmp.add(r)
1631
Shawn O. Pearce88443382010-10-08 10:02:09 +02001632 tmp_packed = ''
1633 old_packed = ''
1634
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301635 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001636 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001637 tmp_packed += line
1638 if r not in tmp:
1639 old_packed += line
1640
1641 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001642 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001643 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001644
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001645 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001646
1647 # The --depth option only affects the initial fetch; after that we'll do
1648 # full fetches of changes.
David Pursehouseede7f122012-11-27 22:25:30 +09001649 if self.clone_depth:
1650 depth = self.clone_depth
1651 else:
1652 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Doug Anderson30d45292011-05-04 15:01:04 -07001653 if depth and initial:
1654 cmd.append('--depth=%s' % depth)
1655
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001656 if quiet:
1657 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001658 if not self.worktree:
1659 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001660 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001661
Brian Harring14a66742012-09-28 20:21:57 -07001662 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001663 # Fetch whole repo
Jimmie Wester2f992cb2012-12-07 12:49:51 +01001664 # If using depth then we should not get all the tags since they may
1665 # be outside of the depth.
1666 if no_tags or depth:
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001667 cmd.append('--no-tags')
1668 else:
1669 cmd.append('--tags')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301670 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001671 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001672 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001673 cmd.append(tag_name)
1674 else:
1675 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001676 if is_sha1:
1677 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001678 if branch.startswith(R_HEADS):
1679 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301680 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001681
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001682 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001683 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001684 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1685 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001686 ok = True
1687 break
Brian Harring14a66742012-09-28 20:21:57 -07001688 elif current_branch_only and is_sha1 and ret == 128:
1689 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1690 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1691 # abort the optimization attempt and do a full sync.
1692 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001693 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001694
1695 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001696 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001697 if old_packed != '':
1698 _lwrite(packed_refs, old_packed)
1699 else:
1700 os.remove(packed_refs)
1701 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001702
1703 if is_sha1 and current_branch_only and self.upstream:
1704 # We just synced the upstream given branch; verify we
1705 # got what we wanted, else trigger a second run of all
1706 # refs.
1707 if not CheckForSha1():
1708 return self._RemoteFetch(name=name, current_branch_only=False,
1709 initial=False, quiet=quiet, alt_dir=alt_dir)
1710
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001711 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001712
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001713 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001714 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001715 return False
1716
1717 remote = self.GetRemote(self.remote.name)
1718 bundle_url = remote.url + '/clone.bundle'
1719 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001720 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1721 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001722 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1723 return False
1724
1725 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1726 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1727
1728 exist_dst = os.path.exists(bundle_dst)
1729 exist_tmp = os.path.exists(bundle_tmp)
1730
1731 if not initial and not exist_dst and not exist_tmp:
1732 return False
1733
1734 if not exist_dst:
1735 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1736 if not exist_dst:
1737 return False
1738
1739 cmd = ['fetch']
1740 if quiet:
1741 cmd.append('--quiet')
1742 if not self.worktree:
1743 cmd.append('--update-head-ok')
1744 cmd.append(bundle_dst)
1745 for f in remote.fetch:
1746 cmd.append(str(f))
1747 cmd.append('refs/tags/*:refs/tags/*')
1748
1749 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001750 if os.path.exists(bundle_dst):
1751 os.remove(bundle_dst)
1752 if os.path.exists(bundle_tmp):
1753 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001754 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001755
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001756 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001757 if os.path.exists(dstPath):
1758 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001759
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001760 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001761 if quiet:
1762 cmd += ['--silent']
1763 if os.path.exists(tmpPath):
1764 size = os.stat(tmpPath).st_size
1765 if size >= 1024:
1766 cmd += ['--continue-at', '%d' % (size,)]
1767 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001768 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001769 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1770 cmd += ['--proxy', os.environ['http_proxy']]
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001771 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
1772 if cookiefile:
1773 cmd += ['--cookie', cookiefile]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001774 cmd += [srcUrl]
1775
1776 if IsTrace():
1777 Trace('%s', ' '.join(cmd))
1778 try:
1779 proc = subprocess.Popen(cmd)
1780 except OSError:
1781 return False
1782
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001783 curlret = proc.wait()
1784
1785 if curlret == 22:
1786 # From curl man page:
1787 # 22: HTTP page not retrieved. The requested url was not found or
1788 # returned another error with the HTTP error code being 400 or above.
1789 # This return code only appears if -f, --fail is used.
1790 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001791 print("Server does not provide clone.bundle; ignoring.",
1792 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001793 return False
1794
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001795 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001796 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001797 os.rename(tmpPath, dstPath)
1798 return True
1799 else:
1800 os.remove(tmpPath)
1801 return False
1802 else:
1803 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001804
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001805 def _Checkout(self, rev, quiet=False):
1806 cmd = ['checkout']
1807 if quiet:
1808 cmd.append('-q')
1809 cmd.append(rev)
1810 cmd.append('--')
1811 if GitCommand(self, cmd).Wait() != 0:
1812 if self._allrefs:
1813 raise GitError('%s checkout %s ' % (self.name, rev))
1814
Pierre Tardye5a21222011-03-24 16:28:18 +01001815 def _CherryPick(self, rev, quiet=False):
1816 cmd = ['cherry-pick']
1817 cmd.append(rev)
1818 cmd.append('--')
1819 if GitCommand(self, cmd).Wait() != 0:
1820 if self._allrefs:
1821 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1822
Erwan Mahea94f1622011-08-19 13:56:09 +02001823 def _Revert(self, rev, quiet=False):
1824 cmd = ['revert']
1825 cmd.append('--no-edit')
1826 cmd.append(rev)
1827 cmd.append('--')
1828 if GitCommand(self, cmd).Wait() != 0:
1829 if self._allrefs:
1830 raise GitError('%s revert %s ' % (self.name, rev))
1831
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001832 def _ResetHard(self, rev, quiet=True):
1833 cmd = ['reset', '--hard']
1834 if quiet:
1835 cmd.append('-q')
1836 cmd.append(rev)
1837 if GitCommand(self, cmd).Wait() != 0:
1838 raise GitError('%s reset --hard %s ' % (self.name, rev))
1839
1840 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001841 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001842 if onto is not None:
1843 cmd.extend(['--onto', onto])
1844 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001845 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001846 raise GitError('%s rebase %s ' % (self.name, upstream))
1847
Pierre Tardy3d125942012-05-04 12:18:12 +02001848 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001849 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001850 if ffonly:
1851 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001852 if GitCommand(self, cmd).Wait() != 0:
1853 raise GitError('%s merge %s ' % (self.name, head))
1854
1855 def _InitGitDir(self):
1856 if not os.path.exists(self.gitdir):
1857 os.makedirs(self.gitdir)
1858 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001859
Shawn O. Pearce88443382010-10-08 10:02:09 +02001860 mp = self.manifest.manifestProject
1861 ref_dir = mp.config.GetString('repo.reference')
1862
1863 if ref_dir:
1864 mirror_git = os.path.join(ref_dir, self.name + '.git')
1865 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1866 self.relpath + '.git')
1867
1868 if os.path.exists(mirror_git):
1869 ref_dir = mirror_git
1870
1871 elif os.path.exists(repo_git):
1872 ref_dir = repo_git
1873
1874 else:
1875 ref_dir = None
1876
1877 if ref_dir:
1878 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1879 os.path.join(ref_dir, 'objects') + '\n')
1880
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001881 if self.manifest.IsMirror:
1882 self.config.SetString('core.bare', 'true')
1883 else:
1884 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001885
1886 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001887 try:
1888 to_rm = os.listdir(hooks)
1889 except OSError:
1890 to_rm = []
1891 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001892 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001893 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001894
1895 m = self.manifest.manifestProject.config
1896 for key in ['user.name', 'user.email']:
1897 if m.Has(key, include_defaults = False):
1898 self.config.SetString(key, m.GetString(key))
1899
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001900 def _InitHooks(self):
1901 hooks = self._gitdir_path('hooks')
1902 if not os.path.exists(hooks):
1903 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001904 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001905 name = os.path.basename(stock_hook)
1906
Victor Boivie65e0f352011-04-18 11:23:29 +02001907 if name in ('commit-msg',) and not self.remote.review \
1908 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001909 # Don't install a Gerrit Code Review hook if this
1910 # project does not appear to use it for reviews.
1911 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001912 # Since the manifest project is one of those, but also
1913 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001914 continue
1915
1916 dst = os.path.join(hooks, name)
1917 if os.path.islink(dst):
1918 continue
1919 if os.path.exists(dst):
1920 if filecmp.cmp(stock_hook, dst, shallow=False):
1921 os.remove(dst)
1922 else:
1923 _error("%s: Not replacing %s hook", self.relpath, name)
1924 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001925 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001926 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001927 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001928 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001929 raise GitError('filesystem must support symlinks')
1930 else:
1931 raise
1932
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001933 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001934 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001935 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001936 remote.url = self.remote.url
1937 remote.review = self.remote.review
1938 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001939
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001940 if self.worktree:
1941 remote.ResetFetch(mirror=False)
1942 else:
1943 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001944 remote.Save()
1945
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001946 def _InitMRef(self):
1947 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001948 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001949
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001950 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001951 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001952
1953 def _InitAnyMRef(self, ref):
1954 cur = self.bare_ref.symref(ref)
1955
1956 if self.revisionId:
1957 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1958 msg = 'manifest set to %s' % self.revisionId
1959 dst = self.revisionId + '^0'
1960 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1961 else:
1962 remote = self.GetRemote(self.remote.name)
1963 dst = remote.ToLocal(self.revisionExpr)
1964 if cur != dst:
1965 msg = 'manifest set to %s' % self.revisionExpr
1966 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001967
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001968 def _InitWorkTree(self):
1969 dotgit = os.path.join(self.worktree, '.git')
1970 if not os.path.exists(dotgit):
1971 os.makedirs(dotgit)
1972
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001973 for name in ['config',
1974 'description',
1975 'hooks',
1976 'info',
1977 'logs',
1978 'objects',
1979 'packed-refs',
1980 'refs',
1981 'rr-cache',
1982 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001983 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001984 src = os.path.join(self.gitdir, name)
1985 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001986 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001987 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001988 else:
1989 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001990 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001991 if e.errno == errno.EPERM:
1992 raise GitError('filesystem must support symlinks')
1993 else:
1994 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001995
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001996 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001997
1998 cmd = ['read-tree', '--reset', '-u']
1999 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002000 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002001 if GitCommand(self, cmd).Wait() != 0:
2002 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002003
2004 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2005 if not os.path.exists(rr_cache):
2006 os.makedirs(rr_cache)
2007
Shawn O. Pearce93609662009-04-21 10:50:33 -07002008 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002009
2010 def _gitdir_path(self, path):
2011 return os.path.join(self.gitdir, path)
2012
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002013 def _revlist(self, *args, **kw):
2014 a = []
2015 a.extend(args)
2016 a.append('--')
2017 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002018
2019 @property
2020 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002021 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002022
2023 class _GitGetByExec(object):
2024 def __init__(self, project, bare):
2025 self._project = project
2026 self._bare = bare
2027
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002028 def LsOthers(self):
2029 p = GitCommand(self._project,
2030 ['ls-files',
2031 '-z',
2032 '--others',
2033 '--exclude-standard'],
2034 bare = False,
2035 capture_stdout = True,
2036 capture_stderr = True)
2037 if p.Wait() == 0:
2038 out = p.stdout
2039 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002040 return out[:-1].split('\0') # pylint: disable=W1401
2041 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002042 return []
2043
2044 def DiffZ(self, name, *args):
2045 cmd = [name]
2046 cmd.append('-z')
2047 cmd.extend(args)
2048 p = GitCommand(self._project,
2049 cmd,
2050 bare = False,
2051 capture_stdout = True,
2052 capture_stderr = True)
2053 try:
2054 out = p.process.stdout.read()
2055 r = {}
2056 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002057 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002058 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002059 try:
2060 info = out.next()
2061 path = out.next()
2062 except StopIteration:
2063 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002064
2065 class _Info(object):
2066 def __init__(self, path, omode, nmode, oid, nid, state):
2067 self.path = path
2068 self.src_path = None
2069 self.old_mode = omode
2070 self.new_mode = nmode
2071 self.old_id = oid
2072 self.new_id = nid
2073
2074 if len(state) == 1:
2075 self.status = state
2076 self.level = None
2077 else:
2078 self.status = state[:1]
2079 self.level = state[1:]
2080 while self.level.startswith('0'):
2081 self.level = self.level[1:]
2082
2083 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002084 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002085 if info.status in ('R', 'C'):
2086 info.src_path = info.path
2087 info.path = out.next()
2088 r[info.path] = info
2089 return r
2090 finally:
2091 p.Wait()
2092
2093 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002094 if self._bare:
2095 path = os.path.join(self._project.gitdir, HEAD)
2096 else:
2097 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002098 try:
2099 fd = open(path, 'rb')
2100 except IOError:
2101 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002102 try:
2103 line = fd.read()
2104 finally:
2105 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302106 try:
2107 line = line.decode()
2108 except AttributeError:
2109 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002110 if line.startswith('ref: '):
2111 return line[5:-1]
2112 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002113
2114 def SetHead(self, ref, message=None):
2115 cmdv = []
2116 if message is not None:
2117 cmdv.extend(['-m', message])
2118 cmdv.append(HEAD)
2119 cmdv.append(ref)
2120 self.symbolic_ref(*cmdv)
2121
2122 def DetachHead(self, new, message=None):
2123 cmdv = ['--no-deref']
2124 if message is not None:
2125 cmdv.extend(['-m', message])
2126 cmdv.append(HEAD)
2127 cmdv.append(new)
2128 self.update_ref(*cmdv)
2129
2130 def UpdateRef(self, name, new, old=None,
2131 message=None,
2132 detach=False):
2133 cmdv = []
2134 if message is not None:
2135 cmdv.extend(['-m', message])
2136 if detach:
2137 cmdv.append('--no-deref')
2138 cmdv.append(name)
2139 cmdv.append(new)
2140 if old is not None:
2141 cmdv.append(old)
2142 self.update_ref(*cmdv)
2143
2144 def DeleteRef(self, name, old=None):
2145 if not old:
2146 old = self.rev_parse(name)
2147 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002148 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002149
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002150 def rev_list(self, *args, **kw):
2151 if 'format' in kw:
2152 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2153 else:
2154 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002155 cmdv.extend(args)
2156 p = GitCommand(self._project,
2157 cmdv,
2158 bare = self._bare,
2159 capture_stdout = True,
2160 capture_stderr = True)
2161 r = []
2162 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002163 if line[-1] == '\n':
2164 line = line[:-1]
2165 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002166 if p.Wait() != 0:
2167 raise GitError('%s rev-list %s: %s' % (
2168 self._project.name,
2169 str(args),
2170 p.stderr))
2171 return r
2172
2173 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002174 """Allow arbitrary git commands using pythonic syntax.
2175
2176 This allows you to do things like:
2177 git_obj.rev_parse('HEAD')
2178
2179 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2180 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002181 Any other positional arguments will be passed to the git command, and the
2182 following keyword arguments are supported:
2183 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002184
2185 Args:
2186 name: The name of the git command to call. Any '_' characters will
2187 be replaced with '-'.
2188
2189 Returns:
2190 A callable object that will try to call git with the named command.
2191 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002192 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002193 def runner(*args, **kwargs):
2194 cmdv = []
2195 config = kwargs.pop('config', None)
2196 for k in kwargs:
2197 raise TypeError('%s() got an unexpected keyword argument %r'
2198 % (name, k))
2199 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002200 if not git_require((1, 7, 2)):
2201 raise ValueError('cannot set config on command line for %s()'
2202 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302203 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002204 cmdv.append('-c')
2205 cmdv.append('%s=%s' % (k, v))
2206 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002207 cmdv.extend(args)
2208 p = GitCommand(self._project,
2209 cmdv,
2210 bare = self._bare,
2211 capture_stdout = True,
2212 capture_stderr = True)
2213 if p.Wait() != 0:
2214 raise GitError('%s %s: %s' % (
2215 self._project.name,
2216 name,
2217 p.stderr))
2218 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302219 try:
2220 r = r.decode()
2221 except AttributeError:
2222 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002223 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2224 return r[:-1]
2225 return r
2226 return runner
2227
2228
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002229class _PriorSyncFailedError(Exception):
2230 def __str__(self):
2231 return 'prior sync failed; rebase still in progress'
2232
2233class _DirtyError(Exception):
2234 def __str__(self):
2235 return 'contains uncommitted changes'
2236
2237class _InfoMessage(object):
2238 def __init__(self, project, text):
2239 self.project = project
2240 self.text = text
2241
2242 def Print(self, syncbuf):
2243 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2244 syncbuf.out.nl()
2245
2246class _Failure(object):
2247 def __init__(self, project, why):
2248 self.project = project
2249 self.why = why
2250
2251 def Print(self, syncbuf):
2252 syncbuf.out.fail('error: %s/: %s',
2253 self.project.relpath,
2254 str(self.why))
2255 syncbuf.out.nl()
2256
2257class _Later(object):
2258 def __init__(self, project, action):
2259 self.project = project
2260 self.action = action
2261
2262 def Run(self, syncbuf):
2263 out = syncbuf.out
2264 out.project('project %s/', self.project.relpath)
2265 out.nl()
2266 try:
2267 self.action()
2268 out.nl()
2269 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002270 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002271 out.nl()
2272 return False
2273
2274class _SyncColoring(Coloring):
2275 def __init__(self, config):
2276 Coloring.__init__(self, config, 'reposync')
2277 self.project = self.printer('header', attr = 'bold')
2278 self.info = self.printer('info')
2279 self.fail = self.printer('fail', fg='red')
2280
2281class SyncBuffer(object):
2282 def __init__(self, config, detach_head=False):
2283 self._messages = []
2284 self._failures = []
2285 self._later_queue1 = []
2286 self._later_queue2 = []
2287
2288 self.out = _SyncColoring(config)
2289 self.out.redirect(sys.stderr)
2290
2291 self.detach_head = detach_head
2292 self.clean = True
2293
2294 def info(self, project, fmt, *args):
2295 self._messages.append(_InfoMessage(project, fmt % args))
2296
2297 def fail(self, project, err=None):
2298 self._failures.append(_Failure(project, err))
2299 self.clean = False
2300
2301 def later1(self, project, what):
2302 self._later_queue1.append(_Later(project, what))
2303
2304 def later2(self, project, what):
2305 self._later_queue2.append(_Later(project, what))
2306
2307 def Finish(self):
2308 self._PrintMessages()
2309 self._RunLater()
2310 self._PrintMessages()
2311 return self.clean
2312
2313 def _RunLater(self):
2314 for q in ['_later_queue1', '_later_queue2']:
2315 if not self._RunQueue(q):
2316 return
2317
2318 def _RunQueue(self, queue):
2319 for m in getattr(self, queue):
2320 if not m.Run(self):
2321 self.clean = False
2322 return False
2323 setattr(self, queue, [])
2324 return True
2325
2326 def _PrintMessages(self):
2327 for m in self._messages:
2328 m.Print(self)
2329 for m in self._failures:
2330 m.Print(self)
2331
2332 self._messages = []
2333 self._failures = []
2334
2335
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002336class MetaProject(Project):
2337 """A special project housed under .repo.
2338 """
2339 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002340 Project.__init__(self,
2341 manifest = manifest,
2342 name = name,
2343 gitdir = gitdir,
2344 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002345 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002346 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002347 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002348 revisionId = None,
2349 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002350
2351 def PreSync(self):
2352 if self.Exists:
2353 cb = self.CurrentBranch
2354 if cb:
2355 base = self.GetBranch(cb).merge
2356 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002357 self.revisionExpr = base
2358 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002359
Florian Vallee5d016502012-06-07 17:19:26 +02002360 def MetaBranchSwitch(self, target):
2361 """ Prepare MetaProject for manifest branch switch
2362 """
2363
2364 # detach and delete manifest branch, allowing a new
2365 # branch to take over
2366 syncbuf = SyncBuffer(self.config, detach_head = True)
2367 self.Sync_LocalHalf(syncbuf)
2368 syncbuf.Finish()
2369
2370 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002371 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002372 capture_stdout = True,
2373 capture_stderr = True).Wait() == 0
2374
2375
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002376 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002377 def LastFetch(self):
2378 try:
2379 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2380 return os.path.getmtime(fh)
2381 except OSError:
2382 return 0
2383
2384 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002385 def HasChanges(self):
2386 """Has the remote received new commits not yet checked out?
2387 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002388 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002389 return False
2390
David Pursehouse8a68ff92012-09-24 12:15:13 +09002391 all_refs = self.bare_ref.all
2392 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002393 head = self.work_git.GetHead()
2394 if head.startswith(R_HEADS):
2395 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002396 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002397 except KeyError:
2398 head = None
2399
2400 if revid == head:
2401 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002402 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002403 return True
2404 return False