blob: effe75c4e9cdfc9a510960b02f6ebb4f3648f244 [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()
Jimmie Westera0444582012-10-24 13:44:42 +0200989 else:
990 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -0700992
993 if is_new:
994 alt = os.path.join(self.gitdir, 'objects/info/alternates')
995 try:
996 fd = open(alt, 'rb')
997 try:
998 alt_dir = fd.readline().rstrip()
999 finally:
1000 fd.close()
1001 except IOError:
1002 alt_dir = None
1003 else:
1004 alt_dir = None
1005
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001006 if clone_bundle \
1007 and alt_dir is None \
1008 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001009 is_new = False
1010
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001011 if not current_branch_only:
1012 if self.sync_c:
1013 current_branch_only = True
1014 elif not self.manifest._loaded:
1015 # Manifest cannot check defaults until it syncs.
1016 current_branch_only = False
1017 elif self.manifest.default.sync_c:
1018 current_branch_only = True
1019
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001020 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001021 current_branch_only=current_branch_only,
1022 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001024
1025 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001026 self._InitMRef()
1027 else:
1028 self._InitMirrorHead()
1029 try:
1030 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1031 except OSError:
1032 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001033 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001034
1035 def PostRepoUpgrade(self):
1036 self._InitHooks()
1037
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001039 for copyfile in self.copyfiles:
1040 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041
David Pursehouse8a68ff92012-09-24 12:15:13 +09001042 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001043 if self.revisionId:
1044 return self.revisionId
1045
1046 rem = self.GetRemote(self.remote.name)
1047 rev = rem.ToLocal(self.revisionExpr)
1048
David Pursehouse8a68ff92012-09-24 12:15:13 +09001049 if all_refs is not None and rev in all_refs:
1050 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001051
1052 try:
1053 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1054 except GitError:
1055 raise ManifestInvalidRevisionError(
1056 'revision %s in %s not found' % (self.revisionExpr,
1057 self.name))
1058
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001059 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060 """Perform only the local IO portion of the sync process.
1061 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001063 all_refs = self.bare_ref.all
1064 self.CleanPublishedCache(all_refs)
1065 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001066
David Pursehouse1d947b32012-10-25 12:23:11 +09001067 def _doff():
1068 self._FastForward(revid)
1069 self._CopyFiles()
1070
Skyler Kaufman835cd682011-03-08 12:14:41 -08001071 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001072 head = self.work_git.GetHead()
1073 if head.startswith(R_HEADS):
1074 branch = head[len(R_HEADS):]
1075 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001076 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001077 except KeyError:
1078 head = None
1079 else:
1080 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001082 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083 # Currently on a detached HEAD. The user is assumed to
1084 # not have any local modifications worth worrying about.
1085 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001086 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001087 syncbuf.fail(self, _PriorSyncFailedError())
1088 return
1089
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001090 if head == revid:
1091 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001092 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001093 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001094 if not syncbuf.detach_head:
1095 return
1096 else:
1097 lost = self._revlist(not_rev(revid), HEAD)
1098 if lost:
1099 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001100
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001102 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001103 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001104 syncbuf.fail(self, e)
1105 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001107 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001109 if head == revid:
1110 # No changes; don't do anything further.
1111 #
1112 return
1113
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001114 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001115
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001116 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001118 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001120 syncbuf.info(self,
1121 "leaving %s; does not track upstream",
1122 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001123 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001124 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001125 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001126 syncbuf.fail(self, e)
1127 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001128 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001129 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001130
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001131 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001132 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001133 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001134 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135 if not_merged:
1136 if upstream_gain:
1137 # The user has published this branch and some of those
1138 # commits are not yet merged upstream. We do not want
1139 # to rewrite the published commits so we punt.
1140 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001141 syncbuf.fail(self,
1142 "branch %s is published (but not merged) and is now %d commits behind"
1143 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001144 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001145 elif pub == head:
1146 # All published commits are merged, and thus we are a
1147 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001148 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001149 syncbuf.later1(self, _doff)
1150 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001151
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001152 # Examine the local commits not in the remote. Find the
1153 # last one attributed to this user, if any.
1154 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001155 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001156 last_mine = None
1157 cnt_mine = 0
1158 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -08001159 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001160 if committer_email == self.UserEmail:
1161 last_mine = commit_id
1162 cnt_mine += 1
1163
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001164 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001165 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001166
1167 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001168 syncbuf.fail(self, _DirtyError())
1169 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001171 # If the upstream switched on us, warn the user.
1172 #
1173 if branch.merge != self.revisionExpr:
1174 if branch.merge and self.revisionExpr:
1175 syncbuf.info(self,
1176 'manifest switched %s...%s',
1177 branch.merge,
1178 self.revisionExpr)
1179 elif branch.merge:
1180 syncbuf.info(self,
1181 'manifest no longer tracks %s',
1182 branch.merge)
1183
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001184 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001185 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001186 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001188 syncbuf.info(self,
1189 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001190 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001191
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001192 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001193 if not ID_RE.match(self.revisionExpr):
1194 # in case of manifest sync the revisionExpr might be a SHA1
1195 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196 branch.Save()
1197
Mike Pontillod3153822012-02-28 11:53:24 -08001198 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001199 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001200 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001201 self._CopyFiles()
1202 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001203 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001205 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001206 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001207 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001208 syncbuf.fail(self, e)
1209 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001210 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001211 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001212
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001213 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001214 # dest should already be an absolute path, but src is project relative
1215 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001216 abssrc = os.path.join(self.worktree, src)
1217 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001218
James W. Mills24c13082012-04-12 15:04:13 -05001219 def AddAnnotation(self, name, value, keep):
1220 self.annotations.append(_Annotation(name, value, keep))
1221
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001222 def DownloadPatchSet(self, change_id, patch_id):
1223 """Download a single patch set of a single change to FETCH_HEAD.
1224 """
1225 remote = self.GetRemote(self.remote.name)
1226
1227 cmd = ['fetch', remote.name]
1228 cmd.append('refs/changes/%2.2d/%d/%d' \
1229 % (change_id % 100, change_id, patch_id))
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301230 cmd.extend(list(map(str, remote.fetch)))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001231 if GitCommand(self, cmd, bare=True).Wait() != 0:
1232 return None
1233 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001234 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001235 change_id,
1236 patch_id,
1237 self.bare_git.rev_parse('FETCH_HEAD'))
1238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001239
1240## Branch Management ##
1241
1242 def StartBranch(self, name):
1243 """Create a new branch off the manifest's revision.
1244 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001245 head = self.work_git.GetHead()
1246 if head == (R_HEADS + name):
1247 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001248
David Pursehouse8a68ff92012-09-24 12:15:13 +09001249 all_refs = self.bare_ref.all
1250 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001251 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001252 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001253 capture_stdout = True,
1254 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001255
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001256 branch = self.GetBranch(name)
1257 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001258 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001259 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001260
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001261 if head.startswith(R_HEADS):
1262 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001263 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001264 except KeyError:
1265 head = None
1266
1267 if revid and head and revid == head:
1268 ref = os.path.join(self.gitdir, R_HEADS + name)
1269 try:
1270 os.makedirs(os.path.dirname(ref))
1271 except OSError:
1272 pass
1273 _lwrite(ref, '%s\n' % revid)
1274 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1275 'ref: %s%s\n' % (R_HEADS, name))
1276 branch.Save()
1277 return True
1278
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001279 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001280 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001281 capture_stdout = True,
1282 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001283 branch.Save()
1284 return True
1285 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286
Wink Saville02d79452009-04-10 13:01:24 -07001287 def CheckoutBranch(self, name):
1288 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001289
1290 Args:
1291 name: The name of the branch to checkout.
1292
1293 Returns:
1294 True if the checkout succeeded; False if it didn't; None if the branch
1295 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001296 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001297 rev = R_HEADS + name
1298 head = self.work_git.GetHead()
1299 if head == rev:
1300 # Already on the branch
1301 #
1302 return True
Wink Saville02d79452009-04-10 13:01:24 -07001303
David Pursehouse8a68ff92012-09-24 12:15:13 +09001304 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001305 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001306 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001307 except KeyError:
1308 # Branch does not exist in this project
1309 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001310 return None
Wink Saville02d79452009-04-10 13:01:24 -07001311
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001312 if head.startswith(R_HEADS):
1313 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001314 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001315 except KeyError:
1316 head = None
1317
1318 if head == revid:
1319 # Same revision; just update HEAD to point to the new
1320 # target branch, but otherwise take no other action.
1321 #
1322 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1323 'ref: %s%s\n' % (R_HEADS, name))
1324 return True
1325
1326 return GitCommand(self,
1327 ['checkout', name, '--'],
1328 capture_stdout = True,
1329 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001330
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001331 def AbandonBranch(self, name):
1332 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001333
1334 Args:
1335 name: The name of the branch to abandon.
1336
1337 Returns:
1338 True if the abandon succeeded; False if it didn't; None if the branch
1339 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001340 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001341 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001342 all_refs = self.bare_ref.all
1343 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001344 # Doesn't exist
1345 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001346
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001347 head = self.work_git.GetHead()
1348 if head == rev:
1349 # We can't destroy the branch while we are sitting
1350 # on it. Switch to a detached HEAD.
1351 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001352 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001353
David Pursehouse8a68ff92012-09-24 12:15:13 +09001354 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001355 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001356 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1357 '%s\n' % revid)
1358 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001359 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001360
1361 return GitCommand(self,
1362 ['branch', '-D', name],
1363 capture_stdout = True,
1364 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001365
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001366 def PruneHeads(self):
1367 """Prune any topic branches already merged into upstream.
1368 """
1369 cb = self.CurrentBranch
1370 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001371 left = self._allrefs
1372 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373 if name.startswith(R_HEADS):
1374 name = name[len(R_HEADS):]
1375 if cb is None or name != cb:
1376 kill.append(name)
1377
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001378 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001379 if cb is not None \
1380 and not self._revlist(HEAD + '...' + rev) \
1381 and not self.IsDirty(consider_untracked = False):
1382 self.work_git.DetachHead(HEAD)
1383 kill.append(cb)
1384
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001386 old = self.bare_git.GetHead()
1387 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001388 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1389
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001390 try:
1391 self.bare_git.DetachHead(rev)
1392
1393 b = ['branch', '-d']
1394 b.extend(kill)
1395 b = GitCommand(self, b, bare=True,
1396 capture_stdout=True,
1397 capture_stderr=True)
1398 b.Wait()
1399 finally:
1400 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001401 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001402
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001403 for branch in kill:
1404 if (R_HEADS + branch) not in left:
1405 self.CleanPublishedCache()
1406 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407
1408 if cb and cb not in kill:
1409 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001410 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001411
1412 kept = []
1413 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001414 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001415 branch = self.GetBranch(branch)
1416 base = branch.LocalMerge
1417 if not base:
1418 base = rev
1419 kept.append(ReviewableBranch(self, branch, base))
1420 return kept
1421
1422
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001423## Submodule Management ##
1424
1425 def GetRegisteredSubprojects(self):
1426 result = []
1427 def rec(subprojects):
1428 if not subprojects:
1429 return
1430 result.extend(subprojects)
1431 for p in subprojects:
1432 rec(p.subprojects)
1433 rec(self.subprojects)
1434 return result
1435
1436 def _GetSubmodules(self):
1437 # Unfortunately we cannot call `git submodule status --recursive` here
1438 # because the working tree might not exist yet, and it cannot be used
1439 # without a working tree in its current implementation.
1440
1441 def get_submodules(gitdir, rev):
1442 # Parse .gitmodules for submodule sub_paths and sub_urls
1443 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1444 if not sub_paths:
1445 return []
1446 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1447 # revision of submodule repository
1448 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1449 submodules = []
1450 for sub_path, sub_url in zip(sub_paths, sub_urls):
1451 try:
1452 sub_rev = sub_revs[sub_path]
1453 except KeyError:
1454 # Ignore non-exist submodules
1455 continue
1456 submodules.append((sub_rev, sub_path, sub_url))
1457 return submodules
1458
1459 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1460 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1461 def parse_gitmodules(gitdir, rev):
1462 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1463 try:
1464 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1465 bare = True, gitdir = gitdir)
1466 except GitError:
1467 return [], []
1468 if p.Wait() != 0:
1469 return [], []
1470
1471 gitmodules_lines = []
1472 fd, temp_gitmodules_path = tempfile.mkstemp()
1473 try:
1474 os.write(fd, p.stdout)
1475 os.close(fd)
1476 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1477 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1478 bare = True, gitdir = gitdir)
1479 if p.Wait() != 0:
1480 return [], []
1481 gitmodules_lines = p.stdout.split('\n')
1482 except GitError:
1483 return [], []
1484 finally:
1485 os.remove(temp_gitmodules_path)
1486
1487 names = set()
1488 paths = {}
1489 urls = {}
1490 for line in gitmodules_lines:
1491 if not line:
1492 continue
1493 m = re_path.match(line)
1494 if m:
1495 names.add(m.group(1))
1496 paths[m.group(1)] = m.group(2)
1497 continue
1498 m = re_url.match(line)
1499 if m:
1500 names.add(m.group(1))
1501 urls[m.group(1)] = m.group(2)
1502 continue
1503 names = sorted(names)
1504 return ([paths.get(name, '') for name in names],
1505 [urls.get(name, '') for name in names])
1506
1507 def git_ls_tree(gitdir, rev, paths):
1508 cmd = ['ls-tree', rev, '--']
1509 cmd.extend(paths)
1510 try:
1511 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1512 bare = True, gitdir = gitdir)
1513 except GitError:
1514 return []
1515 if p.Wait() != 0:
1516 return []
1517 objects = {}
1518 for line in p.stdout.split('\n'):
1519 if not line.strip():
1520 continue
1521 object_rev, object_path = line.split()[2:4]
1522 objects[object_path] = object_rev
1523 return objects
1524
1525 try:
1526 rev = self.GetRevisionId()
1527 except GitError:
1528 return []
1529 return get_submodules(self.gitdir, rev)
1530
1531 def GetDerivedSubprojects(self):
1532 result = []
1533 if not self.Exists:
1534 # If git repo does not exist yet, querying its submodules will
1535 # mess up its states; so return here.
1536 return result
1537 for rev, path, url in self._GetSubmodules():
1538 name = self.manifest.GetSubprojectName(self, path)
1539 project = self.manifest.projects.get(name)
1540 if project:
1541 result.extend(project.GetDerivedSubprojects())
1542 continue
1543 relpath, worktree, gitdir = self.manifest.GetSubprojectPaths(self, path)
1544 remote = RemoteSpec(self.remote.name,
1545 url = url,
1546 review = self.remote.review)
1547 subproject = Project(manifest = self.manifest,
1548 name = name,
1549 remote = remote,
1550 gitdir = gitdir,
1551 worktree = worktree,
1552 relpath = relpath,
1553 revisionExpr = self.revisionExpr,
1554 revisionId = rev,
1555 rebase = self.rebase,
1556 groups = self.groups,
1557 sync_c = self.sync_c,
1558 sync_s = self.sync_s,
1559 parent = self,
1560 is_derived = True)
1561 result.append(subproject)
1562 result.extend(subproject.GetDerivedSubprojects())
1563 return result
1564
1565
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001566## Direct Git Commands ##
1567
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001568 def _RemoteFetch(self, name=None,
1569 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001570 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001571 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001572 alt_dir=None,
1573 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001574
1575 is_sha1 = False
1576 tag_name = None
1577
Brian Harring14a66742012-09-28 20:21:57 -07001578 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001579 try:
1580 # if revision (sha or tag) is not present then following function
1581 # throws an error.
1582 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1583 return True
1584 except GitError:
1585 # There is no such persistent revision. We have to fetch it.
1586 return False
Brian Harring14a66742012-09-28 20:21:57 -07001587
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001588 if current_branch_only:
1589 if ID_RE.match(self.revisionExpr) is not None:
1590 is_sha1 = True
1591 elif self.revisionExpr.startswith(R_TAGS):
1592 # this is a tag and its sha1 value should never change
1593 tag_name = self.revisionExpr[len(R_TAGS):]
1594
1595 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001596 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001597 return True
Brian Harring14a66742012-09-28 20:21:57 -07001598 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1599 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001600
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001601 if not name:
1602 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001603
1604 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001605 remote = self.GetRemote(name)
1606 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001607 ssh_proxy = True
1608
Shawn O. Pearce88443382010-10-08 10:02:09 +02001609 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001610 if alt_dir and 'objects' == os.path.basename(alt_dir):
1611 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001612 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1613 remote = self.GetRemote(name)
1614
David Pursehouse8a68ff92012-09-24 12:15:13 +09001615 all_refs = self.bare_ref.all
1616 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001617 tmp = set()
1618
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301619 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001620 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001621 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001622 all_refs[r] = ref_id
1623 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001624 continue
1625
David Pursehouse8a68ff92012-09-24 12:15:13 +09001626 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001627 continue
1628
David Pursehouse8a68ff92012-09-24 12:15:13 +09001629 r = 'refs/_alt/%s' % ref_id
1630 all_refs[r] = ref_id
1631 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001632 tmp.add(r)
1633
Shawn O. Pearce88443382010-10-08 10:02:09 +02001634 tmp_packed = ''
1635 old_packed = ''
1636
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301637 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001638 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001639 tmp_packed += line
1640 if r not in tmp:
1641 old_packed += line
1642
1643 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001644 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001645 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001646
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001647 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001648
1649 # The --depth option only affects the initial fetch; after that we'll do
1650 # full fetches of changes.
David Pursehouseede7f122012-11-27 22:25:30 +09001651 if self.clone_depth:
1652 depth = self.clone_depth
1653 else:
1654 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Doug Anderson30d45292011-05-04 15:01:04 -07001655 if depth and initial:
1656 cmd.append('--depth=%s' % depth)
1657
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001658 if quiet:
1659 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001660 if not self.worktree:
1661 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001662 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001663
Brian Harring14a66742012-09-28 20:21:57 -07001664 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001665 # Fetch whole repo
Jimmie Wester2f992cb2012-12-07 12:49:51 +01001666 # If using depth then we should not get all the tags since they may
1667 # be outside of the depth.
1668 if no_tags or depth:
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001669 cmd.append('--no-tags')
1670 else:
1671 cmd.append('--tags')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301672 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001673 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001674 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001675 cmd.append(tag_name)
1676 else:
1677 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001678 if is_sha1:
1679 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001680 if branch.startswith(R_HEADS):
1681 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301682 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001683
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001684 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001685 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001686 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1687 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001688 ok = True
1689 break
Brian Harring14a66742012-09-28 20:21:57 -07001690 elif current_branch_only and is_sha1 and ret == 128:
1691 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1692 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1693 # abort the optimization attempt and do a full sync.
1694 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001695 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001696
1697 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001698 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001699 if old_packed != '':
1700 _lwrite(packed_refs, old_packed)
1701 else:
1702 os.remove(packed_refs)
1703 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001704
1705 if is_sha1 and current_branch_only and self.upstream:
1706 # We just synced the upstream given branch; verify we
1707 # got what we wanted, else trigger a second run of all
1708 # refs.
1709 if not CheckForSha1():
1710 return self._RemoteFetch(name=name, current_branch_only=False,
1711 initial=False, quiet=quiet, alt_dir=alt_dir)
1712
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001713 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001714
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001715 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001716 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001717 return False
1718
1719 remote = self.GetRemote(self.remote.name)
1720 bundle_url = remote.url + '/clone.bundle'
1721 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Shawn O. Pearce898e12a2012-03-14 15:22:28 -07001722 if GetSchemeFromUrl(bundle_url) in ('persistent-http', 'persistent-https'):
1723 bundle_url = bundle_url[len('persistent-'):]
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001724 if GetSchemeFromUrl(bundle_url) not in ('http', 'https'):
1725 return False
1726
1727 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1728 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1729
1730 exist_dst = os.path.exists(bundle_dst)
1731 exist_tmp = os.path.exists(bundle_tmp)
1732
1733 if not initial and not exist_dst and not exist_tmp:
1734 return False
1735
1736 if not exist_dst:
1737 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1738 if not exist_dst:
1739 return False
1740
1741 cmd = ['fetch']
1742 if quiet:
1743 cmd.append('--quiet')
1744 if not self.worktree:
1745 cmd.append('--update-head-ok')
1746 cmd.append(bundle_dst)
1747 for f in remote.fetch:
1748 cmd.append(str(f))
1749 cmd.append('refs/tags/*:refs/tags/*')
1750
1751 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001752 if os.path.exists(bundle_dst):
1753 os.remove(bundle_dst)
1754 if os.path.exists(bundle_tmp):
1755 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001756 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001757
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001758 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001759 if os.path.exists(dstPath):
1760 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001761
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001762 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001763 if quiet:
1764 cmd += ['--silent']
1765 if os.path.exists(tmpPath):
1766 size = os.stat(tmpPath).st_size
1767 if size >= 1024:
1768 cmd += ['--continue-at', '%d' % (size,)]
1769 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001770 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001771 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1772 cmd += ['--proxy', os.environ['http_proxy']]
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001773 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
1774 if cookiefile:
1775 cmd += ['--cookie', cookiefile]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001776 cmd += [srcUrl]
1777
1778 if IsTrace():
1779 Trace('%s', ' '.join(cmd))
1780 try:
1781 proc = subprocess.Popen(cmd)
1782 except OSError:
1783 return False
1784
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001785 curlret = proc.wait()
1786
1787 if curlret == 22:
1788 # From curl man page:
1789 # 22: HTTP page not retrieved. The requested url was not found or
1790 # returned another error with the HTTP error code being 400 or above.
1791 # This return code only appears if -f, --fail is used.
1792 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001793 print("Server does not provide clone.bundle; ignoring.",
1794 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001795 return False
1796
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001797 if os.path.exists(tmpPath):
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001798 if curlret == 0 and os.stat(tmpPath).st_size > 16:
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001799 os.rename(tmpPath, dstPath)
1800 return True
1801 else:
1802 os.remove(tmpPath)
1803 return False
1804 else:
1805 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001806
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001807 def _Checkout(self, rev, quiet=False):
1808 cmd = ['checkout']
1809 if quiet:
1810 cmd.append('-q')
1811 cmd.append(rev)
1812 cmd.append('--')
1813 if GitCommand(self, cmd).Wait() != 0:
1814 if self._allrefs:
1815 raise GitError('%s checkout %s ' % (self.name, rev))
1816
Pierre Tardye5a21222011-03-24 16:28:18 +01001817 def _CherryPick(self, rev, quiet=False):
1818 cmd = ['cherry-pick']
1819 cmd.append(rev)
1820 cmd.append('--')
1821 if GitCommand(self, cmd).Wait() != 0:
1822 if self._allrefs:
1823 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1824
Erwan Mahea94f1622011-08-19 13:56:09 +02001825 def _Revert(self, rev, quiet=False):
1826 cmd = ['revert']
1827 cmd.append('--no-edit')
1828 cmd.append(rev)
1829 cmd.append('--')
1830 if GitCommand(self, cmd).Wait() != 0:
1831 if self._allrefs:
1832 raise GitError('%s revert %s ' % (self.name, rev))
1833
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001834 def _ResetHard(self, rev, quiet=True):
1835 cmd = ['reset', '--hard']
1836 if quiet:
1837 cmd.append('-q')
1838 cmd.append(rev)
1839 if GitCommand(self, cmd).Wait() != 0:
1840 raise GitError('%s reset --hard %s ' % (self.name, rev))
1841
1842 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001843 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001844 if onto is not None:
1845 cmd.extend(['--onto', onto])
1846 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001847 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001848 raise GitError('%s rebase %s ' % (self.name, upstream))
1849
Pierre Tardy3d125942012-05-04 12:18:12 +02001850 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001851 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001852 if ffonly:
1853 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001854 if GitCommand(self, cmd).Wait() != 0:
1855 raise GitError('%s merge %s ' % (self.name, head))
1856
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001857 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001858 if not os.path.exists(self.gitdir):
1859 os.makedirs(self.gitdir)
1860 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001861
Shawn O. Pearce88443382010-10-08 10:02:09 +02001862 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001863 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02001864
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001865 if ref_dir or mirror_git:
1866 if not mirror_git:
1867 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001868 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1869 self.relpath + '.git')
1870
1871 if os.path.exists(mirror_git):
1872 ref_dir = mirror_git
1873
1874 elif os.path.exists(repo_git):
1875 ref_dir = repo_git
1876
1877 else:
1878 ref_dir = None
1879
1880 if ref_dir:
1881 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1882 os.path.join(ref_dir, 'objects') + '\n')
1883
Jimmie Westera0444582012-10-24 13:44:42 +02001884 self._UpdateHooks()
1885
1886 m = self.manifest.manifestProject.config
1887 for key in ['user.name', 'user.email']:
1888 if m.Has(key, include_defaults = False):
1889 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001890 if self.manifest.IsMirror:
1891 self.config.SetString('core.bare', 'true')
1892 else:
1893 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001894
Jimmie Westera0444582012-10-24 13:44:42 +02001895 def _UpdateHooks(self):
1896 if os.path.exists(self.gitdir):
1897 # Always recreate hooks since they can have been changed
1898 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001899 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001900 try:
1901 to_rm = os.listdir(hooks)
1902 except OSError:
1903 to_rm = []
1904 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001905 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001906 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001907
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001908 def _InitHooks(self):
1909 hooks = self._gitdir_path('hooks')
1910 if not os.path.exists(hooks):
1911 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08001912 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001913 name = os.path.basename(stock_hook)
1914
Victor Boivie65e0f352011-04-18 11:23:29 +02001915 if name in ('commit-msg',) and not self.remote.review \
1916 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001917 # Don't install a Gerrit Code Review hook if this
1918 # project does not appear to use it for reviews.
1919 #
Victor Boivie65e0f352011-04-18 11:23:29 +02001920 # Since the manifest project is one of those, but also
1921 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001922 continue
1923
1924 dst = os.path.join(hooks, name)
1925 if os.path.islink(dst):
1926 continue
1927 if os.path.exists(dst):
1928 if filecmp.cmp(stock_hook, dst, shallow=False):
1929 os.remove(dst)
1930 else:
1931 _error("%s: Not replacing %s hook", self.relpath, name)
1932 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001933 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001934 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001935 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001936 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001937 raise GitError('filesystem must support symlinks')
1938 else:
1939 raise
1940
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001941 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001942 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001943 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001944 remote.url = self.remote.url
1945 remote.review = self.remote.review
1946 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001947
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001948 if self.worktree:
1949 remote.ResetFetch(mirror=False)
1950 else:
1951 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001952 remote.Save()
1953
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001954 def _InitMRef(self):
1955 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001956 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001957
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001958 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001959 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001960
1961 def _InitAnyMRef(self, ref):
1962 cur = self.bare_ref.symref(ref)
1963
1964 if self.revisionId:
1965 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1966 msg = 'manifest set to %s' % self.revisionId
1967 dst = self.revisionId + '^0'
1968 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1969 else:
1970 remote = self.GetRemote(self.remote.name)
1971 dst = remote.ToLocal(self.revisionExpr)
1972 if cur != dst:
1973 msg = 'manifest set to %s' % self.revisionExpr
1974 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001975
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001976 def _InitWorkTree(self):
1977 dotgit = os.path.join(self.worktree, '.git')
1978 if not os.path.exists(dotgit):
1979 os.makedirs(dotgit)
1980
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001981 for name in ['config',
1982 'description',
1983 'hooks',
1984 'info',
1985 'logs',
1986 'objects',
1987 'packed-refs',
1988 'refs',
1989 'rr-cache',
1990 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001991 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001992 src = os.path.join(self.gitdir, name)
1993 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001994 if os.path.islink(dst) or not os.path.exists(dst):
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02001995 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001996 else:
1997 raise GitError('cannot overwrite a local work tree')
Sarah Owensa5be53f2012-09-09 15:37:57 -07001998 except OSError as e:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001999 if e.errno == errno.EPERM:
2000 raise GitError('filesystem must support symlinks')
2001 else:
2002 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002003
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002004 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002005
2006 cmd = ['read-tree', '--reset', '-u']
2007 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002008 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002009 if GitCommand(self, cmd).Wait() != 0:
2010 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002011
2012 rr_cache = os.path.join(self.gitdir, 'rr-cache')
2013 if not os.path.exists(rr_cache):
2014 os.makedirs(rr_cache)
2015
Shawn O. Pearce93609662009-04-21 10:50:33 -07002016 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002017
2018 def _gitdir_path(self, path):
2019 return os.path.join(self.gitdir, path)
2020
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002021 def _revlist(self, *args, **kw):
2022 a = []
2023 a.extend(args)
2024 a.append('--')
2025 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002026
2027 @property
2028 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002029 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002030
2031 class _GitGetByExec(object):
2032 def __init__(self, project, bare):
2033 self._project = project
2034 self._bare = bare
2035
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002036 def LsOthers(self):
2037 p = GitCommand(self._project,
2038 ['ls-files',
2039 '-z',
2040 '--others',
2041 '--exclude-standard'],
2042 bare = False,
2043 capture_stdout = True,
2044 capture_stderr = True)
2045 if p.Wait() == 0:
2046 out = p.stdout
2047 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002048 return out[:-1].split('\0') # pylint: disable=W1401
2049 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002050 return []
2051
2052 def DiffZ(self, name, *args):
2053 cmd = [name]
2054 cmd.append('-z')
2055 cmd.extend(args)
2056 p = GitCommand(self._project,
2057 cmd,
2058 bare = False,
2059 capture_stdout = True,
2060 capture_stderr = True)
2061 try:
2062 out = p.process.stdout.read()
2063 r = {}
2064 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002065 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002066 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002067 try:
2068 info = out.next()
2069 path = out.next()
2070 except StopIteration:
2071 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002072
2073 class _Info(object):
2074 def __init__(self, path, omode, nmode, oid, nid, state):
2075 self.path = path
2076 self.src_path = None
2077 self.old_mode = omode
2078 self.new_mode = nmode
2079 self.old_id = oid
2080 self.new_id = nid
2081
2082 if len(state) == 1:
2083 self.status = state
2084 self.level = None
2085 else:
2086 self.status = state[:1]
2087 self.level = state[1:]
2088 while self.level.startswith('0'):
2089 self.level = self.level[1:]
2090
2091 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002092 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002093 if info.status in ('R', 'C'):
2094 info.src_path = info.path
2095 info.path = out.next()
2096 r[info.path] = info
2097 return r
2098 finally:
2099 p.Wait()
2100
2101 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002102 if self._bare:
2103 path = os.path.join(self._project.gitdir, HEAD)
2104 else:
2105 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002106 try:
2107 fd = open(path, 'rb')
2108 except IOError:
2109 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002110 try:
2111 line = fd.read()
2112 finally:
2113 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302114 try:
2115 line = line.decode()
2116 except AttributeError:
2117 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002118 if line.startswith('ref: '):
2119 return line[5:-1]
2120 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002121
2122 def SetHead(self, ref, message=None):
2123 cmdv = []
2124 if message is not None:
2125 cmdv.extend(['-m', message])
2126 cmdv.append(HEAD)
2127 cmdv.append(ref)
2128 self.symbolic_ref(*cmdv)
2129
2130 def DetachHead(self, new, message=None):
2131 cmdv = ['--no-deref']
2132 if message is not None:
2133 cmdv.extend(['-m', message])
2134 cmdv.append(HEAD)
2135 cmdv.append(new)
2136 self.update_ref(*cmdv)
2137
2138 def UpdateRef(self, name, new, old=None,
2139 message=None,
2140 detach=False):
2141 cmdv = []
2142 if message is not None:
2143 cmdv.extend(['-m', message])
2144 if detach:
2145 cmdv.append('--no-deref')
2146 cmdv.append(name)
2147 cmdv.append(new)
2148 if old is not None:
2149 cmdv.append(old)
2150 self.update_ref(*cmdv)
2151
2152 def DeleteRef(self, name, old=None):
2153 if not old:
2154 old = self.rev_parse(name)
2155 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002156 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002157
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002158 def rev_list(self, *args, **kw):
2159 if 'format' in kw:
2160 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2161 else:
2162 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002163 cmdv.extend(args)
2164 p = GitCommand(self._project,
2165 cmdv,
2166 bare = self._bare,
2167 capture_stdout = True,
2168 capture_stderr = True)
2169 r = []
2170 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002171 if line[-1] == '\n':
2172 line = line[:-1]
2173 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002174 if p.Wait() != 0:
2175 raise GitError('%s rev-list %s: %s' % (
2176 self._project.name,
2177 str(args),
2178 p.stderr))
2179 return r
2180
2181 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002182 """Allow arbitrary git commands using pythonic syntax.
2183
2184 This allows you to do things like:
2185 git_obj.rev_parse('HEAD')
2186
2187 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2188 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002189 Any other positional arguments will be passed to the git command, and the
2190 following keyword arguments are supported:
2191 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002192
2193 Args:
2194 name: The name of the git command to call. Any '_' characters will
2195 be replaced with '-'.
2196
2197 Returns:
2198 A callable object that will try to call git with the named command.
2199 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002200 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002201 def runner(*args, **kwargs):
2202 cmdv = []
2203 config = kwargs.pop('config', None)
2204 for k in kwargs:
2205 raise TypeError('%s() got an unexpected keyword argument %r'
2206 % (name, k))
2207 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002208 if not git_require((1, 7, 2)):
2209 raise ValueError('cannot set config on command line for %s()'
2210 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302211 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002212 cmdv.append('-c')
2213 cmdv.append('%s=%s' % (k, v))
2214 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002215 cmdv.extend(args)
2216 p = GitCommand(self._project,
2217 cmdv,
2218 bare = self._bare,
2219 capture_stdout = True,
2220 capture_stderr = True)
2221 if p.Wait() != 0:
2222 raise GitError('%s %s: %s' % (
2223 self._project.name,
2224 name,
2225 p.stderr))
2226 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302227 try:
2228 r = r.decode()
2229 except AttributeError:
2230 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002231 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2232 return r[:-1]
2233 return r
2234 return runner
2235
2236
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002237class _PriorSyncFailedError(Exception):
2238 def __str__(self):
2239 return 'prior sync failed; rebase still in progress'
2240
2241class _DirtyError(Exception):
2242 def __str__(self):
2243 return 'contains uncommitted changes'
2244
2245class _InfoMessage(object):
2246 def __init__(self, project, text):
2247 self.project = project
2248 self.text = text
2249
2250 def Print(self, syncbuf):
2251 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2252 syncbuf.out.nl()
2253
2254class _Failure(object):
2255 def __init__(self, project, why):
2256 self.project = project
2257 self.why = why
2258
2259 def Print(self, syncbuf):
2260 syncbuf.out.fail('error: %s/: %s',
2261 self.project.relpath,
2262 str(self.why))
2263 syncbuf.out.nl()
2264
2265class _Later(object):
2266 def __init__(self, project, action):
2267 self.project = project
2268 self.action = action
2269
2270 def Run(self, syncbuf):
2271 out = syncbuf.out
2272 out.project('project %s/', self.project.relpath)
2273 out.nl()
2274 try:
2275 self.action()
2276 out.nl()
2277 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002278 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002279 out.nl()
2280 return False
2281
2282class _SyncColoring(Coloring):
2283 def __init__(self, config):
2284 Coloring.__init__(self, config, 'reposync')
2285 self.project = self.printer('header', attr = 'bold')
2286 self.info = self.printer('info')
2287 self.fail = self.printer('fail', fg='red')
2288
2289class SyncBuffer(object):
2290 def __init__(self, config, detach_head=False):
2291 self._messages = []
2292 self._failures = []
2293 self._later_queue1 = []
2294 self._later_queue2 = []
2295
2296 self.out = _SyncColoring(config)
2297 self.out.redirect(sys.stderr)
2298
2299 self.detach_head = detach_head
2300 self.clean = True
2301
2302 def info(self, project, fmt, *args):
2303 self._messages.append(_InfoMessage(project, fmt % args))
2304
2305 def fail(self, project, err=None):
2306 self._failures.append(_Failure(project, err))
2307 self.clean = False
2308
2309 def later1(self, project, what):
2310 self._later_queue1.append(_Later(project, what))
2311
2312 def later2(self, project, what):
2313 self._later_queue2.append(_Later(project, what))
2314
2315 def Finish(self):
2316 self._PrintMessages()
2317 self._RunLater()
2318 self._PrintMessages()
2319 return self.clean
2320
2321 def _RunLater(self):
2322 for q in ['_later_queue1', '_later_queue2']:
2323 if not self._RunQueue(q):
2324 return
2325
2326 def _RunQueue(self, queue):
2327 for m in getattr(self, queue):
2328 if not m.Run(self):
2329 self.clean = False
2330 return False
2331 setattr(self, queue, [])
2332 return True
2333
2334 def _PrintMessages(self):
2335 for m in self._messages:
2336 m.Print(self)
2337 for m in self._failures:
2338 m.Print(self)
2339
2340 self._messages = []
2341 self._failures = []
2342
2343
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002344class MetaProject(Project):
2345 """A special project housed under .repo.
2346 """
2347 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002348 Project.__init__(self,
2349 manifest = manifest,
2350 name = name,
2351 gitdir = gitdir,
2352 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002353 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002354 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002355 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002356 revisionId = None,
2357 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002358
2359 def PreSync(self):
2360 if self.Exists:
2361 cb = self.CurrentBranch
2362 if cb:
2363 base = self.GetBranch(cb).merge
2364 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002365 self.revisionExpr = base
2366 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002367
Florian Vallee5d016502012-06-07 17:19:26 +02002368 def MetaBranchSwitch(self, target):
2369 """ Prepare MetaProject for manifest branch switch
2370 """
2371
2372 # detach and delete manifest branch, allowing a new
2373 # branch to take over
2374 syncbuf = SyncBuffer(self.config, detach_head = True)
2375 self.Sync_LocalHalf(syncbuf)
2376 syncbuf.Finish()
2377
2378 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002379 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002380 capture_stdout = True,
2381 capture_stderr = True).Wait() == 0
2382
2383
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002384 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002385 def LastFetch(self):
2386 try:
2387 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2388 return os.path.getmtime(fh)
2389 except OSError:
2390 return 0
2391
2392 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002393 def HasChanges(self):
2394 """Has the remote received new commits not yet checked out?
2395 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002396 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002397 return False
2398
David Pursehouse8a68ff92012-09-24 12:15:13 +09002399 all_refs = self.bare_ref.all
2400 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002401 head = self.work_git.GetHead()
2402 if head.startswith(R_HEADS):
2403 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002404 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002405 except KeyError:
2406 head = None
2407
2408 if revid == head:
2409 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002410 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002411 return True
2412 return False