blob: feac5c011a97f42cc81fdbf6a3cf265c5b001258 [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
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001664 if no_tags:
1665 cmd.append('--no-tags')
1666 else:
1667 cmd.append('--tags')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301668 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001669 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001670 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001671 cmd.append(tag_name)
1672 else:
1673 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001674 if is_sha1:
1675 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001676 if branch.startswith(R_HEADS):
1677 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301678 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001679
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001680 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001681 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001682 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1683 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001684 ok = True
1685 break
Brian Harring14a66742012-09-28 20:21:57 -07001686 elif current_branch_only and is_sha1 and ret == 128:
1687 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1688 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1689 # abort the optimization attempt and do a full sync.
1690 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001691 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001692
1693 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001694 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001695 if old_packed != '':
1696 _lwrite(packed_refs, old_packed)
1697 else:
1698 os.remove(packed_refs)
1699 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001700
1701 if is_sha1 and current_branch_only and self.upstream:
1702 # We just synced the upstream given branch; verify we
1703 # got what we wanted, else trigger a second run of all
1704 # refs.
1705 if not CheckForSha1():
1706 return self._RemoteFetch(name=name, current_branch_only=False,
1707 initial=False, quiet=quiet, alt_dir=alt_dir)
1708
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001709 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001710
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001711 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001712 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001713 return False
1714
1715 remote = self.GetRemote(self.remote.name)
1716 bundle_url = remote.url + '/clone.bundle'
1717 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001718 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1719 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001720 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1721 return False
1722
1723 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1724 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1725
1726 exist_dst = os.path.exists(bundle_dst)
1727 exist_tmp = os.path.exists(bundle_tmp)
1728
1729 if not initial and not exist_dst and not exist_tmp:
1730 return False
1731
1732 if not exist_dst:
1733 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1734 if not exist_dst:
1735 return False
1736
1737 cmd = ['fetch']
1738 if quiet:
1739 cmd.append('--quiet')
1740 if not self.worktree:
1741 cmd.append('--update-head-ok')
1742 cmd.append(bundle_dst)
1743 for f in remote.fetch:
1744 cmd.append(str(f))
1745 cmd.append('refs/tags/*:refs/tags/*')
1746
1747 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001748 if os.path.exists(bundle_dst):
1749 os.remove(bundle_dst)
1750 if os.path.exists(bundle_tmp):
1751 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001752 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001753
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001754 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001755 if os.path.exists(dstPath):
1756 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001757
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001758 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001759 if quiet:
1760 cmd += ['--silent']
1761 if os.path.exists(tmpPath):
1762 size = os.stat(tmpPath).st_size
1763 if size >= 1024:
1764 cmd += ['--continue-at', '%d' % (size,)]
1765 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001766 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001767 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1768 cmd += ['--proxy', os.environ['http_proxy']]
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001769 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
1770 if cookiefile:
1771 cmd += ['--cookie', cookiefile]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001772 cmd += [srcUrl]
1773
1774 if IsTrace():
1775 Trace('%s', ' '.join(cmd))
1776 try:
1777 proc = subprocess.Popen(cmd)
1778 except OSError:
1779 return False
1780
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001781 curlret = proc.wait()
1782
1783 if curlret == 22:
1784 # From curl man page:
1785 # 22: HTTP page not retrieved. The requested url was not found or
1786 # returned another error with the HTTP error code being 400 or above.
1787 # This return code only appears if -f, --fail is used.
1788 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001789 print("Server does not provide clone.bundle; ignoring.",
1790 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001791 return False
1792
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001793 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001794 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001795 os.rename(tmpPath, dstPath)
1796 return True
1797 else:
1798 os.remove(tmpPath)
1799 return False
1800 else:
1801 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001802
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001803 def _Checkout(self, rev, quiet=False):
1804 cmd = ['checkout']
1805 if quiet:
1806 cmd.append('-q')
1807 cmd.append(rev)
1808 cmd.append('--')
1809 if GitCommand(self, cmd).Wait() != 0:
1810 if self._allrefs:
1811 raise GitError('%s checkout %s ' % (self.name, rev))
1812
Pierre Tardye5a21222011-03-24 16:28:18 +01001813 def _CherryPick(self, rev, quiet=False):
1814 cmd = ['cherry-pick']
1815 cmd.append(rev)
1816 cmd.append('--')
1817 if GitCommand(self, cmd).Wait() != 0:
1818 if self._allrefs:
1819 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1820
Erwan Mahea94f1622011-08-19 13:56:09 +02001821 def _Revert(self, rev, quiet=False):
1822 cmd = ['revert']
1823 cmd.append('--no-edit')
1824 cmd.append(rev)
1825 cmd.append('--')
1826 if GitCommand(self, cmd).Wait() != 0:
1827 if self._allrefs:
1828 raise GitError('%s revert %s ' % (self.name, rev))
1829
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001830 def _ResetHard(self, rev, quiet=True):
1831 cmd = ['reset', '--hard']
1832 if quiet:
1833 cmd.append('-q')
1834 cmd.append(rev)
1835 if GitCommand(self, cmd).Wait() != 0:
1836 raise GitError('%s reset --hard %s ' % (self.name, rev))
1837
1838 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001839 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001840 if onto is not None:
1841 cmd.extend(['--onto', onto])
1842 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001843 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001844 raise GitError('%s rebase %s ' % (self.name, upstream))
1845
Pierre Tardy3d125942012-05-04 12:18:12 +02001846 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001847 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001848 if ffonly:
1849 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001850 if GitCommand(self, cmd).Wait() != 0:
1851 raise GitError('%s merge %s ' % (self.name, head))
1852
1853 def _InitGitDir(self):
1854 if not os.path.exists(self.gitdir):
1855 os.makedirs(self.gitdir)
1856 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001857
Shawn O. Pearce88443382010-10-08 10:02:09 +02001858 mp = self.manifest.manifestProject
1859 ref_dir = mp.config.GetString('repo.reference')
1860
1861 if ref_dir:
1862 mirror_git = os.path.join(ref_dir, self.name + '.git')
1863 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1864 self.relpath + '.git')
1865
1866 if os.path.exists(mirror_git):
1867 ref_dir = mirror_git
1868
1869 elif os.path.exists(repo_git):
1870 ref_dir = repo_git
1871
1872 else:
1873 ref_dir = None
1874
1875 if ref_dir:
1876 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1877 os.path.join(ref_dir, 'objects') + '\n')
1878
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001879 if self.manifest.IsMirror:
1880 self.config.SetString('core.bare', 'true')
1881 else:
1882 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001883
1884 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001885 try:
1886 to_rm = os.listdir(hooks)
1887 except OSError:
1888 to_rm = []
1889 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001890 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001891 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001892
1893 m = self.manifest.manifestProject.config
1894 for key in ['user.name', 'user.email']:
1895 if m.Has(key, include_defaults = False):
1896 self.config.SetString(key, m.GetString(key))
1897
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001898 def _InitHooks(self):
1899 hooks = self._gitdir_path('hooks')
1900 if not os.path.exists(hooks):
1901 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001902 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001903 name = os.path.basename(stock_hook)
1904
Victor Boivie65e0f352011-04-18 11:23:29 +02001905 if name in ('commit-msg',) and not self.remote.review \
1906 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001907 # Don't install a Gerrit Code Review hook if this
1908 # project does not appear to use it for reviews.
1909 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001910 # Since the manifest project is one of those, but also
1911 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001912 continue
1913
1914 dst = os.path.join(hooks, name)
1915 if os.path.islink(dst):
1916 continue
1917 if os.path.exists(dst):
1918 if filecmp.cmp(stock_hook, dst, shallow=False):
1919 os.remove(dst)
1920 else:
1921 _error("%s: Not replacing %s hook", self.relpath, name)
1922 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001923 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001924 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001925 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001926 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001927 raise GitError('filesystem must support symlinks')
1928 else:
1929 raise
1930
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001931 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001932 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001933 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001934 remote.url = self.remote.url
1935 remote.review = self.remote.review
1936 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001937
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001938 if self.worktree:
1939 remote.ResetFetch(mirror=False)
1940 else:
1941 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001942 remote.Save()
1943
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001944 def _InitMRef(self):
1945 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001946 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001947
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001948 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001949 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001950
1951 def _InitAnyMRef(self, ref):
1952 cur = self.bare_ref.symref(ref)
1953
1954 if self.revisionId:
1955 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1956 msg = 'manifest set to %s' % self.revisionId
1957 dst = self.revisionId + '^0'
1958 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1959 else:
1960 remote = self.GetRemote(self.remote.name)
1961 dst = remote.ToLocal(self.revisionExpr)
1962 if cur != dst:
1963 msg = 'manifest set to %s' % self.revisionExpr
1964 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001965
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001966 def _InitWorkTree(self):
1967 dotgit = os.path.join(self.worktree, '.git')
1968 if not os.path.exists(dotgit):
1969 os.makedirs(dotgit)
1970
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001971 for name in ['config',
1972 'description',
1973 'hooks',
1974 'info',
1975 'logs',
1976 'objects',
1977 'packed-refs',
1978 'refs',
1979 'rr-cache',
1980 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001981 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001982 src = os.path.join(self.gitdir, name)
1983 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001984 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001985 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001986 else:
1987 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001988 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001989 if e.errno == errno.EPERM:
1990 raise GitError('filesystem must support symlinks')
1991 else:
1992 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001993
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001994 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001995
1996 cmd = ['read-tree', '--reset', '-u']
1997 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001998 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001999 if GitCommand(self, cmd).Wait() != 0:
2000 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002001
2002 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2003 if not os.path.exists(rr_cache):
2004 os.makedirs(rr_cache)
2005
Shawn O. Pearce93609662009-04-21 10:50:33 -07002006 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002007
2008 def _gitdir_path(self, path):
2009 return os.path.join(self.gitdir, path)
2010
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002011 def _revlist(self, *args, **kw):
2012 a = []
2013 a.extend(args)
2014 a.append('--')
2015 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002016
2017 @property
2018 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002019 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002020
2021 class _GitGetByExec(object):
2022 def __init__(self, project, bare):
2023 self._project = project
2024 self._bare = bare
2025
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002026 def LsOthers(self):
2027 p = GitCommand(self._project,
2028 ['ls-files',
2029 '-z',
2030 '--others',
2031 '--exclude-standard'],
2032 bare = False,
2033 capture_stdout = True,
2034 capture_stderr = True)
2035 if p.Wait() == 0:
2036 out = p.stdout
2037 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002038 return out[:-1].split('\0') # pylint: disable=W1401
2039 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002040 return []
2041
2042 def DiffZ(self, name, *args):
2043 cmd = [name]
2044 cmd.append('-z')
2045 cmd.extend(args)
2046 p = GitCommand(self._project,
2047 cmd,
2048 bare = False,
2049 capture_stdout = True,
2050 capture_stderr = True)
2051 try:
2052 out = p.process.stdout.read()
2053 r = {}
2054 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002055 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002056 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002057 try:
2058 info = out.next()
2059 path = out.next()
2060 except StopIteration:
2061 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002062
2063 class _Info(object):
2064 def __init__(self, path, omode, nmode, oid, nid, state):
2065 self.path = path
2066 self.src_path = None
2067 self.old_mode = omode
2068 self.new_mode = nmode
2069 self.old_id = oid
2070 self.new_id = nid
2071
2072 if len(state) == 1:
2073 self.status = state
2074 self.level = None
2075 else:
2076 self.status = state[:1]
2077 self.level = state[1:]
2078 while self.level.startswith('0'):
2079 self.level = self.level[1:]
2080
2081 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002082 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002083 if info.status in ('R', 'C'):
2084 info.src_path = info.path
2085 info.path = out.next()
2086 r[info.path] = info
2087 return r
2088 finally:
2089 p.Wait()
2090
2091 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002092 if self._bare:
2093 path = os.path.join(self._project.gitdir, HEAD)
2094 else:
2095 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002096 try:
2097 fd = open(path, 'rb')
2098 except IOError:
2099 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002100 try:
2101 line = fd.read()
2102 finally:
2103 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302104 try:
2105 line = line.decode()
2106 except AttributeError:
2107 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002108 if line.startswith('ref: '):
2109 return line[5:-1]
2110 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002111
2112 def SetHead(self, ref, message=None):
2113 cmdv = []
2114 if message is not None:
2115 cmdv.extend(['-m', message])
2116 cmdv.append(HEAD)
2117 cmdv.append(ref)
2118 self.symbolic_ref(*cmdv)
2119
2120 def DetachHead(self, new, message=None):
2121 cmdv = ['--no-deref']
2122 if message is not None:
2123 cmdv.extend(['-m', message])
2124 cmdv.append(HEAD)
2125 cmdv.append(new)
2126 self.update_ref(*cmdv)
2127
2128 def UpdateRef(self, name, new, old=None,
2129 message=None,
2130 detach=False):
2131 cmdv = []
2132 if message is not None:
2133 cmdv.extend(['-m', message])
2134 if detach:
2135 cmdv.append('--no-deref')
2136 cmdv.append(name)
2137 cmdv.append(new)
2138 if old is not None:
2139 cmdv.append(old)
2140 self.update_ref(*cmdv)
2141
2142 def DeleteRef(self, name, old=None):
2143 if not old:
2144 old = self.rev_parse(name)
2145 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002146 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002147
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002148 def rev_list(self, *args, **kw):
2149 if 'format' in kw:
2150 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2151 else:
2152 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002153 cmdv.extend(args)
2154 p = GitCommand(self._project,
2155 cmdv,
2156 bare = self._bare,
2157 capture_stdout = True,
2158 capture_stderr = True)
2159 r = []
2160 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002161 if line[-1] == '\n':
2162 line = line[:-1]
2163 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002164 if p.Wait() != 0:
2165 raise GitError('%s rev-list %s: %s' % (
2166 self._project.name,
2167 str(args),
2168 p.stderr))
2169 return r
2170
2171 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002172 """Allow arbitrary git commands using pythonic syntax.
2173
2174 This allows you to do things like:
2175 git_obj.rev_parse('HEAD')
2176
2177 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2178 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002179 Any other positional arguments will be passed to the git command, and the
2180 following keyword arguments are supported:
2181 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002182
2183 Args:
2184 name: The name of the git command to call. Any '_' characters will
2185 be replaced with '-'.
2186
2187 Returns:
2188 A callable object that will try to call git with the named command.
2189 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002190 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002191 def runner(*args, **kwargs):
2192 cmdv = []
2193 config = kwargs.pop('config', None)
2194 for k in kwargs:
2195 raise TypeError('%s() got an unexpected keyword argument %r'
2196 % (name, k))
2197 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002198 if not git_require((1, 7, 2)):
2199 raise ValueError('cannot set config on command line for %s()'
2200 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302201 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002202 cmdv.append('-c')
2203 cmdv.append('%s=%s' % (k, v))
2204 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002205 cmdv.extend(args)
2206 p = GitCommand(self._project,
2207 cmdv,
2208 bare = self._bare,
2209 capture_stdout = True,
2210 capture_stderr = True)
2211 if p.Wait() != 0:
2212 raise GitError('%s %s: %s' % (
2213 self._project.name,
2214 name,
2215 p.stderr))
2216 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302217 try:
2218 r = r.decode()
2219 except AttributeError:
2220 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002221 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2222 return r[:-1]
2223 return r
2224 return runner
2225
2226
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002227class _PriorSyncFailedError(Exception):
2228 def __str__(self):
2229 return 'prior sync failed; rebase still in progress'
2230
2231class _DirtyError(Exception):
2232 def __str__(self):
2233 return 'contains uncommitted changes'
2234
2235class _InfoMessage(object):
2236 def __init__(self, project, text):
2237 self.project = project
2238 self.text = text
2239
2240 def Print(self, syncbuf):
2241 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2242 syncbuf.out.nl()
2243
2244class _Failure(object):
2245 def __init__(self, project, why):
2246 self.project = project
2247 self.why = why
2248
2249 def Print(self, syncbuf):
2250 syncbuf.out.fail('error: %s/: %s',
2251 self.project.relpath,
2252 str(self.why))
2253 syncbuf.out.nl()
2254
2255class _Later(object):
2256 def __init__(self, project, action):
2257 self.project = project
2258 self.action = action
2259
2260 def Run(self, syncbuf):
2261 out = syncbuf.out
2262 out.project('project %s/', self.project.relpath)
2263 out.nl()
2264 try:
2265 self.action()
2266 out.nl()
2267 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002268 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002269 out.nl()
2270 return False
2271
2272class _SyncColoring(Coloring):
2273 def __init__(self, config):
2274 Coloring.__init__(self, config, 'reposync')
2275 self.project = self.printer('header', attr = 'bold')
2276 self.info = self.printer('info')
2277 self.fail = self.printer('fail', fg='red')
2278
2279class SyncBuffer(object):
2280 def __init__(self, config, detach_head=False):
2281 self._messages = []
2282 self._failures = []
2283 self._later_queue1 = []
2284 self._later_queue2 = []
2285
2286 self.out = _SyncColoring(config)
2287 self.out.redirect(sys.stderr)
2288
2289 self.detach_head = detach_head
2290 self.clean = True
2291
2292 def info(self, project, fmt, *args):
2293 self._messages.append(_InfoMessage(project, fmt % args))
2294
2295 def fail(self, project, err=None):
2296 self._failures.append(_Failure(project, err))
2297 self.clean = False
2298
2299 def later1(self, project, what):
2300 self._later_queue1.append(_Later(project, what))
2301
2302 def later2(self, project, what):
2303 self._later_queue2.append(_Later(project, what))
2304
2305 def Finish(self):
2306 self._PrintMessages()
2307 self._RunLater()
2308 self._PrintMessages()
2309 return self.clean
2310
2311 def _RunLater(self):
2312 for q in ['_later_queue1', '_later_queue2']:
2313 if not self._RunQueue(q):
2314 return
2315
2316 def _RunQueue(self, queue):
2317 for m in getattr(self, queue):
2318 if not m.Run(self):
2319 self.clean = False
2320 return False
2321 setattr(self, queue, [])
2322 return True
2323
2324 def _PrintMessages(self):
2325 for m in self._messages:
2326 m.Print(self)
2327 for m in self._failures:
2328 m.Print(self)
2329
2330 self._messages = []
2331 self._failures = []
2332
2333
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002334class MetaProject(Project):
2335 """A special project housed under .repo.
2336 """
2337 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002338 Project.__init__(self,
2339 manifest = manifest,
2340 name = name,
2341 gitdir = gitdir,
2342 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002343 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002344 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002345 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002346 revisionId = None,
2347 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002348
2349 def PreSync(self):
2350 if self.Exists:
2351 cb = self.CurrentBranch
2352 if cb:
2353 base = self.GetBranch(cb).merge
2354 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002355 self.revisionExpr = base
2356 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002357
Florian Vallee5d016502012-06-07 17:19:26 +02002358 def MetaBranchSwitch(self, target):
2359 """ Prepare MetaProject for manifest branch switch
2360 """
2361
2362 # detach and delete manifest branch, allowing a new
2363 # branch to take over
2364 syncbuf = SyncBuffer(self.config, detach_head = True)
2365 self.Sync_LocalHalf(syncbuf)
2366 syncbuf.Finish()
2367
2368 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002369 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002370 capture_stdout = True,
2371 capture_stderr = True).Wait() == 0
2372
2373
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002374 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002375 def LastFetch(self):
2376 try:
2377 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2378 return os.path.getmtime(fh)
2379 except OSError:
2380 return 0
2381
2382 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002383 def HasChanges(self):
2384 """Has the remote received new commits not yet checked out?
2385 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002386 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002387 return False
2388
David Pursehouse8a68ff92012-09-24 12:15:13 +09002389 all_refs = self.bare_ref.all
2390 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002391 head = self.work_git.GetHead()
2392 if head.startswith(R_HEADS):
2393 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002394 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002395 except KeyError:
2396 head = None
2397
2398 if revid == head:
2399 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002400 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002401 return True
2402 return False