blob: 460bf3150df5e450baff0c2eb9bcdad94f1c6d9f [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
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070029
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070031from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070032from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090033from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080034from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080035from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070036from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037
Shawn O. Pearced237b692009-04-17 18:49:50 -070038from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
David Pursehouse59bbb582013-05-17 10:49:33 +090040from pyversion import is_python3
41if not is_python3():
42 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053043 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090044 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053045
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070046def _lwrite(path, content):
47 lock = '%s.lock' % path
48
49 fd = open(lock, 'wb')
50 try:
51 fd.write(content)
52 finally:
53 fd.close()
54
55 try:
56 os.rename(lock, path)
57 except OSError:
58 os.remove(lock)
59 raise
60
Shawn O. Pearce48244782009-04-16 08:25:57 -070061def _error(fmt, *args):
62 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070063 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070064
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070065def not_rev(r):
66 return '^' + r
67
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080068def sq(r):
69 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080070
Doug Anderson8ced8642011-01-10 14:16:30 -080071_project_hook_list = None
72def _ProjectHooks():
73 """List the hooks present in the 'hooks' directory.
74
75 These hooks are project hooks and are copied to the '.git/hooks' directory
76 of all subprojects.
77
78 This function caches the list of hooks (based on the contents of the
79 'repo/hooks' directory) on the first call.
80
81 Returns:
82 A list of absolute paths to all of the files in the hooks directory.
83 """
84 global _project_hook_list
85 if _project_hook_list is None:
Jesse Hall672cc492013-11-27 11:17:13 -080086 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087 d = os.path.join(d , 'hooks')
Chirayu Desai217ea7d2013-03-01 19:14:38 +053088 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
Doug Anderson8ced8642011-01-10 14:16:30 -080089 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080090
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080091
Shawn O. Pearce632768b2008-10-23 11:58:52 -070092class DownloadedChange(object):
93 _commit_cache = None
94
95 def __init__(self, project, base, change_id, ps_id, commit):
96 self.project = project
97 self.base = base
98 self.change_id = change_id
99 self.ps_id = ps_id
100 self.commit = commit
101
102 @property
103 def commits(self):
104 if self._commit_cache is None:
105 self._commit_cache = self.project.bare_git.rev_list(
106 '--abbrev=8',
107 '--abbrev-commit',
108 '--pretty=oneline',
109 '--reverse',
110 '--date-order',
111 not_rev(self.base),
112 self.commit,
113 '--')
114 return self._commit_cache
115
116
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700117class ReviewableBranch(object):
118 _commit_cache = None
119
120 def __init__(self, project, branch, base):
121 self.project = project
122 self.branch = branch
123 self.base = base
124
125 @property
126 def name(self):
127 return self.branch.name
128
129 @property
130 def commits(self):
131 if self._commit_cache is None:
132 self._commit_cache = self.project.bare_git.rev_list(
133 '--abbrev=8',
134 '--abbrev-commit',
135 '--pretty=oneline',
136 '--reverse',
137 '--date-order',
138 not_rev(self.base),
139 R_HEADS + self.name,
140 '--')
141 return self._commit_cache
142
143 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800144 def unabbrev_commits(self):
145 r = dict()
146 for commit in self.project.bare_git.rev_list(
147 not_rev(self.base),
148 R_HEADS + self.name,
149 '--'):
150 r[commit[0:8]] = commit
151 return r
152
153 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154 def date(self):
155 return self.project.bare_git.log(
156 '--pretty=format:%cd',
157 '-n', '1',
158 R_HEADS + self.name,
159 '--')
160
Bryan Jacobsf609f912013-05-06 13:36:24 -0400161 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700163 people,
Brian Harring435370c2012-07-28 15:37:04 -0700164 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400165 draft=draft,
166 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700168 def GetPublishedRefs(self):
169 refs = {}
170 output = self.project.bare_git.ls_remote(
171 self.branch.remote.SshReviewUrl(self.project.UserEmail),
172 'refs/changes/*')
173 for line in output.split('\n'):
174 try:
175 (sha, ref) = line.split()
176 refs[sha] = ref
177 except ValueError:
178 pass
179
180 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
182class StatusColoring(Coloring):
183 def __init__(self, config):
184 Coloring.__init__(self, config, 'status')
185 self.project = self.printer('header', attr = 'bold')
186 self.branch = self.printer('header', attr = 'bold')
187 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700188 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700189
190 self.added = self.printer('added', fg = 'green')
191 self.changed = self.printer('changed', fg = 'red')
192 self.untracked = self.printer('untracked', fg = 'red')
193
194
195class DiffColoring(Coloring):
196 def __init__(self, config):
197 Coloring.__init__(self, config, 'diff')
198 self.project = self.printer('header', attr = 'bold')
199
James W. Mills24c13082012-04-12 15:04:13 -0500200class _Annotation:
201 def __init__(self, name, value, keep):
202 self.name = name
203 self.value = value
204 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205
206class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800207 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 self.src = src
209 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800210 self.abs_src = abssrc
211 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700212
213 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800214 src = self.abs_src
215 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 # copy file if it does not exist or is out of date
217 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
218 try:
219 # remove existing file first, since it might be read-only
220 if os.path.exists(dest):
221 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400222 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200223 dest_dir = os.path.dirname(dest)
224 if not os.path.isdir(dest_dir):
225 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226 shutil.copy(src, dest)
227 # make the file read-only
228 mode = os.stat(dest)[stat.ST_MODE]
229 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
230 os.chmod(dest, mode)
231 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700232 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500234class _LinkFile:
235 def __init__(self, src, dest, abssrc, absdest):
236 self.src = src
237 self.dest = dest
238 self.abs_src = abssrc
239 self.abs_dest = absdest
240
241 def _Link(self):
242 src = self.abs_src
243 dest = self.abs_dest
244 # link file if it does not exist or is out of date
245 if not os.path.islink(dest) or os.readlink(dest) != src:
246 try:
247 # remove existing file first, since it might be read-only
248 if os.path.exists(dest):
249 os.remove(dest)
250 else:
251 dest_dir = os.path.dirname(dest)
252 if not os.path.isdir(dest_dir):
253 os.makedirs(dest_dir)
254 os.symlink(src, dest)
255 except IOError:
256 _error('Cannot link file %s to %s', src, dest)
257
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700258class RemoteSpec(object):
259 def __init__(self,
260 name,
261 url = None,
Anthony King36ea2fb2014-05-06 11:54:01 +0100262 review = None,
263 revision = None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700264 self.name = name
265 self.url = url
266 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100267 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700268
Doug Anderson37282b42011-03-04 11:54:18 -0800269class RepoHook(object):
270 """A RepoHook contains information about a script to run as a hook.
271
272 Hooks are used to run a python script before running an upload (for instance,
273 to run presubmit checks). Eventually, we may have hooks for other actions.
274
275 This shouldn't be confused with files in the 'repo/hooks' directory. Those
276 files are copied into each '.git/hooks' folder for each project. Repo-level
277 hooks are associated instead with repo actions.
278
279 Hooks are always python. When a hook is run, we will load the hook into the
280 interpreter and execute its main() function.
281 """
282 def __init__(self,
283 hook_type,
284 hooks_project,
285 topdir,
286 abort_if_user_denies=False):
287 """RepoHook constructor.
288
289 Params:
290 hook_type: A string representing the type of hook. This is also used
291 to figure out the name of the file containing the hook. For
292 example: 'pre-upload'.
293 hooks_project: The project containing the repo hooks. If you have a
294 manifest, this is manifest.repo_hooks_project. OK if this is None,
295 which will make the hook a no-op.
296 topdir: Repo's top directory (the one containing the .repo directory).
297 Scripts will run with CWD as this directory. If you have a manifest,
298 this is manifest.topdir
299 abort_if_user_denies: If True, we'll throw a HookError() if the user
300 doesn't allow us to run the hook.
301 """
302 self._hook_type = hook_type
303 self._hooks_project = hooks_project
304 self._topdir = topdir
305 self._abort_if_user_denies = abort_if_user_denies
306
307 # Store the full path to the script for convenience.
308 if self._hooks_project:
309 self._script_fullpath = os.path.join(self._hooks_project.worktree,
310 self._hook_type + '.py')
311 else:
312 self._script_fullpath = None
313
314 def _GetHash(self):
315 """Return a hash of the contents of the hooks directory.
316
317 We'll just use git to do this. This hash has the property that if anything
318 changes in the directory we will return a different has.
319
320 SECURITY CONSIDERATION:
321 This hash only represents the contents of files in the hook directory, not
322 any other files imported or called by hooks. Changes to imported files
323 can change the script behavior without affecting the hash.
324
325 Returns:
326 A string representing the hash. This will always be ASCII so that it can
327 be printed to the user easily.
328 """
329 assert self._hooks_project, "Must have hooks to calculate their hash."
330
331 # We will use the work_git object rather than just calling GetRevisionId().
332 # That gives us a hash of the latest checked in version of the files that
333 # the user will actually be executing. Specifically, GetRevisionId()
334 # doesn't appear to change even if a user checks out a different version
335 # of the hooks repo (via git checkout) nor if a user commits their own revs.
336 #
337 # NOTE: Local (non-committed) changes will not be factored into this hash.
338 # I think this is OK, since we're really only worried about warning the user
339 # about upstream changes.
340 return self._hooks_project.work_git.rev_parse('HEAD')
341
342 def _GetMustVerb(self):
343 """Return 'must' if the hook is required; 'should' if not."""
344 if self._abort_if_user_denies:
345 return 'must'
346 else:
347 return 'should'
348
349 def _CheckForHookApproval(self):
350 """Check to see whether this hook has been approved.
351
352 We'll look at the hash of all of the hooks. If this matches the hash that
353 the user last approved, we're done. If it doesn't, we'll ask the user
354 about approval.
355
356 Note that we ask permission for each individual hook even though we use
357 the hash of all hooks when detecting changes. We'd like the user to be
358 able to approve / deny each hook individually. We only use the hash of all
359 hooks because there is no other easy way to detect changes to local imports.
360
361 Returns:
362 True if this hook is approved to run; False otherwise.
363
364 Raises:
365 HookError: Raised if the user doesn't approve and abort_if_user_denies
366 was passed to the consturctor.
367 """
Doug Anderson37282b42011-03-04 11:54:18 -0800368 hooks_config = self._hooks_project.config
369 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
370
371 # Get the last hash that the user approved for this hook; may be None.
372 old_hash = hooks_config.GetString(git_approval_key)
373
374 # Get the current hash so we can tell if scripts changed since approval.
375 new_hash = self._GetHash()
376
377 if old_hash is not None:
378 # User previously approved hook and asked not to be prompted again.
379 if new_hash == old_hash:
380 # Approval matched. We're done.
381 return True
382 else:
383 # Give the user a reason why we're prompting, since they last told
384 # us to "never ask again".
385 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
386 self._hook_type)
387 else:
388 prompt = ''
389
390 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
391 if sys.stdout.isatty():
392 prompt += ('Repo %s run the script:\n'
393 ' %s\n'
394 '\n'
395 'Do you want to allow this script to run '
396 '(yes/yes-never-ask-again/NO)? ') % (
397 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530398 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900399 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800400
401 # User is doing a one-time approval.
402 if response in ('y', 'yes'):
403 return True
404 elif response == 'yes-never-ask-again':
405 hooks_config.SetString(git_approval_key, new_hash)
406 return True
407
408 # For anything else, we'll assume no approval.
409 if self._abort_if_user_denies:
410 raise HookError('You must allow the %s hook or use --no-verify.' %
411 self._hook_type)
412
413 return False
414
415 def _ExecuteHook(self, **kwargs):
416 """Actually execute the given hook.
417
418 This will run the hook's 'main' function in our python interpreter.
419
420 Args:
421 kwargs: Keyword arguments to pass to the hook. These are often specific
422 to the hook type. For instance, pre-upload hooks will contain
423 a project_list.
424 """
425 # Keep sys.path and CWD stashed away so that we can always restore them
426 # upon function exit.
427 orig_path = os.getcwd()
428 orig_syspath = sys.path
429
430 try:
431 # Always run hooks with CWD as topdir.
432 os.chdir(self._topdir)
433
434 # Put the hook dir as the first item of sys.path so hooks can do
435 # relative imports. We want to replace the repo dir as [0] so
436 # hooks can't import repo files.
437 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
438
439 # Exec, storing global context in the context dict. We catch exceptions
440 # and convert to a HookError w/ just the failing traceback.
441 context = {}
442 try:
Anthony King70f68902014-05-05 21:15:34 +0100443 exec(compile(open(self._script_fullpath).read(),
444 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800445 except Exception:
446 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
447 traceback.format_exc(), self._hook_type))
448
449 # Running the script should have defined a main() function.
450 if 'main' not in context:
451 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
452
453
454 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
455 # We don't actually want hooks to define their main with this argument--
456 # it's there to remind them that their hook should always take **kwargs.
457 # For instance, a pre-upload hook should be defined like:
458 # def main(project_list, **kwargs):
459 #
460 # This allows us to later expand the API without breaking old hooks.
461 kwargs = kwargs.copy()
462 kwargs['hook_should_take_kwargs'] = True
463
464 # Call the main function in the hook. If the hook should cause the
465 # build to fail, it will raise an Exception. We'll catch that convert
466 # to a HookError w/ just the failing traceback.
467 try:
468 context['main'](**kwargs)
469 except Exception:
470 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
471 'above.' % (
472 traceback.format_exc(), self._hook_type))
473 finally:
474 # Restore sys.path and CWD.
475 sys.path = orig_syspath
476 os.chdir(orig_path)
477
478 def Run(self, user_allows_all_hooks, **kwargs):
479 """Run the hook.
480
481 If the hook doesn't exist (because there is no hooks project or because
482 this particular hook is not enabled), this is a no-op.
483
484 Args:
485 user_allows_all_hooks: If True, we will never prompt about running the
486 hook--we'll just assume it's OK to run it.
487 kwargs: Keyword arguments to pass to the hook. These are often specific
488 to the hook type. For instance, pre-upload hooks will contain
489 a project_list.
490
491 Raises:
492 HookError: If there was a problem finding the hook or the user declined
493 to run a required hook (from _CheckForHookApproval).
494 """
495 # No-op if there is no hooks project or if hook is disabled.
496 if ((not self._hooks_project) or
497 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
498 return
499
500 # Bail with a nice error if we can't find the hook.
501 if not os.path.isfile(self._script_fullpath):
502 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
503
504 # Make sure the user is OK with running the hook.
505 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
506 return
507
508 # Run the hook with the same version of python we're using.
509 self._ExecuteHook(**kwargs)
510
511
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512class Project(object):
513 def __init__(self,
514 manifest,
515 name,
516 remote,
517 gitdir,
David James8d201162013-10-11 17:03:19 -0700518 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700519 worktree,
520 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700521 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800522 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700523 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700524 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700525 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800526 sync_s = False,
David Pursehouseede7f122012-11-27 22:25:30 +0900527 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800528 upstream = None,
529 parent = None,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400530 is_derived = False,
531 dest_branch = None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800532 """Init a Project object.
533
534 Args:
535 manifest: The XmlManifest object.
536 name: The `name` attribute of manifest.xml's project element.
537 remote: RemoteSpec object specifying its remote's properties.
538 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700539 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800540 worktree: Absolute path of git working tree.
541 relpath: Relative path of git working tree to repo's top directory.
542 revisionExpr: The `revision` attribute of manifest.xml's project element.
543 revisionId: git commit id for checking out.
544 rebase: The `rebase` attribute of manifest.xml's project element.
545 groups: The `groups` attribute of manifest.xml's project element.
546 sync_c: The `sync-c` attribute of manifest.xml's project element.
547 sync_s: The `sync-s` attribute of manifest.xml's project element.
548 upstream: The `upstream` attribute of manifest.xml's project element.
549 parent: The parent Project object.
550 is_derived: False if the project was explicitly defined in the manifest;
551 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400552 dest_branch: The branch to which to push changes for review by default.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800553 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700554 self.manifest = manifest
555 self.name = name
556 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800557 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700558 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800559 if worktree:
560 self.worktree = worktree.replace('\\', '/')
561 else:
562 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700563 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700564 self.revisionExpr = revisionExpr
565
566 if revisionId is None \
567 and revisionExpr \
568 and IsId(revisionExpr):
569 self.revisionId = revisionExpr
570 else:
571 self.revisionId = revisionId
572
Mike Pontillod3153822012-02-28 11:53:24 -0800573 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700574 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700575 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800576 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900577 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700578 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800579 self.parent = parent
580 self.is_derived = is_derived
581 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800582
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700584 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500585 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500586 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700587 self.config = GitConfig.ForRepository(
588 gitdir = self.gitdir,
589 defaults = self.manifest.globalConfig)
590
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800591 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700592 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800593 else:
594 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700595 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700596 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700597 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400598 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700599
Doug Anderson37282b42011-03-04 11:54:18 -0800600 # This will be filled in if a project is later identified to be the
601 # project containing repo hooks.
602 self.enabled_repo_hooks = []
603
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700604 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800605 def Derived(self):
606 return self.is_derived
607
608 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700609 def Exists(self):
610 return os.path.isdir(self.gitdir)
611
612 @property
613 def CurrentBranch(self):
614 """Obtain the name of the currently checked out branch.
615 The branch name omits the 'refs/heads/' prefix.
616 None is returned if the project is on a detached HEAD.
617 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700618 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700619 if b.startswith(R_HEADS):
620 return b[len(R_HEADS):]
621 return None
622
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700623 def IsRebaseInProgress(self):
624 w = self.worktree
625 g = os.path.join(w, '.git')
626 return os.path.exists(os.path.join(g, 'rebase-apply')) \
627 or os.path.exists(os.path.join(g, 'rebase-merge')) \
628 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200629
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700630 def IsDirty(self, consider_untracked=True):
631 """Is the working directory modified in some way?
632 """
633 self.work_git.update_index('-q',
634 '--unmerged',
635 '--ignore-missing',
636 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900637 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700638 return True
639 if self.work_git.DiffZ('diff-files'):
640 return True
641 if consider_untracked and self.work_git.LsOthers():
642 return True
643 return False
644
645 _userident_name = None
646 _userident_email = None
647
648 @property
649 def UserName(self):
650 """Obtain the user's personal name.
651 """
652 if self._userident_name is None:
653 self._LoadUserIdentity()
654 return self._userident_name
655
656 @property
657 def UserEmail(self):
658 """Obtain the user's email address. This is very likely
659 to be their Gerrit login.
660 """
661 if self._userident_email is None:
662 self._LoadUserIdentity()
663 return self._userident_email
664
665 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900666 u = self.bare_git.var('GIT_COMMITTER_IDENT')
667 m = re.compile("^(.*) <([^>]*)> ").match(u)
668 if m:
669 self._userident_name = m.group(1)
670 self._userident_email = m.group(2)
671 else:
672 self._userident_name = ''
673 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700674
675 def GetRemote(self, name):
676 """Get the configuration for a single remote.
677 """
678 return self.config.GetRemote(name)
679
680 def GetBranch(self, name):
681 """Get the configuration for a single branch.
682 """
683 return self.config.GetBranch(name)
684
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700685 def GetBranches(self):
686 """Get all existing local branches.
687 """
688 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900689 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700690 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700691
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530692 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700693 if name.startswith(R_HEADS):
694 name = name[len(R_HEADS):]
695 b = self.GetBranch(name)
696 b.current = name == current
697 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900698 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700699 heads[name] = b
700
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530701 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700702 if name.startswith(R_PUB):
703 name = name[len(R_PUB):]
704 b = heads.get(name)
705 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900706 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700707
708 return heads
709
Colin Cross5acde752012-03-28 20:15:45 -0700710 def MatchesGroups(self, manifest_groups):
711 """Returns true if the manifest groups specified at init should cause
712 this project to be synced.
713 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700714 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700715
716 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700717 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700718 manifest_groups: "-group1,group2"
719 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500720
721 The special manifest group "default" will match any project that
722 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700723 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500724 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700725 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500726 if not 'notdefault' in expanded_project_groups:
727 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700728
Conley Owens971de8e2012-04-16 10:36:08 -0700729 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700730 for group in expanded_manifest_groups:
731 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700732 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700733 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700734 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700735
Conley Owens971de8e2012-04-16 10:36:08 -0700736 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737
738## Status Display ##
739
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500740 def HasChanges(self):
741 """Returns true if there are uncommitted changes.
742 """
743 self.work_git.update_index('-q',
744 '--unmerged',
745 '--ignore-missing',
746 '--refresh')
747 if self.IsRebaseInProgress():
748 return True
749
750 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
751 return True
752
753 if self.work_git.DiffZ('diff-files'):
754 return True
755
756 if self.work_git.LsOthers():
757 return True
758
759 return False
760
Terence Haddock4655e812011-03-31 12:33:34 +0200761 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700762 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200763
764 Args:
765 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700766 """
767 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200768 if output_redir == None:
769 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700770 print(file=output_redir)
771 print('project %s/' % self.relpath, file=output_redir)
772 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700773 return
774
775 self.work_git.update_index('-q',
776 '--unmerged',
777 '--ignore-missing',
778 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700779 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
781 df = self.work_git.DiffZ('diff-files')
782 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100783 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700784 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700785
786 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200787 if not output_redir == None:
788 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700789 out.project('project %-40s', self.relpath + '/')
790
791 branch = self.CurrentBranch
792 if branch is None:
793 out.nobranch('(*** NO BRANCH ***)')
794 else:
795 out.branch('branch %s', branch)
796 out.nl()
797
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700798 if rb:
799 out.important('prior sync failed; rebase still in progress')
800 out.nl()
801
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700802 paths = list()
803 paths.extend(di.keys())
804 paths.extend(df.keys())
805 paths.extend(do)
806
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530807 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900808 try:
809 i = di[p]
810 except KeyError:
811 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900813 try:
814 f = df[p]
815 except KeyError:
816 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200817
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900818 if i:
819 i_status = i.status.upper()
820 else:
821 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700822
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900823 if f:
824 f_status = f.status.lower()
825 else:
826 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700827
828 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800829 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830 i.src_path, p, i.level)
831 else:
832 line = ' %s%s\t%s' % (i_status, f_status, p)
833
834 if i and not f:
835 out.added('%s', line)
836 elif (i and f) or (not i and f):
837 out.changed('%s', line)
838 elif not i and not f:
839 out.untracked('%s', line)
840 else:
841 out.write('%s', line)
842 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200843
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700844 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845
pelyad67872d2012-03-28 14:49:58 +0300846 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700847 """Prints the status of the repository to stdout.
848 """
849 out = DiffColoring(self.config)
850 cmd = ['diff']
851 if out.is_on:
852 cmd.append('--color')
853 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300854 if absolute_paths:
855 cmd.append('--src-prefix=a/%s/' % self.relpath)
856 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857 cmd.append('--')
858 p = GitCommand(self,
859 cmd,
860 capture_stdout = True,
861 capture_stderr = True)
862 has_diff = False
863 for line in p.process.stdout:
864 if not has_diff:
865 out.nl()
866 out.project('project %s/' % self.relpath)
867 out.nl()
868 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700869 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700870 p.Wait()
871
872
873## Publish / Upload ##
874
David Pursehouse8a68ff92012-09-24 12:15:13 +0900875 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700876 """Was the branch published (uploaded) for code review?
877 If so, returns the SHA-1 hash of the last published
878 state for the branch.
879 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700880 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900881 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700882 try:
883 return self.bare_git.rev_parse(key)
884 except GitError:
885 return None
886 else:
887 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900888 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700889 except KeyError:
890 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700891
David Pursehouse8a68ff92012-09-24 12:15:13 +0900892 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893 """Prunes any stale published refs.
894 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900895 if all_refs is None:
896 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700897 heads = set()
898 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530899 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900 if name.startswith(R_HEADS):
901 heads.add(name)
902 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900903 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530905 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906 n = name[len(R_PUB):]
907 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900908 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700909
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700910 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911 """List any branches which can be uploaded for review.
912 """
913 heads = {}
914 pubed = {}
915
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530916 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900918 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900920 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921
922 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530923 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900924 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700926 if selected_branch and branch != selected_branch:
927 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800929 rb = self.GetUploadableBranch(branch)
930 if rb:
931 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932 return ready
933
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800934 def GetUploadableBranch(self, branch_name):
935 """Get a single uploadable branch, or None.
936 """
937 branch = self.GetBranch(branch_name)
938 base = branch.LocalMerge
939 if branch.LocalMerge:
940 rb = ReviewableBranch(self, branch, base)
941 if rb.commits:
942 return rb
943 return None
944
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700945 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700946 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700947 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400948 draft=False,
949 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950 """Uploads the named branch for code review.
951 """
952 if branch is None:
953 branch = self.CurrentBranch
954 if branch is None:
955 raise GitError('not currently on a branch')
956
957 branch = self.GetBranch(branch)
958 if not branch.LocalMerge:
959 raise GitError('branch %s does not track a remote' % branch.name)
960 if not branch.remote.review:
961 raise GitError('remote %s has no review url' % branch.remote.name)
962
Bryan Jacobsf609f912013-05-06 13:36:24 -0400963 if dest_branch is None:
964 dest_branch = self.dest_branch
965 if dest_branch is None:
966 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967 if not dest_branch.startswith(R_HEADS):
968 dest_branch = R_HEADS + dest_branch
969
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800970 if not branch.remote.projectname:
971 branch.remote.projectname = self.name
972 branch.remote.Save()
973
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800974 url = branch.remote.ReviewUrl(self.UserEmail)
975 if url is None:
976 raise UploadError('review not configured')
977 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800978
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800979 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800980 rp = ['gerrit receive-pack']
981 for e in people[0]:
982 rp.append('--reviewer=%s' % sq(e))
983 for e in people[1]:
984 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800985 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700986
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800987 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800988
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800989 if dest_branch.startswith(R_HEADS):
990 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700991
992 upload_type = 'for'
993 if draft:
994 upload_type = 'drafts'
995
996 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
997 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800998 if auto_topic:
999 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001000 if not url.startswith('ssh://'):
1001 rp = ['r=%s' % p for p in people[0]] + \
1002 ['cc=%s' % p for p in people[1]]
1003 if rp:
1004 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001005 cmd.append(ref_spec)
1006
1007 if GitCommand(self, cmd, bare = True).Wait() != 0:
1008 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001009
1010 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1011 self.bare_git.UpdateRef(R_PUB + branch.name,
1012 R_HEADS + branch.name,
1013 message = msg)
1014
1015
1016## Sync ##
1017
Julien Campergue335f5ef2013-10-16 11:02:35 +02001018 def _ExtractArchive(self, tarpath, path=None):
1019 """Extract the given tar on its current location
1020
1021 Args:
1022 - tarpath: The path to the actual tar file
1023
1024 """
1025 try:
1026 with tarfile.open(tarpath, 'r') as tar:
1027 tar.extractall(path=path)
1028 return True
1029 except (IOError, tarfile.TarError) as e:
1030 print("error: Cannot extract archive %s: "
1031 "%s" % (tarpath, str(e)), file=sys.stderr)
1032 return False
1033
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001034 def Sync_NetworkHalf(self,
1035 quiet=False,
1036 is_new=None,
1037 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001038 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001039 no_tags=False,
1040 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041 """Perform only the network IO portion of the sync process.
1042 Local working directory/branch state is not affected.
1043 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001044 if archive and not isinstance(self, MetaProject):
1045 if self.remote.url.startswith(('http://', 'https://')):
1046 print("error: %s: Cannot fetch archives from http/https "
1047 "remotes." % self.name, file=sys.stderr)
1048 return False
1049
1050 name = self.relpath.replace('\\', '/')
1051 name = name.replace('/', '_')
1052 tarpath = '%s.tar' % name
1053 topdir = self.manifest.topdir
1054
1055 try:
1056 self._FetchArchive(tarpath, cwd=topdir)
1057 except GitError as e:
1058 print('error: %s' % str(e), file=sys.stderr)
1059 return False
1060
1061 # From now on, we only need absolute tarpath
1062 tarpath = os.path.join(topdir, tarpath)
1063
1064 if not self._ExtractArchive(tarpath, path=topdir):
1065 return False
1066 try:
1067 os.remove(tarpath)
1068 except OSError as e:
1069 print("warn: Cannot remove archive %s: "
1070 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001071 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001072 return True
1073
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001074 if is_new is None:
1075 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001076 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001078 else:
1079 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001081
1082 if is_new:
1083 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1084 try:
1085 fd = open(alt, 'rb')
1086 try:
1087 alt_dir = fd.readline().rstrip()
1088 finally:
1089 fd.close()
1090 except IOError:
1091 alt_dir = None
1092 else:
1093 alt_dir = None
1094
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001095 if clone_bundle \
1096 and alt_dir is None \
1097 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001098 is_new = False
1099
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001100 if not current_branch_only:
1101 if self.sync_c:
1102 current_branch_only = True
1103 elif not self.manifest._loaded:
1104 # Manifest cannot check defaults until it syncs.
1105 current_branch_only = False
1106 elif self.manifest.default.sync_c:
1107 current_branch_only = True
1108
Conley Owens666d5342014-05-01 13:09:57 -07001109 has_sha1 = ID_RE.match(self.revisionExpr) and self._CheckForSha1()
1110 if (not has_sha1 #Need to fetch since we don't already have this revision
1111 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1112 current_branch_only=current_branch_only,
1113 no_tags=no_tags)):
1114 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001115
1116 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001117 self._InitMRef()
1118 else:
1119 self._InitMirrorHead()
1120 try:
1121 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1122 except OSError:
1123 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001124 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001125
1126 def PostRepoUpgrade(self):
1127 self._InitHooks()
1128
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001129 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001130 for copyfile in self.copyfiles:
1131 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001132 for linkfile in self.linkfiles:
1133 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001134
Julien Camperguedd654222014-01-09 16:21:37 +01001135 def GetCommitRevisionId(self):
1136 """Get revisionId of a commit.
1137
1138 Use this method instead of GetRevisionId to get the id of the commit rather
1139 than the id of the current git object (for example, a tag)
1140
1141 """
1142 if not self.revisionExpr.startswith(R_TAGS):
1143 return self.GetRevisionId(self._allrefs)
1144
1145 try:
1146 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1147 except GitError:
1148 raise ManifestInvalidRevisionError(
1149 'revision %s in %s not found' % (self.revisionExpr,
1150 self.name))
1151
David Pursehouse8a68ff92012-09-24 12:15:13 +09001152 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001153 if self.revisionId:
1154 return self.revisionId
1155
1156 rem = self.GetRemote(self.remote.name)
1157 rev = rem.ToLocal(self.revisionExpr)
1158
David Pursehouse8a68ff92012-09-24 12:15:13 +09001159 if all_refs is not None and rev in all_refs:
1160 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001161
1162 try:
1163 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1164 except GitError:
1165 raise ManifestInvalidRevisionError(
1166 'revision %s in %s not found' % (self.revisionExpr,
1167 self.name))
1168
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001169 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170 """Perform only the local IO portion of the sync process.
1171 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172 """
David James8d201162013-10-11 17:03:19 -07001173 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001174 all_refs = self.bare_ref.all
1175 self.CleanPublishedCache(all_refs)
1176 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001177
David Pursehouse1d947b32012-10-25 12:23:11 +09001178 def _doff():
1179 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001180 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001181
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001182 head = self.work_git.GetHead()
1183 if head.startswith(R_HEADS):
1184 branch = head[len(R_HEADS):]
1185 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001186 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001187 except KeyError:
1188 head = None
1189 else:
1190 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001191
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001192 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193 # Currently on a detached HEAD. The user is assumed to
1194 # not have any local modifications worth worrying about.
1195 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001196 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001197 syncbuf.fail(self, _PriorSyncFailedError())
1198 return
1199
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001200 if head == revid:
1201 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001202 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001203 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001204 if not syncbuf.detach_head:
1205 return
1206 else:
1207 lost = self._revlist(not_rev(revid), HEAD)
1208 if lost:
1209 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001210
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001211 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001212 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001213 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001214 syncbuf.fail(self, e)
1215 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001216 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001217 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001218
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001219 if head == revid:
1220 # No changes; don't do anything further.
1221 #
1222 return
1223
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001225
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001226 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001228 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001229 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001230 syncbuf.info(self,
1231 "leaving %s; does not track upstream",
1232 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001233 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001234 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001235 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001236 syncbuf.fail(self, e)
1237 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001238 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001239 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001240
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001241 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001242 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001243 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001244 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001245 if not_merged:
1246 if upstream_gain:
1247 # The user has published this branch and some of those
1248 # commits are not yet merged upstream. We do not want
1249 # to rewrite the published commits so we punt.
1250 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001251 syncbuf.fail(self,
1252 "branch %s is published (but not merged) and is now %d commits behind"
1253 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001254 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001255 elif pub == head:
1256 # All published commits are merged, and thus we are a
1257 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001258 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001259 syncbuf.later1(self, _doff)
1260 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001261
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001262 # Examine the local commits not in the remote. Find the
1263 # last one attributed to this user, if any.
1264 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001265 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001266 last_mine = None
1267 cnt_mine = 0
1268 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301269 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001270 if committer_email == self.UserEmail:
1271 last_mine = commit_id
1272 cnt_mine += 1
1273
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001274 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001275 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001276
1277 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001278 syncbuf.fail(self, _DirtyError())
1279 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001281 # If the upstream switched on us, warn the user.
1282 #
1283 if branch.merge != self.revisionExpr:
1284 if branch.merge and self.revisionExpr:
1285 syncbuf.info(self,
1286 'manifest switched %s...%s',
1287 branch.merge,
1288 self.revisionExpr)
1289 elif branch.merge:
1290 syncbuf.info(self,
1291 'manifest no longer tracks %s',
1292 branch.merge)
1293
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001294 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001295 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001296 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001298 syncbuf.info(self,
1299 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001300 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001302 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001303 if not ID_RE.match(self.revisionExpr):
1304 # in case of manifest sync the revisionExpr might be a SHA1
1305 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 branch.Save()
1307
Mike Pontillod3153822012-02-28 11:53:24 -08001308 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001309 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001310 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001311 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001312 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001313 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001314 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001315 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001316 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001317 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001318 syncbuf.fail(self, e)
1319 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001321 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001323 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 # dest should already be an absolute path, but src is project relative
1325 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001326 abssrc = os.path.join(self.worktree, src)
1327 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001329 def AddLinkFile(self, src, dest, absdest):
1330 # dest should already be an absolute path, but src is project relative
1331 # make src an absolute path
1332 abssrc = os.path.join(self.worktree, src)
1333 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1334
James W. Mills24c13082012-04-12 15:04:13 -05001335 def AddAnnotation(self, name, value, keep):
1336 self.annotations.append(_Annotation(name, value, keep))
1337
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001338 def DownloadPatchSet(self, change_id, patch_id):
1339 """Download a single patch set of a single change to FETCH_HEAD.
1340 """
1341 remote = self.GetRemote(self.remote.name)
1342
1343 cmd = ['fetch', remote.name]
1344 cmd.append('refs/changes/%2.2d/%d/%d' \
1345 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001346 if GitCommand(self, cmd, bare=True).Wait() != 0:
1347 return None
1348 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001349 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001350 change_id,
1351 patch_id,
1352 self.bare_git.rev_parse('FETCH_HEAD'))
1353
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001354
1355## Branch Management ##
1356
1357 def StartBranch(self, name):
1358 """Create a new branch off the manifest's revision.
1359 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001360 head = self.work_git.GetHead()
1361 if head == (R_HEADS + name):
1362 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363
David Pursehouse8a68ff92012-09-24 12:15:13 +09001364 all_refs = self.bare_ref.all
1365 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001366 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001367 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001368 capture_stdout = True,
1369 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001370
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001371 branch = self.GetBranch(name)
1372 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001373 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001374 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001375
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001376 if head.startswith(R_HEADS):
1377 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001378 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001379 except KeyError:
1380 head = None
1381
1382 if revid and head and revid == head:
1383 ref = os.path.join(self.gitdir, R_HEADS + name)
1384 try:
1385 os.makedirs(os.path.dirname(ref))
1386 except OSError:
1387 pass
1388 _lwrite(ref, '%s\n' % revid)
1389 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1390 'ref: %s%s\n' % (R_HEADS, name))
1391 branch.Save()
1392 return True
1393
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001394 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001395 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001396 capture_stdout = True,
1397 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001398 branch.Save()
1399 return True
1400 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001401
Wink Saville02d79452009-04-10 13:01:24 -07001402 def CheckoutBranch(self, name):
1403 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001404
1405 Args:
1406 name: The name of the branch to checkout.
1407
1408 Returns:
1409 True if the checkout succeeded; False if it didn't; None if the branch
1410 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001411 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001412 rev = R_HEADS + name
1413 head = self.work_git.GetHead()
1414 if head == rev:
1415 # Already on the branch
1416 #
1417 return True
Wink Saville02d79452009-04-10 13:01:24 -07001418
David Pursehouse8a68ff92012-09-24 12:15:13 +09001419 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001420 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001421 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001422 except KeyError:
1423 # Branch does not exist in this project
1424 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001425 return None
Wink Saville02d79452009-04-10 13:01:24 -07001426
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001427 if head.startswith(R_HEADS):
1428 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001429 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001430 except KeyError:
1431 head = None
1432
1433 if head == revid:
1434 # Same revision; just update HEAD to point to the new
1435 # target branch, but otherwise take no other action.
1436 #
1437 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1438 'ref: %s%s\n' % (R_HEADS, name))
1439 return True
1440
1441 return GitCommand(self,
1442 ['checkout', name, '--'],
1443 capture_stdout = True,
1444 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001445
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001446 def AbandonBranch(self, name):
1447 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001448
1449 Args:
1450 name: The name of the branch to abandon.
1451
1452 Returns:
1453 True if the abandon succeeded; False if it didn't; None if the branch
1454 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001455 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001456 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001457 all_refs = self.bare_ref.all
1458 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001459 # Doesn't exist
1460 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001461
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001462 head = self.work_git.GetHead()
1463 if head == rev:
1464 # We can't destroy the branch while we are sitting
1465 # on it. Switch to a detached HEAD.
1466 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001467 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001468
David Pursehouse8a68ff92012-09-24 12:15:13 +09001469 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001470 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001471 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1472 '%s\n' % revid)
1473 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001474 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001475
1476 return GitCommand(self,
1477 ['branch', '-D', name],
1478 capture_stdout = True,
1479 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001480
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001481 def PruneHeads(self):
1482 """Prune any topic branches already merged into upstream.
1483 """
1484 cb = self.CurrentBranch
1485 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001486 left = self._allrefs
1487 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001488 if name.startswith(R_HEADS):
1489 name = name[len(R_HEADS):]
1490 if cb is None or name != cb:
1491 kill.append(name)
1492
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001493 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001494 if cb is not None \
1495 and not self._revlist(HEAD + '...' + rev) \
1496 and not self.IsDirty(consider_untracked = False):
1497 self.work_git.DetachHead(HEAD)
1498 kill.append(cb)
1499
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001500 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001501 old = self.bare_git.GetHead()
1502 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001503 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1504
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001505 try:
1506 self.bare_git.DetachHead(rev)
1507
1508 b = ['branch', '-d']
1509 b.extend(kill)
1510 b = GitCommand(self, b, bare=True,
1511 capture_stdout=True,
1512 capture_stderr=True)
1513 b.Wait()
1514 finally:
1515 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001516 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001517
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001518 for branch in kill:
1519 if (R_HEADS + branch) not in left:
1520 self.CleanPublishedCache()
1521 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001522
1523 if cb and cb not in kill:
1524 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001525 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001526
1527 kept = []
1528 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001529 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001530 branch = self.GetBranch(branch)
1531 base = branch.LocalMerge
1532 if not base:
1533 base = rev
1534 kept.append(ReviewableBranch(self, branch, base))
1535 return kept
1536
1537
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001538## Submodule Management ##
1539
1540 def GetRegisteredSubprojects(self):
1541 result = []
1542 def rec(subprojects):
1543 if not subprojects:
1544 return
1545 result.extend(subprojects)
1546 for p in subprojects:
1547 rec(p.subprojects)
1548 rec(self.subprojects)
1549 return result
1550
1551 def _GetSubmodules(self):
1552 # Unfortunately we cannot call `git submodule status --recursive` here
1553 # because the working tree might not exist yet, and it cannot be used
1554 # without a working tree in its current implementation.
1555
1556 def get_submodules(gitdir, rev):
1557 # Parse .gitmodules for submodule sub_paths and sub_urls
1558 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1559 if not sub_paths:
1560 return []
1561 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1562 # revision of submodule repository
1563 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1564 submodules = []
1565 for sub_path, sub_url in zip(sub_paths, sub_urls):
1566 try:
1567 sub_rev = sub_revs[sub_path]
1568 except KeyError:
1569 # Ignore non-exist submodules
1570 continue
1571 submodules.append((sub_rev, sub_path, sub_url))
1572 return submodules
1573
1574 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1575 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1576 def parse_gitmodules(gitdir, rev):
1577 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1578 try:
1579 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1580 bare = True, gitdir = gitdir)
1581 except GitError:
1582 return [], []
1583 if p.Wait() != 0:
1584 return [], []
1585
1586 gitmodules_lines = []
1587 fd, temp_gitmodules_path = tempfile.mkstemp()
1588 try:
1589 os.write(fd, p.stdout)
1590 os.close(fd)
1591 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1592 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1593 bare = True, gitdir = gitdir)
1594 if p.Wait() != 0:
1595 return [], []
1596 gitmodules_lines = p.stdout.split('\n')
1597 except GitError:
1598 return [], []
1599 finally:
1600 os.remove(temp_gitmodules_path)
1601
1602 names = set()
1603 paths = {}
1604 urls = {}
1605 for line in gitmodules_lines:
1606 if not line:
1607 continue
1608 m = re_path.match(line)
1609 if m:
1610 names.add(m.group(1))
1611 paths[m.group(1)] = m.group(2)
1612 continue
1613 m = re_url.match(line)
1614 if m:
1615 names.add(m.group(1))
1616 urls[m.group(1)] = m.group(2)
1617 continue
1618 names = sorted(names)
1619 return ([paths.get(name, '') for name in names],
1620 [urls.get(name, '') for name in names])
1621
1622 def git_ls_tree(gitdir, rev, paths):
1623 cmd = ['ls-tree', rev, '--']
1624 cmd.extend(paths)
1625 try:
1626 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1627 bare = True, gitdir = gitdir)
1628 except GitError:
1629 return []
1630 if p.Wait() != 0:
1631 return []
1632 objects = {}
1633 for line in p.stdout.split('\n'):
1634 if not line.strip():
1635 continue
1636 object_rev, object_path = line.split()[2:4]
1637 objects[object_path] = object_rev
1638 return objects
1639
1640 try:
1641 rev = self.GetRevisionId()
1642 except GitError:
1643 return []
1644 return get_submodules(self.gitdir, rev)
1645
1646 def GetDerivedSubprojects(self):
1647 result = []
1648 if not self.Exists:
1649 # If git repo does not exist yet, querying its submodules will
1650 # mess up its states; so return here.
1651 return result
1652 for rev, path, url in self._GetSubmodules():
1653 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001654 relpath, worktree, gitdir, objdir = \
1655 self.manifest.GetSubprojectPaths(self, name, path)
1656 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001657 if project:
1658 result.extend(project.GetDerivedSubprojects())
1659 continue
David James8d201162013-10-11 17:03:19 -07001660
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001661 remote = RemoteSpec(self.remote.name,
1662 url = url,
Anthony King36ea2fb2014-05-06 11:54:01 +01001663 review = self.remote.review,
1664 revision = self.remote.revision)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001665 subproject = Project(manifest = self.manifest,
1666 name = name,
1667 remote = remote,
1668 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001669 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001670 worktree = worktree,
1671 relpath = relpath,
1672 revisionExpr = self.revisionExpr,
1673 revisionId = rev,
1674 rebase = self.rebase,
1675 groups = self.groups,
1676 sync_c = self.sync_c,
1677 sync_s = self.sync_s,
1678 parent = self,
1679 is_derived = True)
1680 result.append(subproject)
1681 result.extend(subproject.GetDerivedSubprojects())
1682 return result
1683
1684
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001686 def _CheckForSha1(self):
1687 try:
1688 # if revision (sha or tag) is not present then following function
1689 # throws an error.
1690 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1691 return True
1692 except GitError:
1693 # There is no such persistent revision. We have to fetch it.
1694 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001695
Julien Campergue335f5ef2013-10-16 11:02:35 +02001696 def _FetchArchive(self, tarpath, cwd=None):
1697 cmd = ['archive', '-v', '-o', tarpath]
1698 cmd.append('--remote=%s' % self.remote.url)
1699 cmd.append('--prefix=%s/' % self.relpath)
1700 cmd.append(self.revisionExpr)
1701
1702 command = GitCommand(self, cmd, cwd=cwd,
1703 capture_stdout=True,
1704 capture_stderr=True)
1705
1706 if command.Wait() != 0:
1707 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1708
Conley Owens80b87fe2014-05-09 17:13:44 -07001709
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001710 def _RemoteFetch(self, name=None,
1711 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001712 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001713 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001714 alt_dir=None,
1715 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001716
1717 is_sha1 = False
1718 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001719 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001720
David Pursehouse9bc422f2014-04-15 10:28:56 +09001721 # The depth should not be used when fetching to a mirror because
1722 # it will result in a shallow repository that cannot be cloned or
1723 # fetched from.
1724 if not self.manifest.IsMirror:
1725 if self.clone_depth:
1726 depth = self.clone_depth
1727 else:
1728 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1729
Shawn Pearce69e04d82014-01-29 12:48:54 -08001730 if depth:
1731 current_branch_only = True
1732
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001733 if current_branch_only:
1734 if ID_RE.match(self.revisionExpr) is not None:
1735 is_sha1 = True
1736 elif self.revisionExpr.startswith(R_TAGS):
1737 # this is a tag and its sha1 value should never change
1738 tag_name = self.revisionExpr[len(R_TAGS):]
1739
1740 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001741 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001742 return True
Brian Harring14a66742012-09-28 20:21:57 -07001743 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1744 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001745
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001746 if not name:
1747 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001748
1749 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001750 remote = self.GetRemote(name)
1751 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001752 ssh_proxy = True
1753
Shawn O. Pearce88443382010-10-08 10:02:09 +02001754 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001755 if alt_dir and 'objects' == os.path.basename(alt_dir):
1756 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001757 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1758 remote = self.GetRemote(name)
1759
David Pursehouse8a68ff92012-09-24 12:15:13 +09001760 all_refs = self.bare_ref.all
1761 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001762 tmp = set()
1763
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301764 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001765 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001766 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001767 all_refs[r] = ref_id
1768 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001769 continue
1770
David Pursehouse8a68ff92012-09-24 12:15:13 +09001771 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001772 continue
1773
David Pursehouse8a68ff92012-09-24 12:15:13 +09001774 r = 'refs/_alt/%s' % ref_id
1775 all_refs[r] = ref_id
1776 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001777 tmp.add(r)
1778
Shawn O. Pearce88443382010-10-08 10:02:09 +02001779 tmp_packed = ''
1780 old_packed = ''
1781
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301782 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001783 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001784 tmp_packed += line
1785 if r not in tmp:
1786 old_packed += line
1787
1788 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001789 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001790 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001791
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001792 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001793
1794 # The --depth option only affects the initial fetch; after that we'll do
1795 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001796 if depth and initial:
1797 cmd.append('--depth=%s' % depth)
1798
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001799 if quiet:
1800 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001801 if not self.worktree:
1802 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001803 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001804
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001805 # If using depth then we should not get all the tags since they may
1806 # be outside of the depth.
1807 if no_tags or depth:
1808 cmd.append('--no-tags')
1809 else:
1810 cmd.append('--tags')
1811
Conley Owens80b87fe2014-05-09 17:13:44 -07001812 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001813 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001814 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001815 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001816 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001817 spec.append('tag')
1818 spec.append(tag_name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001819 else:
1820 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001821 if is_sha1:
1822 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001823 if branch.startswith(R_HEADS):
1824 branch = branch[len(R_HEADS):]
Conley Owens80b87fe2014-05-09 17:13:44 -07001825 spec.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
1826 cmd.extend(spec)
1827
1828 shallowfetch = self.config.GetString('repo.shallowfetch')
1829 if shallowfetch and shallowfetch != ' '.join(spec):
1830 GitCommand(self, ['fetch', '--unshallow', name] + shallowfetch.split(),
1831 bare=True, ssh_proxy=ssh_proxy).Wait()
1832 if depth:
1833 self.config.SetString('repo.shallowfetch', ' '.join(spec))
1834 else:
1835 self.config.SetString('repo.shallowfetch', None)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001836
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001837 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001838 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001839 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1840 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001841 ok = True
1842 break
Brian Harring14a66742012-09-28 20:21:57 -07001843 elif current_branch_only and is_sha1 and ret == 128:
1844 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1845 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1846 # abort the optimization attempt and do a full sync.
1847 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001848 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001849
1850 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001851 # Ensure that some refs exist. Otherwise, we probably aren't looking
1852 # at a real git repository and may have a bad url.
1853 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001854 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001855
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001856 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001857 if old_packed != '':
1858 _lwrite(packed_refs, old_packed)
1859 else:
1860 os.remove(packed_refs)
1861 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001862
1863 if is_sha1 and current_branch_only and self.upstream:
1864 # We just synced the upstream given branch; verify we
1865 # got what we wanted, else trigger a second run of all
1866 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001867 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001868 return self._RemoteFetch(name=name, current_branch_only=False,
1869 initial=False, quiet=quiet, alt_dir=alt_dir)
1870
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001871 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001872
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001873 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001874 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001875 return False
1876
1877 remote = self.GetRemote(self.remote.name)
1878 bundle_url = remote.url + '/clone.bundle'
1879 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001880 if GetSchemeFromUrl(bundle_url) not in (
1881 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001882 return False
1883
1884 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1885 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1886
1887 exist_dst = os.path.exists(bundle_dst)
1888 exist_tmp = os.path.exists(bundle_tmp)
1889
1890 if not initial and not exist_dst and not exist_tmp:
1891 return False
1892
1893 if not exist_dst:
1894 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1895 if not exist_dst:
1896 return False
1897
1898 cmd = ['fetch']
1899 if quiet:
1900 cmd.append('--quiet')
1901 if not self.worktree:
1902 cmd.append('--update-head-ok')
1903 cmd.append(bundle_dst)
1904 for f in remote.fetch:
1905 cmd.append(str(f))
1906 cmd.append('refs/tags/*:refs/tags/*')
1907
1908 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001909 if os.path.exists(bundle_dst):
1910 os.remove(bundle_dst)
1911 if os.path.exists(bundle_tmp):
1912 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001913 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001914
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001915 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001916 if os.path.exists(dstPath):
1917 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001918
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001919 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001920 if quiet:
1921 cmd += ['--silent']
1922 if os.path.exists(tmpPath):
1923 size = os.stat(tmpPath).st_size
1924 if size >= 1024:
1925 cmd += ['--continue-at', '%d' % (size,)]
1926 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001927 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001928 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1929 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001930 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001931 if cookiefile:
1932 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001933 if srcUrl.startswith('persistent-'):
1934 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001935 cmd += [srcUrl]
1936
1937 if IsTrace():
1938 Trace('%s', ' '.join(cmd))
1939 try:
1940 proc = subprocess.Popen(cmd)
1941 except OSError:
1942 return False
1943
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001944 curlret = proc.wait()
1945
1946 if curlret == 22:
1947 # From curl man page:
1948 # 22: HTTP page not retrieved. The requested url was not found or
1949 # returned another error with the HTTP error code being 400 or above.
1950 # This return code only appears if -f, --fail is used.
1951 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001952 print("Server does not provide clone.bundle; ignoring.",
1953 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001954 return False
1955
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001956 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001957 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001958 os.rename(tmpPath, dstPath)
1959 return True
1960 else:
1961 os.remove(tmpPath)
1962 return False
1963 else:
1964 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001965
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001966 def _IsValidBundle(self, path):
1967 try:
1968 with open(path) as f:
1969 if f.read(16) == '# v2 git bundle\n':
1970 return True
1971 else:
1972 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1973 return False
1974 except OSError:
1975 return False
1976
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001977 def _GetBundleCookieFile(self, url):
1978 if url.startswith('persistent-'):
1979 try:
1980 p = subprocess.Popen(
1981 ['git-remote-persistent-https', '-print_config', url],
1982 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1983 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001984 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001985 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001986 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001987 for line in p.stdout:
1988 line = line.strip()
1989 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001990 cookiefile = line[len(prefix):]
1991 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001992 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001993 err_msg = p.stderr.read()
1994 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001995 pass # Persistent proxy doesn't support -print_config.
1996 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001997 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001998 if cookiefile:
1999 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002000 except OSError as e:
2001 if e.errno == errno.ENOENT:
2002 pass # No persistent proxy.
2003 raise
2004 return GitConfig.ForUser().GetString('http.cookiefile')
2005
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002006 def _Checkout(self, rev, quiet=False):
2007 cmd = ['checkout']
2008 if quiet:
2009 cmd.append('-q')
2010 cmd.append(rev)
2011 cmd.append('--')
2012 if GitCommand(self, cmd).Wait() != 0:
2013 if self._allrefs:
2014 raise GitError('%s checkout %s ' % (self.name, rev))
2015
Pierre Tardye5a21222011-03-24 16:28:18 +01002016 def _CherryPick(self, rev, quiet=False):
2017 cmd = ['cherry-pick']
2018 cmd.append(rev)
2019 cmd.append('--')
2020 if GitCommand(self, cmd).Wait() != 0:
2021 if self._allrefs:
2022 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2023
Erwan Mahea94f1622011-08-19 13:56:09 +02002024 def _Revert(self, rev, quiet=False):
2025 cmd = ['revert']
2026 cmd.append('--no-edit')
2027 cmd.append(rev)
2028 cmd.append('--')
2029 if GitCommand(self, cmd).Wait() != 0:
2030 if self._allrefs:
2031 raise GitError('%s revert %s ' % (self.name, rev))
2032
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002033 def _ResetHard(self, rev, quiet=True):
2034 cmd = ['reset', '--hard']
2035 if quiet:
2036 cmd.append('-q')
2037 cmd.append(rev)
2038 if GitCommand(self, cmd).Wait() != 0:
2039 raise GitError('%s reset --hard %s ' % (self.name, rev))
2040
2041 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002042 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002043 if onto is not None:
2044 cmd.extend(['--onto', onto])
2045 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002046 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002047 raise GitError('%s rebase %s ' % (self.name, upstream))
2048
Pierre Tardy3d125942012-05-04 12:18:12 +02002049 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002050 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002051 if ffonly:
2052 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002053 if GitCommand(self, cmd).Wait() != 0:
2054 raise GitError('%s merge %s ' % (self.name, head))
2055
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002056 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002057 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002058
2059 # Initialize the bare repository, which contains all of the objects.
2060 if not os.path.exists(self.objdir):
2061 os.makedirs(self.objdir)
2062 self.bare_objdir.init()
2063
2064 # If we have a separate directory to hold refs, initialize it as well.
2065 if self.objdir != self.gitdir:
2066 os.makedirs(self.gitdir)
2067 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2068 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002069
Shawn O. Pearce88443382010-10-08 10:02:09 +02002070 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002071 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002072
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002073 if ref_dir or mirror_git:
2074 if not mirror_git:
2075 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002076 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2077 self.relpath + '.git')
2078
2079 if os.path.exists(mirror_git):
2080 ref_dir = mirror_git
2081
2082 elif os.path.exists(repo_git):
2083 ref_dir = repo_git
2084
2085 else:
2086 ref_dir = None
2087
2088 if ref_dir:
2089 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2090 os.path.join(ref_dir, 'objects') + '\n')
2091
Jimmie Westera0444582012-10-24 13:44:42 +02002092 self._UpdateHooks()
2093
2094 m = self.manifest.manifestProject.config
2095 for key in ['user.name', 'user.email']:
2096 if m.Has(key, include_defaults = False):
2097 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002098 if self.manifest.IsMirror:
2099 self.config.SetString('core.bare', 'true')
2100 else:
2101 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002102
Jimmie Westera0444582012-10-24 13:44:42 +02002103 def _UpdateHooks(self):
2104 if os.path.exists(self.gitdir):
2105 # Always recreate hooks since they can have been changed
2106 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002107 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002108 try:
2109 to_rm = os.listdir(hooks)
2110 except OSError:
2111 to_rm = []
2112 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002113 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002114 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002115
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002116 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002117 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002118 if not os.path.exists(hooks):
2119 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002120 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002121 name = os.path.basename(stock_hook)
2122
Victor Boivie65e0f352011-04-18 11:23:29 +02002123 if name in ('commit-msg',) and not self.remote.review \
2124 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002125 # Don't install a Gerrit Code Review hook if this
2126 # project does not appear to use it for reviews.
2127 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002128 # Since the manifest project is one of those, but also
2129 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002130 continue
2131
2132 dst = os.path.join(hooks, name)
2133 if os.path.islink(dst):
2134 continue
2135 if os.path.exists(dst):
2136 if filecmp.cmp(stock_hook, dst, shallow=False):
2137 os.remove(dst)
2138 else:
2139 _error("%s: Not replacing %s hook", self.relpath, name)
2140 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002141 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002142 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002143 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002144 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002145 raise GitError('filesystem must support symlinks')
2146 else:
2147 raise
2148
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002149 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002150 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002151 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002152 remote.url = self.remote.url
2153 remote.review = self.remote.review
2154 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002155
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002156 if self.worktree:
2157 remote.ResetFetch(mirror=False)
2158 else:
2159 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002160 remote.Save()
2161
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002162 def _InitMRef(self):
2163 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002164 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002165
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002166 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002167 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002168
2169 def _InitAnyMRef(self, ref):
2170 cur = self.bare_ref.symref(ref)
2171
2172 if self.revisionId:
2173 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2174 msg = 'manifest set to %s' % self.revisionId
2175 dst = self.revisionId + '^0'
2176 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2177 else:
2178 remote = self.GetRemote(self.remote.name)
2179 dst = remote.ToLocal(self.revisionExpr)
2180 if cur != dst:
2181 msg = 'manifest set to %s' % self.revisionExpr
2182 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002183
David James8d201162013-10-11 17:03:19 -07002184 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2185 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2186
2187 Args:
2188 gitdir: The bare git repository. Must already be initialized.
2189 dotgit: The repository you would like to initialize.
2190 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2191 Only one work tree can store refs under a given |gitdir|.
2192 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2193 This saves you the effort of initializing |dotgit| yourself.
2194 """
2195 # These objects can be shared between several working trees.
2196 symlink_files = ['description', 'info']
2197 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2198 if share_refs:
2199 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002200 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002201 symlink_dirs += ['logs', 'refs']
2202 to_symlink = symlink_files + symlink_dirs
2203
2204 to_copy = []
2205 if copy_all:
2206 to_copy = os.listdir(gitdir)
2207
2208 for name in set(to_copy).union(to_symlink):
2209 try:
2210 src = os.path.realpath(os.path.join(gitdir, name))
2211 dst = os.path.realpath(os.path.join(dotgit, name))
2212
2213 if os.path.lexists(dst) and not os.path.islink(dst):
2214 raise GitError('cannot overwrite a local work tree')
2215
2216 # If the source dir doesn't exist, create an empty dir.
2217 if name in symlink_dirs and not os.path.lexists(src):
2218 os.makedirs(src)
2219
Conley Owens80b87fe2014-05-09 17:13:44 -07002220 # If the source file doesn't exist, ensure the destination
2221 # file doesn't either.
2222 if name in symlink_files and not os.path.lexists(src):
2223 try:
2224 os.remove(dst)
2225 except OSError:
2226 pass
2227
David James8d201162013-10-11 17:03:19 -07002228 if name in to_symlink:
2229 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2230 elif copy_all and not os.path.islink(dst):
2231 if os.path.isdir(src):
2232 shutil.copytree(src, dst)
2233 elif os.path.isfile(src):
2234 shutil.copy(src, dst)
2235 except OSError as e:
2236 if e.errno == errno.EPERM:
2237 raise GitError('filesystem must support symlinks')
2238 else:
2239 raise
2240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002241 def _InitWorkTree(self):
2242 dotgit = os.path.join(self.worktree, '.git')
2243 if not os.path.exists(dotgit):
2244 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002245 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2246 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002247
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002248 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002249
2250 cmd = ['read-tree', '--reset', '-u']
2251 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002252 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002253 if GitCommand(self, cmd).Wait() != 0:
2254 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002255
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002256 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002257
2258 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002259 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002260
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002261 def _revlist(self, *args, **kw):
2262 a = []
2263 a.extend(args)
2264 a.append('--')
2265 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002266
2267 @property
2268 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002269 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002270
Julien Camperguedd654222014-01-09 16:21:37 +01002271 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2272 """Get logs between two revisions of this project."""
2273 comp = '..'
2274 if rev1:
2275 revs = [rev1]
2276 if rev2:
2277 revs.extend([comp, rev2])
2278 cmd = ['log', ''.join(revs)]
2279 out = DiffColoring(self.config)
2280 if out.is_on and color:
2281 cmd.append('--color')
2282 if oneline:
2283 cmd.append('--oneline')
2284
2285 try:
2286 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2287 if log.Wait() == 0:
2288 return log.stdout
2289 except GitError:
2290 # worktree may not exist if groups changed for example. In that case,
2291 # try in gitdir instead.
2292 if not os.path.exists(self.worktree):
2293 return self.bare_git.log(*cmd[1:])
2294 else:
2295 raise
2296 return None
2297
2298 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2299 """Get the list of logs from this revision to given revisionId"""
2300 logs = {}
2301 selfId = self.GetRevisionId(self._allrefs)
2302 toId = toProject.GetRevisionId(toProject._allrefs)
2303
2304 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2305 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2306 return logs
2307
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002308 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002309 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002310 self._project = project
2311 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002312 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002313
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002314 def LsOthers(self):
2315 p = GitCommand(self._project,
2316 ['ls-files',
2317 '-z',
2318 '--others',
2319 '--exclude-standard'],
2320 bare = False,
David James8d201162013-10-11 17:03:19 -07002321 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002322 capture_stdout = True,
2323 capture_stderr = True)
2324 if p.Wait() == 0:
2325 out = p.stdout
2326 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002327 return out[:-1].split('\0') # pylint: disable=W1401
2328 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002329 return []
2330
2331 def DiffZ(self, name, *args):
2332 cmd = [name]
2333 cmd.append('-z')
2334 cmd.extend(args)
2335 p = GitCommand(self._project,
2336 cmd,
David James8d201162013-10-11 17:03:19 -07002337 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002338 bare = False,
2339 capture_stdout = True,
2340 capture_stderr = True)
2341 try:
2342 out = p.process.stdout.read()
2343 r = {}
2344 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002345 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002346 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002347 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002348 info = next(out)
2349 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002350 except StopIteration:
2351 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002352
2353 class _Info(object):
2354 def __init__(self, path, omode, nmode, oid, nid, state):
2355 self.path = path
2356 self.src_path = None
2357 self.old_mode = omode
2358 self.new_mode = nmode
2359 self.old_id = oid
2360 self.new_id = nid
2361
2362 if len(state) == 1:
2363 self.status = state
2364 self.level = None
2365 else:
2366 self.status = state[:1]
2367 self.level = state[1:]
2368 while self.level.startswith('0'):
2369 self.level = self.level[1:]
2370
2371 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002372 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002373 if info.status in ('R', 'C'):
2374 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002375 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002376 r[info.path] = info
2377 return r
2378 finally:
2379 p.Wait()
2380
2381 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002382 if self._bare:
2383 path = os.path.join(self._project.gitdir, HEAD)
2384 else:
2385 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002386 try:
2387 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002388 except IOError as e:
2389 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002390 try:
2391 line = fd.read()
2392 finally:
2393 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302394 try:
2395 line = line.decode()
2396 except AttributeError:
2397 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002398 if line.startswith('ref: '):
2399 return line[5:-1]
2400 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002401
2402 def SetHead(self, ref, message=None):
2403 cmdv = []
2404 if message is not None:
2405 cmdv.extend(['-m', message])
2406 cmdv.append(HEAD)
2407 cmdv.append(ref)
2408 self.symbolic_ref(*cmdv)
2409
2410 def DetachHead(self, new, message=None):
2411 cmdv = ['--no-deref']
2412 if message is not None:
2413 cmdv.extend(['-m', message])
2414 cmdv.append(HEAD)
2415 cmdv.append(new)
2416 self.update_ref(*cmdv)
2417
2418 def UpdateRef(self, name, new, old=None,
2419 message=None,
2420 detach=False):
2421 cmdv = []
2422 if message is not None:
2423 cmdv.extend(['-m', message])
2424 if detach:
2425 cmdv.append('--no-deref')
2426 cmdv.append(name)
2427 cmdv.append(new)
2428 if old is not None:
2429 cmdv.append(old)
2430 self.update_ref(*cmdv)
2431
2432 def DeleteRef(self, name, old=None):
2433 if not old:
2434 old = self.rev_parse(name)
2435 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002436 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002437
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002438 def rev_list(self, *args, **kw):
2439 if 'format' in kw:
2440 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2441 else:
2442 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002443 cmdv.extend(args)
2444 p = GitCommand(self._project,
2445 cmdv,
2446 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002447 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002448 capture_stdout = True,
2449 capture_stderr = True)
2450 r = []
2451 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002452 if line[-1] == '\n':
2453 line = line[:-1]
2454 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002455 if p.Wait() != 0:
2456 raise GitError('%s rev-list %s: %s' % (
2457 self._project.name,
2458 str(args),
2459 p.stderr))
2460 return r
2461
2462 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002463 """Allow arbitrary git commands using pythonic syntax.
2464
2465 This allows you to do things like:
2466 git_obj.rev_parse('HEAD')
2467
2468 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2469 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002470 Any other positional arguments will be passed to the git command, and the
2471 following keyword arguments are supported:
2472 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002473
2474 Args:
2475 name: The name of the git command to call. Any '_' characters will
2476 be replaced with '-'.
2477
2478 Returns:
2479 A callable object that will try to call git with the named command.
2480 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002481 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002482 def runner(*args, **kwargs):
2483 cmdv = []
2484 config = kwargs.pop('config', None)
2485 for k in kwargs:
2486 raise TypeError('%s() got an unexpected keyword argument %r'
2487 % (name, k))
2488 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002489 if not git_require((1, 7, 2)):
2490 raise ValueError('cannot set config on command line for %s()'
2491 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302492 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002493 cmdv.append('-c')
2494 cmdv.append('%s=%s' % (k, v))
2495 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002496 cmdv.extend(args)
2497 p = GitCommand(self._project,
2498 cmdv,
2499 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002500 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002501 capture_stdout = True,
2502 capture_stderr = True)
2503 if p.Wait() != 0:
2504 raise GitError('%s %s: %s' % (
2505 self._project.name,
2506 name,
2507 p.stderr))
2508 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302509 try:
Conley Owensedd01512013-09-26 12:59:58 -07002510 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302511 except AttributeError:
2512 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002513 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2514 return r[:-1]
2515 return r
2516 return runner
2517
2518
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002519class _PriorSyncFailedError(Exception):
2520 def __str__(self):
2521 return 'prior sync failed; rebase still in progress'
2522
2523class _DirtyError(Exception):
2524 def __str__(self):
2525 return 'contains uncommitted changes'
2526
2527class _InfoMessage(object):
2528 def __init__(self, project, text):
2529 self.project = project
2530 self.text = text
2531
2532 def Print(self, syncbuf):
2533 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2534 syncbuf.out.nl()
2535
2536class _Failure(object):
2537 def __init__(self, project, why):
2538 self.project = project
2539 self.why = why
2540
2541 def Print(self, syncbuf):
2542 syncbuf.out.fail('error: %s/: %s',
2543 self.project.relpath,
2544 str(self.why))
2545 syncbuf.out.nl()
2546
2547class _Later(object):
2548 def __init__(self, project, action):
2549 self.project = project
2550 self.action = action
2551
2552 def Run(self, syncbuf):
2553 out = syncbuf.out
2554 out.project('project %s/', self.project.relpath)
2555 out.nl()
2556 try:
2557 self.action()
2558 out.nl()
2559 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002560 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002561 out.nl()
2562 return False
2563
2564class _SyncColoring(Coloring):
2565 def __init__(self, config):
2566 Coloring.__init__(self, config, 'reposync')
2567 self.project = self.printer('header', attr = 'bold')
2568 self.info = self.printer('info')
2569 self.fail = self.printer('fail', fg='red')
2570
2571class SyncBuffer(object):
2572 def __init__(self, config, detach_head=False):
2573 self._messages = []
2574 self._failures = []
2575 self._later_queue1 = []
2576 self._later_queue2 = []
2577
2578 self.out = _SyncColoring(config)
2579 self.out.redirect(sys.stderr)
2580
2581 self.detach_head = detach_head
2582 self.clean = True
2583
2584 def info(self, project, fmt, *args):
2585 self._messages.append(_InfoMessage(project, fmt % args))
2586
2587 def fail(self, project, err=None):
2588 self._failures.append(_Failure(project, err))
2589 self.clean = False
2590
2591 def later1(self, project, what):
2592 self._later_queue1.append(_Later(project, what))
2593
2594 def later2(self, project, what):
2595 self._later_queue2.append(_Later(project, what))
2596
2597 def Finish(self):
2598 self._PrintMessages()
2599 self._RunLater()
2600 self._PrintMessages()
2601 return self.clean
2602
2603 def _RunLater(self):
2604 for q in ['_later_queue1', '_later_queue2']:
2605 if not self._RunQueue(q):
2606 return
2607
2608 def _RunQueue(self, queue):
2609 for m in getattr(self, queue):
2610 if not m.Run(self):
2611 self.clean = False
2612 return False
2613 setattr(self, queue, [])
2614 return True
2615
2616 def _PrintMessages(self):
2617 for m in self._messages:
2618 m.Print(self)
2619 for m in self._failures:
2620 m.Print(self)
2621
2622 self._messages = []
2623 self._failures = []
2624
2625
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002626class MetaProject(Project):
2627 """A special project housed under .repo.
2628 """
2629 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002630 Project.__init__(self,
2631 manifest = manifest,
2632 name = name,
2633 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002634 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002635 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002636 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002637 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002638 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002639 revisionId = None,
2640 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002641
2642 def PreSync(self):
2643 if self.Exists:
2644 cb = self.CurrentBranch
2645 if cb:
2646 base = self.GetBranch(cb).merge
2647 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002648 self.revisionExpr = base
2649 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002650
Florian Vallee5d016502012-06-07 17:19:26 +02002651 def MetaBranchSwitch(self, target):
2652 """ Prepare MetaProject for manifest branch switch
2653 """
2654
2655 # detach and delete manifest branch, allowing a new
2656 # branch to take over
2657 syncbuf = SyncBuffer(self.config, detach_head = True)
2658 self.Sync_LocalHalf(syncbuf)
2659 syncbuf.Finish()
2660
2661 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002662 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002663 capture_stdout = True,
2664 capture_stderr = True).Wait() == 0
2665
2666
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002667 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002668 def LastFetch(self):
2669 try:
2670 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2671 return os.path.getmtime(fh)
2672 except OSError:
2673 return 0
2674
2675 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002676 def HasChanges(self):
2677 """Has the remote received new commits not yet checked out?
2678 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002679 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002680 return False
2681
David Pursehouse8a68ff92012-09-24 12:15:13 +09002682 all_refs = self.bare_ref.all
2683 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002684 head = self.work_git.GetHead()
2685 if head.startswith(R_HEADS):
2686 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002687 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002688 except KeyError:
2689 head = None
2690
2691 if revid == head:
2692 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002693 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002694 return True
2695 return False