blob: 316ce7ba1212442253d68eb4b3ba1fbaf206258f [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
Chirayu Desai303a82f2014-08-19 22:57:17 +053049 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070050 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".
Colin Cross5acde752012-03-28 20:15:45 -0700715
Conley Owens971de8e2012-04-16 10:36:08 -0700716 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"
Conley Owens971de8e2012-04-16 10:36:08 -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 ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700739 def UncommitedFiles(self, get_all=True):
740 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700742 Args:
743 get_all: a boolean, if True - get information about all different
744 uncommitted files. If False - return as soon as any kind of
745 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500746 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700747 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500748 self.work_git.update_index('-q',
749 '--unmerged',
750 '--ignore-missing',
751 '--refresh')
752 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700753 details.append("rebase in progress")
754 if not get_all:
755 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500756
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700757 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
758 if changes:
759 details.extend(changes)
760 if not get_all:
761 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500762
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700763 changes = self.work_git.DiffZ('diff-files').keys()
764 if changes:
765 details.extend(changes)
766 if not get_all:
767 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500768
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700769 changes = self.work_git.LsOthers()
770 if changes:
771 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500772
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700773 return details
774
775 def HasChanges(self):
776 """Returns true if there are uncommitted changes.
777 """
778 if self.UncommitedFiles(get_all=False):
779 return True
780 else:
781 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500782
Terence Haddock4655e812011-03-31 12:33:34 +0200783 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200785
786 Args:
787 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788 """
789 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200790 if output_redir == None:
791 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700792 print(file=output_redir)
793 print('project %s/' % self.relpath, file=output_redir)
794 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700795 return
796
797 self.work_git.update_index('-q',
798 '--unmerged',
799 '--ignore-missing',
800 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700801 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700802 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
803 df = self.work_git.DiffZ('diff-files')
804 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100805 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700806 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807
808 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200809 if not output_redir == None:
810 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811 out.project('project %-40s', self.relpath + '/')
812
813 branch = self.CurrentBranch
814 if branch is None:
815 out.nobranch('(*** NO BRANCH ***)')
816 else:
817 out.branch('branch %s', branch)
818 out.nl()
819
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700820 if rb:
821 out.important('prior sync failed; rebase still in progress')
822 out.nl()
823
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824 paths = list()
825 paths.extend(di.keys())
826 paths.extend(df.keys())
827 paths.extend(do)
828
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530829 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900830 try:
831 i = di[p]
832 except KeyError:
833 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900835 try:
836 f = df[p]
837 except KeyError:
838 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200839
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900840 if i:
841 i_status = i.status.upper()
842 else:
843 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900845 if f:
846 f_status = f.status.lower()
847 else:
848 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849
850 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800851 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700852 i.src_path, p, i.level)
853 else:
854 line = ' %s%s\t%s' % (i_status, f_status, p)
855
856 if i and not f:
857 out.added('%s', line)
858 elif (i and f) or (not i and f):
859 out.changed('%s', line)
860 elif not i and not f:
861 out.untracked('%s', line)
862 else:
863 out.write('%s', line)
864 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200865
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700866 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867
pelyad67872d2012-03-28 14:49:58 +0300868 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 """Prints the status of the repository to stdout.
870 """
871 out = DiffColoring(self.config)
872 cmd = ['diff']
873 if out.is_on:
874 cmd.append('--color')
875 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300876 if absolute_paths:
877 cmd.append('--src-prefix=a/%s/' % self.relpath)
878 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879 cmd.append('--')
880 p = GitCommand(self,
881 cmd,
882 capture_stdout = True,
883 capture_stderr = True)
884 has_diff = False
885 for line in p.process.stdout:
886 if not has_diff:
887 out.nl()
888 out.project('project %s/' % self.relpath)
889 out.nl()
890 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700891 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892 p.Wait()
893
894
895## Publish / Upload ##
896
David Pursehouse8a68ff92012-09-24 12:15:13 +0900897 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898 """Was the branch published (uploaded) for code review?
899 If so, returns the SHA-1 hash of the last published
900 state for the branch.
901 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700902 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900903 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700904 try:
905 return self.bare_git.rev_parse(key)
906 except GitError:
907 return None
908 else:
909 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900910 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700911 except KeyError:
912 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913
David Pursehouse8a68ff92012-09-24 12:15:13 +0900914 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700915 """Prunes any stale published refs.
916 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900917 if all_refs is None:
918 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919 heads = set()
920 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530921 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 if name.startswith(R_HEADS):
923 heads.add(name)
924 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900925 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530927 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928 n = name[len(R_PUB):]
929 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900930 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700932 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933 """List any branches which can be uploaded for review.
934 """
935 heads = {}
936 pubed = {}
937
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530938 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900940 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900942 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943
944 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530945 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900946 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700948 if selected_branch and branch != selected_branch:
949 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800951 rb = self.GetUploadableBranch(branch)
952 if rb:
953 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954 return ready
955
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800956 def GetUploadableBranch(self, branch_name):
957 """Get a single uploadable branch, or None.
958 """
959 branch = self.GetBranch(branch_name)
960 base = branch.LocalMerge
961 if branch.LocalMerge:
962 rb = ReviewableBranch(self, branch, base)
963 if rb.commits:
964 return rb
965 return None
966
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700967 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700968 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700969 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400970 draft=False,
971 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 """Uploads the named branch for code review.
973 """
974 if branch is None:
975 branch = self.CurrentBranch
976 if branch is None:
977 raise GitError('not currently on a branch')
978
979 branch = self.GetBranch(branch)
980 if not branch.LocalMerge:
981 raise GitError('branch %s does not track a remote' % branch.name)
982 if not branch.remote.review:
983 raise GitError('remote %s has no review url' % branch.remote.name)
984
Bryan Jacobsf609f912013-05-06 13:36:24 -0400985 if dest_branch is None:
986 dest_branch = self.dest_branch
987 if dest_branch is None:
988 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 if not dest_branch.startswith(R_HEADS):
990 dest_branch = R_HEADS + dest_branch
991
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800992 if not branch.remote.projectname:
993 branch.remote.projectname = self.name
994 branch.remote.Save()
995
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800996 url = branch.remote.ReviewUrl(self.UserEmail)
997 if url is None:
998 raise UploadError('review not configured')
999 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001000
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001001 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001002 rp = ['gerrit receive-pack']
1003 for e in people[0]:
1004 rp.append('--reviewer=%s' % sq(e))
1005 for e in people[1]:
1006 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001007 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001008
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001009 cmd.append(url)
1010
1011 if dest_branch.startswith(R_HEADS):
1012 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001013
1014 upload_type = 'for'
1015 if draft:
1016 upload_type = 'drafts'
1017
1018 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1019 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001020 if auto_topic:
1021 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001022 if not url.startswith('ssh://'):
1023 rp = ['r=%s' % p for p in people[0]] + \
1024 ['cc=%s' % p for p in people[1]]
1025 if rp:
1026 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001027 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001028
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001029 if GitCommand(self, cmd, bare = True).Wait() != 0:
1030 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031
1032 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1033 self.bare_git.UpdateRef(R_PUB + branch.name,
1034 R_HEADS + branch.name,
1035 message = msg)
1036
1037
1038## Sync ##
1039
Julien Campergue335f5ef2013-10-16 11:02:35 +02001040 def _ExtractArchive(self, tarpath, path=None):
1041 """Extract the given tar on its current location
1042
1043 Args:
1044 - tarpath: The path to the actual tar file
1045
1046 """
1047 try:
1048 with tarfile.open(tarpath, 'r') as tar:
1049 tar.extractall(path=path)
1050 return True
1051 except (IOError, tarfile.TarError) as e:
1052 print("error: Cannot extract archive %s: "
1053 "%s" % (tarpath, str(e)), file=sys.stderr)
1054 return False
1055
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001056 def Sync_NetworkHalf(self,
1057 quiet=False,
1058 is_new=None,
1059 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001060 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001061 no_tags=False,
1062 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063 """Perform only the network IO portion of the sync process.
1064 Local working directory/branch state is not affected.
1065 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001066 if archive and not isinstance(self, MetaProject):
1067 if self.remote.url.startswith(('http://', 'https://')):
1068 print("error: %s: Cannot fetch archives from http/https "
1069 "remotes." % self.name, file=sys.stderr)
1070 return False
1071
1072 name = self.relpath.replace('\\', '/')
1073 name = name.replace('/', '_')
1074 tarpath = '%s.tar' % name
1075 topdir = self.manifest.topdir
1076
1077 try:
1078 self._FetchArchive(tarpath, cwd=topdir)
1079 except GitError as e:
1080 print('error: %s' % str(e), file=sys.stderr)
1081 return False
1082
1083 # From now on, we only need absolute tarpath
1084 tarpath = os.path.join(topdir, tarpath)
1085
1086 if not self._ExtractArchive(tarpath, path=topdir):
1087 return False
1088 try:
1089 os.remove(tarpath)
1090 except OSError as e:
1091 print("warn: Cannot remove archive %s: "
1092 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001093 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001094 return True
1095
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001096 if is_new is None:
1097 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001098 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001099 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001100 else:
1101 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001103
1104 if is_new:
1105 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1106 try:
1107 fd = open(alt, 'rb')
1108 try:
1109 alt_dir = fd.readline().rstrip()
1110 finally:
1111 fd.close()
1112 except IOError:
1113 alt_dir = None
1114 else:
1115 alt_dir = None
1116
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001117 if clone_bundle \
1118 and alt_dir is None \
1119 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001120 is_new = False
1121
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001122 if not current_branch_only:
1123 if self.sync_c:
1124 current_branch_only = True
1125 elif not self.manifest._loaded:
1126 # Manifest cannot check defaults until it syncs.
1127 current_branch_only = False
1128 elif self.manifest.default.sync_c:
1129 current_branch_only = True
1130
Conley Owens666d5342014-05-01 13:09:57 -07001131 has_sha1 = ID_RE.match(self.revisionExpr) and self._CheckForSha1()
1132 if (not has_sha1 #Need to fetch since we don't already have this revision
1133 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1134 current_branch_only=current_branch_only,
1135 no_tags=no_tags)):
1136 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001137
1138 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001139 self._InitMRef()
1140 else:
1141 self._InitMirrorHead()
1142 try:
1143 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1144 except OSError:
1145 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001147
1148 def PostRepoUpgrade(self):
1149 self._InitHooks()
1150
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001151 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001152 for copyfile in self.copyfiles:
1153 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001154 for linkfile in self.linkfiles:
1155 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001156
Julien Camperguedd654222014-01-09 16:21:37 +01001157 def GetCommitRevisionId(self):
1158 """Get revisionId of a commit.
1159
1160 Use this method instead of GetRevisionId to get the id of the commit rather
1161 than the id of the current git object (for example, a tag)
1162
1163 """
1164 if not self.revisionExpr.startswith(R_TAGS):
1165 return self.GetRevisionId(self._allrefs)
1166
1167 try:
1168 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1169 except GitError:
1170 raise ManifestInvalidRevisionError(
1171 'revision %s in %s not found' % (self.revisionExpr,
1172 self.name))
1173
David Pursehouse8a68ff92012-09-24 12:15:13 +09001174 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001175 if self.revisionId:
1176 return self.revisionId
1177
1178 rem = self.GetRemote(self.remote.name)
1179 rev = rem.ToLocal(self.revisionExpr)
1180
David Pursehouse8a68ff92012-09-24 12:15:13 +09001181 if all_refs is not None and rev in all_refs:
1182 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001183
1184 try:
1185 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1186 except GitError:
1187 raise ManifestInvalidRevisionError(
1188 'revision %s in %s not found' % (self.revisionExpr,
1189 self.name))
1190
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001191 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192 """Perform only the local IO portion of the sync process.
1193 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194 """
David James8d201162013-10-11 17:03:19 -07001195 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001196 all_refs = self.bare_ref.all
1197 self.CleanPublishedCache(all_refs)
1198 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001199
David Pursehouse1d947b32012-10-25 12:23:11 +09001200 def _doff():
1201 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001202 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001203
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001204 head = self.work_git.GetHead()
1205 if head.startswith(R_HEADS):
1206 branch = head[len(R_HEADS):]
1207 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001208 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001209 except KeyError:
1210 head = None
1211 else:
1212 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001214 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001215 # Currently on a detached HEAD. The user is assumed to
1216 # not have any local modifications worth worrying about.
1217 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001218 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001219 syncbuf.fail(self, _PriorSyncFailedError())
1220 return
1221
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001222 if head == revid:
1223 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001224 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001225 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001226 if not syncbuf.detach_head:
1227 return
1228 else:
1229 lost = self._revlist(not_rev(revid), HEAD)
1230 if lost:
1231 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001232
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. Pearcefbcde472009-04-17 20:58:02 -07001241 if head == revid:
1242 # No changes; don't do anything further.
1243 #
1244 return
1245
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001246 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001248 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001249 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001250 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001251 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001252 syncbuf.info(self,
1253 "leaving %s; does not track upstream",
1254 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001255 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001256 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001257 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001258 syncbuf.fail(self, e)
1259 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001260 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001261 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001262
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001263 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001264 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001265 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001266 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001267 if not_merged:
1268 if upstream_gain:
1269 # The user has published this branch and some of those
1270 # commits are not yet merged upstream. We do not want
1271 # to rewrite the published commits so we punt.
1272 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001273 syncbuf.fail(self,
1274 "branch %s is published (but not merged) and is now %d commits behind"
1275 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001276 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001277 elif pub == head:
1278 # All published commits are merged, and thus we are a
1279 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001280 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001281 syncbuf.later1(self, _doff)
1282 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001283
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001284 # Examine the local commits not in the remote. Find the
1285 # last one attributed to this user, if any.
1286 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001287 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001288 last_mine = None
1289 cnt_mine = 0
1290 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301291 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001292 if committer_email == self.UserEmail:
1293 last_mine = commit_id
1294 cnt_mine += 1
1295
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001296 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001297 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298
1299 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001300 syncbuf.fail(self, _DirtyError())
1301 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001303 # If the upstream switched on us, warn the user.
1304 #
1305 if branch.merge != self.revisionExpr:
1306 if branch.merge and self.revisionExpr:
1307 syncbuf.info(self,
1308 'manifest switched %s...%s',
1309 branch.merge,
1310 self.revisionExpr)
1311 elif branch.merge:
1312 syncbuf.info(self,
1313 'manifest no longer tracks %s',
1314 branch.merge)
1315
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001316 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001318 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001320 syncbuf.info(self,
1321 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001322 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001324 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001325 if not ID_RE.match(self.revisionExpr):
1326 # in case of manifest sync the revisionExpr might be a SHA1
1327 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328 branch.Save()
1329
Mike Pontillod3153822012-02-28 11:53:24 -08001330 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001331 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001332 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001333 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001334 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001335 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001336 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001337 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001338 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001339 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001340 syncbuf.fail(self, e)
1341 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001343 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001345 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001346 # dest should already be an absolute path, but src is project relative
1347 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001348 abssrc = os.path.join(self.worktree, src)
1349 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001351 def AddLinkFile(self, src, dest, absdest):
1352 # dest should already be an absolute path, but src is project relative
1353 # make src an absolute path
1354 abssrc = os.path.join(self.worktree, src)
1355 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1356
James W. Mills24c13082012-04-12 15:04:13 -05001357 def AddAnnotation(self, name, value, keep):
1358 self.annotations.append(_Annotation(name, value, keep))
1359
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001360 def DownloadPatchSet(self, change_id, patch_id):
1361 """Download a single patch set of a single change to FETCH_HEAD.
1362 """
1363 remote = self.GetRemote(self.remote.name)
1364
1365 cmd = ['fetch', remote.name]
1366 cmd.append('refs/changes/%2.2d/%d/%d' \
1367 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001368 if GitCommand(self, cmd, bare=True).Wait() != 0:
1369 return None
1370 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001371 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001372 change_id,
1373 patch_id,
1374 self.bare_git.rev_parse('FETCH_HEAD'))
1375
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376
1377## Branch Management ##
1378
1379 def StartBranch(self, name):
1380 """Create a new branch off the manifest's revision.
1381 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001382 head = self.work_git.GetHead()
1383 if head == (R_HEADS + name):
1384 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385
David Pursehouse8a68ff92012-09-24 12:15:13 +09001386 all_refs = self.bare_ref.all
1387 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001388 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001389 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001390 capture_stdout = True,
1391 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001392
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001393 branch = self.GetBranch(name)
1394 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001395 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001396 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001397
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001398 if head.startswith(R_HEADS):
1399 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001400 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001401 except KeyError:
1402 head = None
1403
1404 if revid and head and revid == head:
1405 ref = os.path.join(self.gitdir, R_HEADS + name)
1406 try:
1407 os.makedirs(os.path.dirname(ref))
1408 except OSError:
1409 pass
1410 _lwrite(ref, '%s\n' % revid)
1411 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1412 'ref: %s%s\n' % (R_HEADS, name))
1413 branch.Save()
1414 return True
1415
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001416 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001417 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001418 capture_stdout = True,
1419 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001420 branch.Save()
1421 return True
1422 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001423
Wink Saville02d79452009-04-10 13:01:24 -07001424 def CheckoutBranch(self, name):
1425 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001426
1427 Args:
1428 name: The name of the branch to checkout.
1429
1430 Returns:
1431 True if the checkout succeeded; False if it didn't; None if the branch
1432 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001433 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001434 rev = R_HEADS + name
1435 head = self.work_git.GetHead()
1436 if head == rev:
1437 # Already on the branch
1438 #
1439 return True
Wink Saville02d79452009-04-10 13:01:24 -07001440
David Pursehouse8a68ff92012-09-24 12:15:13 +09001441 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001442 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001443 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001444 except KeyError:
1445 # Branch does not exist in this project
1446 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001447 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001448
1449 if head.startswith(R_HEADS):
1450 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001451 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001452 except KeyError:
1453 head = None
1454
1455 if head == revid:
1456 # Same revision; just update HEAD to point to the new
1457 # target branch, but otherwise take no other action.
1458 #
1459 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1460 'ref: %s%s\n' % (R_HEADS, name))
1461 return True
Wink Saville02d79452009-04-10 13:01:24 -07001462
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001463 return GitCommand(self,
1464 ['checkout', name, '--'],
1465 capture_stdout = True,
1466 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001467
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001468 def AbandonBranch(self, name):
1469 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001470
1471 Args:
1472 name: The name of the branch to abandon.
1473
1474 Returns:
1475 True if the abandon succeeded; False if it didn't; None if the branch
1476 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001477 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001478 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001479 all_refs = self.bare_ref.all
1480 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001481 # Doesn't exist
1482 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001483
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001484 head = self.work_git.GetHead()
1485 if head == rev:
1486 # We can't destroy the branch while we are sitting
1487 # on it. Switch to a detached HEAD.
1488 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001489 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001490
David Pursehouse8a68ff92012-09-24 12:15:13 +09001491 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001492 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001493 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1494 '%s\n' % revid)
1495 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001496 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001497
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001498 return GitCommand(self,
1499 ['branch', '-D', name],
1500 capture_stdout = True,
1501 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001502
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001503 def PruneHeads(self):
1504 """Prune any topic branches already merged into upstream.
1505 """
1506 cb = self.CurrentBranch
1507 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001508 left = self._allrefs
1509 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001510 if name.startswith(R_HEADS):
1511 name = name[len(R_HEADS):]
1512 if cb is None or name != cb:
1513 kill.append(name)
1514
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001515 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001516 if cb is not None \
1517 and not self._revlist(HEAD + '...' + rev) \
1518 and not self.IsDirty(consider_untracked = False):
1519 self.work_git.DetachHead(HEAD)
1520 kill.append(cb)
1521
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001522 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001523 old = self.bare_git.GetHead()
1524 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001525 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1526
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001527 try:
1528 self.bare_git.DetachHead(rev)
1529
1530 b = ['branch', '-d']
1531 b.extend(kill)
1532 b = GitCommand(self, b, bare=True,
1533 capture_stdout=True,
1534 capture_stderr=True)
1535 b.Wait()
1536 finally:
1537 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001538 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001539
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001540 for branch in kill:
1541 if (R_HEADS + branch) not in left:
1542 self.CleanPublishedCache()
1543 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001544
1545 if cb and cb not in kill:
1546 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001547 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001548
1549 kept = []
1550 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001551 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001552 branch = self.GetBranch(branch)
1553 base = branch.LocalMerge
1554 if not base:
1555 base = rev
1556 kept.append(ReviewableBranch(self, branch, base))
1557 return kept
1558
1559
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001560## Submodule Management ##
1561
1562 def GetRegisteredSubprojects(self):
1563 result = []
1564 def rec(subprojects):
1565 if not subprojects:
1566 return
1567 result.extend(subprojects)
1568 for p in subprojects:
1569 rec(p.subprojects)
1570 rec(self.subprojects)
1571 return result
1572
1573 def _GetSubmodules(self):
1574 # Unfortunately we cannot call `git submodule status --recursive` here
1575 # because the working tree might not exist yet, and it cannot be used
1576 # without a working tree in its current implementation.
1577
1578 def get_submodules(gitdir, rev):
1579 # Parse .gitmodules for submodule sub_paths and sub_urls
1580 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1581 if not sub_paths:
1582 return []
1583 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1584 # revision of submodule repository
1585 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1586 submodules = []
1587 for sub_path, sub_url in zip(sub_paths, sub_urls):
1588 try:
1589 sub_rev = sub_revs[sub_path]
1590 except KeyError:
1591 # Ignore non-exist submodules
1592 continue
1593 submodules.append((sub_rev, sub_path, sub_url))
1594 return submodules
1595
1596 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1597 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1598 def parse_gitmodules(gitdir, rev):
1599 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1600 try:
1601 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1602 bare = True, gitdir = gitdir)
1603 except GitError:
1604 return [], []
1605 if p.Wait() != 0:
1606 return [], []
1607
1608 gitmodules_lines = []
1609 fd, temp_gitmodules_path = tempfile.mkstemp()
1610 try:
1611 os.write(fd, p.stdout)
1612 os.close(fd)
1613 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1614 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1615 bare = True, gitdir = gitdir)
1616 if p.Wait() != 0:
1617 return [], []
1618 gitmodules_lines = p.stdout.split('\n')
1619 except GitError:
1620 return [], []
1621 finally:
1622 os.remove(temp_gitmodules_path)
1623
1624 names = set()
1625 paths = {}
1626 urls = {}
1627 for line in gitmodules_lines:
1628 if not line:
1629 continue
1630 m = re_path.match(line)
1631 if m:
1632 names.add(m.group(1))
1633 paths[m.group(1)] = m.group(2)
1634 continue
1635 m = re_url.match(line)
1636 if m:
1637 names.add(m.group(1))
1638 urls[m.group(1)] = m.group(2)
1639 continue
1640 names = sorted(names)
1641 return ([paths.get(name, '') for name in names],
1642 [urls.get(name, '') for name in names])
1643
1644 def git_ls_tree(gitdir, rev, paths):
1645 cmd = ['ls-tree', rev, '--']
1646 cmd.extend(paths)
1647 try:
1648 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1649 bare = True, gitdir = gitdir)
1650 except GitError:
1651 return []
1652 if p.Wait() != 0:
1653 return []
1654 objects = {}
1655 for line in p.stdout.split('\n'):
1656 if not line.strip():
1657 continue
1658 object_rev, object_path = line.split()[2:4]
1659 objects[object_path] = object_rev
1660 return objects
1661
1662 try:
1663 rev = self.GetRevisionId()
1664 except GitError:
1665 return []
1666 return get_submodules(self.gitdir, rev)
1667
1668 def GetDerivedSubprojects(self):
1669 result = []
1670 if not self.Exists:
1671 # If git repo does not exist yet, querying its submodules will
1672 # mess up its states; so return here.
1673 return result
1674 for rev, path, url in self._GetSubmodules():
1675 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001676 relpath, worktree, gitdir, objdir = \
1677 self.manifest.GetSubprojectPaths(self, name, path)
1678 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001679 if project:
1680 result.extend(project.GetDerivedSubprojects())
1681 continue
David James8d201162013-10-11 17:03:19 -07001682
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001683 remote = RemoteSpec(self.remote.name,
1684 url = url,
Anthony King36ea2fb2014-05-06 11:54:01 +01001685 review = self.remote.review,
1686 revision = self.remote.revision)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001687 subproject = Project(manifest = self.manifest,
1688 name = name,
1689 remote = remote,
1690 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001691 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001692 worktree = worktree,
1693 relpath = relpath,
1694 revisionExpr = self.revisionExpr,
1695 revisionId = rev,
1696 rebase = self.rebase,
1697 groups = self.groups,
1698 sync_c = self.sync_c,
1699 sync_s = self.sync_s,
1700 parent = self,
1701 is_derived = True)
1702 result.append(subproject)
1703 result.extend(subproject.GetDerivedSubprojects())
1704 return result
1705
1706
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001707## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001708 def _CheckForSha1(self):
1709 try:
1710 # if revision (sha or tag) is not present then following function
1711 # throws an error.
1712 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1713 return True
1714 except GitError:
1715 # There is no such persistent revision. We have to fetch it.
1716 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001717
Julien Campergue335f5ef2013-10-16 11:02:35 +02001718 def _FetchArchive(self, tarpath, cwd=None):
1719 cmd = ['archive', '-v', '-o', tarpath]
1720 cmd.append('--remote=%s' % self.remote.url)
1721 cmd.append('--prefix=%s/' % self.relpath)
1722 cmd.append(self.revisionExpr)
1723
1724 command = GitCommand(self, cmd, cwd=cwd,
1725 capture_stdout=True,
1726 capture_stderr=True)
1727
1728 if command.Wait() != 0:
1729 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1730
Conley Owens80b87fe2014-05-09 17:13:44 -07001731
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001732 def _RemoteFetch(self, name=None,
1733 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001734 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001735 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001736 alt_dir=None,
1737 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001738
1739 is_sha1 = False
1740 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001741 depth = None
1742
1743 # The depth should not be used when fetching to a mirror because
1744 # it will result in a shallow repository that cannot be cloned or
1745 # fetched from.
1746 if not self.manifest.IsMirror:
1747 if self.clone_depth:
1748 depth = self.clone_depth
1749 else:
1750 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001751
Shawn Pearce69e04d82014-01-29 12:48:54 -08001752 if depth:
1753 current_branch_only = True
1754
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001755 if current_branch_only:
1756 if ID_RE.match(self.revisionExpr) is not None:
1757 is_sha1 = True
1758 elif self.revisionExpr.startswith(R_TAGS):
1759 # this is a tag and its sha1 value should never change
1760 tag_name = self.revisionExpr[len(R_TAGS):]
1761
1762 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001763 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001764 return True
Brian Harring14a66742012-09-28 20:21:57 -07001765 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1766 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001767
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768 if not name:
1769 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001770
1771 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001772 remote = self.GetRemote(name)
1773 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001774 ssh_proxy = True
1775
Shawn O. Pearce88443382010-10-08 10:02:09 +02001776 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001777 if alt_dir and 'objects' == os.path.basename(alt_dir):
1778 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001779 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1780 remote = self.GetRemote(name)
1781
David Pursehouse8a68ff92012-09-24 12:15:13 +09001782 all_refs = self.bare_ref.all
1783 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001784 tmp = set()
1785
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301786 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001787 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001788 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001789 all_refs[r] = ref_id
1790 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001791 continue
1792
David Pursehouse8a68ff92012-09-24 12:15:13 +09001793 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001794 continue
1795
David Pursehouse8a68ff92012-09-24 12:15:13 +09001796 r = 'refs/_alt/%s' % ref_id
1797 all_refs[r] = ref_id
1798 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001799 tmp.add(r)
1800
Shawn O. Pearce88443382010-10-08 10:02:09 +02001801 tmp_packed = ''
1802 old_packed = ''
1803
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301804 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001805 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001806 tmp_packed += line
1807 if r not in tmp:
1808 old_packed += line
1809
1810 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001811 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001812 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001813
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001814 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001815
1816 # The --depth option only affects the initial fetch; after that we'll do
1817 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001818 if depth and initial:
1819 cmd.append('--depth=%s' % depth)
1820
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001821 if quiet:
1822 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001823 if not self.worktree:
1824 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001825 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001826
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001827 # If using depth then we should not get all the tags since they may
1828 # be outside of the depth.
1829 if no_tags or depth:
1830 cmd.append('--no-tags')
1831 else:
1832 cmd.append('--tags')
1833
Conley Owens80b87fe2014-05-09 17:13:44 -07001834 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001835 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001836 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001837 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001838 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001839 spec.append('tag')
1840 spec.append(tag_name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001841 else:
1842 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001843 if is_sha1:
1844 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001845 if branch.startswith(R_HEADS):
1846 branch = branch[len(R_HEADS):]
Conley Owens80b87fe2014-05-09 17:13:44 -07001847 spec.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
1848 cmd.extend(spec)
1849
1850 shallowfetch = self.config.GetString('repo.shallowfetch')
1851 if shallowfetch and shallowfetch != ' '.join(spec):
1852 GitCommand(self, ['fetch', '--unshallow', name] + shallowfetch.split(),
1853 bare=True, ssh_proxy=ssh_proxy).Wait()
1854 if depth:
1855 self.config.SetString('repo.shallowfetch', ' '.join(spec))
1856 else:
1857 self.config.SetString('repo.shallowfetch', None)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001858
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001859 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001860 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001861 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1862 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001863 ok = True
1864 break
Brian Harring14a66742012-09-28 20:21:57 -07001865 elif current_branch_only and is_sha1 and ret == 128:
1866 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1867 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1868 # abort the optimization attempt and do a full sync.
1869 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001870 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001871
1872 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001873 # Ensure that some refs exist. Otherwise, we probably aren't looking
1874 # at a real git repository and may have a bad url.
1875 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001876 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001877
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001878 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001879 if old_packed != '':
1880 _lwrite(packed_refs, old_packed)
1881 else:
1882 os.remove(packed_refs)
1883 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001884
1885 if is_sha1 and current_branch_only and self.upstream:
1886 # We just synced the upstream given branch; verify we
1887 # got what we wanted, else trigger a second run of all
1888 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001889 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001890 return self._RemoteFetch(name=name, current_branch_only=False,
1891 initial=False, quiet=quiet, alt_dir=alt_dir)
1892
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001893 return ok
1894
1895 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001896 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001897 return False
1898
1899 remote = self.GetRemote(self.remote.name)
1900 bundle_url = remote.url + '/clone.bundle'
1901 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001902 if GetSchemeFromUrl(bundle_url) not in (
1903 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001904 return False
1905
1906 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1907 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001908
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001909 exist_dst = os.path.exists(bundle_dst)
1910 exist_tmp = os.path.exists(bundle_tmp)
1911
1912 if not initial and not exist_dst and not exist_tmp:
1913 return False
1914
1915 if not exist_dst:
1916 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1917 if not exist_dst:
1918 return False
1919
1920 cmd = ['fetch']
1921 if quiet:
1922 cmd.append('--quiet')
1923 if not self.worktree:
1924 cmd.append('--update-head-ok')
1925 cmd.append(bundle_dst)
1926 for f in remote.fetch:
1927 cmd.append(str(f))
1928 cmd.append('refs/tags/*:refs/tags/*')
1929
1930 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001931 if os.path.exists(bundle_dst):
1932 os.remove(bundle_dst)
1933 if os.path.exists(bundle_tmp):
1934 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001935 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001936
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001937 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001938 if os.path.exists(dstPath):
1939 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001940
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001941 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001942 if quiet:
1943 cmd += ['--silent']
1944 if os.path.exists(tmpPath):
1945 size = os.stat(tmpPath).st_size
1946 if size >= 1024:
1947 cmd += ['--continue-at', '%d' % (size,)]
1948 else:
1949 os.remove(tmpPath)
1950 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1951 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001952 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001953 if cookiefile:
1954 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001955 if srcUrl.startswith('persistent-'):
1956 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001957 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001958
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001959 if IsTrace():
1960 Trace('%s', ' '.join(cmd))
1961 try:
1962 proc = subprocess.Popen(cmd)
1963 except OSError:
1964 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001965
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001966 curlret = proc.wait()
1967
1968 if curlret == 22:
1969 # From curl man page:
1970 # 22: HTTP page not retrieved. The requested url was not found or
1971 # returned another error with the HTTP error code being 400 or above.
1972 # This return code only appears if -f, --fail is used.
1973 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001974 print("Server does not provide clone.bundle; ignoring.",
1975 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001976 return False
1977
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001978 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001979 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001980 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001981 return True
1982 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001983 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001984 return False
1985 else:
1986 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001987
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001988 def _IsValidBundle(self, path):
1989 try:
1990 with open(path) as f:
1991 if f.read(16) == '# v2 git bundle\n':
1992 return True
1993 else:
1994 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1995 return False
1996 except OSError:
1997 return False
1998
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001999 def _GetBundleCookieFile(self, url):
2000 if url.startswith('persistent-'):
2001 try:
2002 p = subprocess.Popen(
2003 ['git-remote-persistent-https', '-print_config', url],
2004 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2005 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07002006 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002007 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07002008 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002009 for line in p.stdout:
2010 line = line.strip()
2011 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07002012 cookiefile = line[len(prefix):]
2013 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002014 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08002015 err_msg = p.stderr.read()
2016 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002017 pass # Persistent proxy doesn't support -print_config.
2018 else:
Conley Owenscbc07982013-11-21 10:38:03 -08002019 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07002020 if cookiefile:
2021 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002022 except OSError as e:
2023 if e.errno == errno.ENOENT:
2024 pass # No persistent proxy.
2025 raise
2026 return GitConfig.ForUser().GetString('http.cookiefile')
2027
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002028 def _Checkout(self, rev, quiet=False):
2029 cmd = ['checkout']
2030 if quiet:
2031 cmd.append('-q')
2032 cmd.append(rev)
2033 cmd.append('--')
2034 if GitCommand(self, cmd).Wait() != 0:
2035 if self._allrefs:
2036 raise GitError('%s checkout %s ' % (self.name, rev))
2037
Pierre Tardye5a21222011-03-24 16:28:18 +01002038 def _CherryPick(self, rev, quiet=False):
2039 cmd = ['cherry-pick']
2040 cmd.append(rev)
2041 cmd.append('--')
2042 if GitCommand(self, cmd).Wait() != 0:
2043 if self._allrefs:
2044 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2045
Erwan Mahea94f1622011-08-19 13:56:09 +02002046 def _Revert(self, rev, quiet=False):
2047 cmd = ['revert']
2048 cmd.append('--no-edit')
2049 cmd.append(rev)
2050 cmd.append('--')
2051 if GitCommand(self, cmd).Wait() != 0:
2052 if self._allrefs:
2053 raise GitError('%s revert %s ' % (self.name, rev))
2054
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002055 def _ResetHard(self, rev, quiet=True):
2056 cmd = ['reset', '--hard']
2057 if quiet:
2058 cmd.append('-q')
2059 cmd.append(rev)
2060 if GitCommand(self, cmd).Wait() != 0:
2061 raise GitError('%s reset --hard %s ' % (self.name, rev))
2062
2063 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002064 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002065 if onto is not None:
2066 cmd.extend(['--onto', onto])
2067 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002068 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002069 raise GitError('%s rebase %s ' % (self.name, upstream))
2070
Pierre Tardy3d125942012-05-04 12:18:12 +02002071 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002072 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002073 if ffonly:
2074 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002075 if GitCommand(self, cmd).Wait() != 0:
2076 raise GitError('%s merge %s ' % (self.name, head))
2077
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002078 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002079 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002080
2081 # Initialize the bare repository, which contains all of the objects.
2082 if not os.path.exists(self.objdir):
2083 os.makedirs(self.objdir)
2084 self.bare_objdir.init()
2085
2086 # If we have a separate directory to hold refs, initialize it as well.
2087 if self.objdir != self.gitdir:
2088 os.makedirs(self.gitdir)
2089 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2090 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002091
Shawn O. Pearce88443382010-10-08 10:02:09 +02002092 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002093 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002094
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002095 if ref_dir or mirror_git:
2096 if not mirror_git:
2097 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002098 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2099 self.relpath + '.git')
2100
2101 if os.path.exists(mirror_git):
2102 ref_dir = mirror_git
2103
2104 elif os.path.exists(repo_git):
2105 ref_dir = repo_git
2106
2107 else:
2108 ref_dir = None
2109
2110 if ref_dir:
2111 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2112 os.path.join(ref_dir, 'objects') + '\n')
2113
Jimmie Westera0444582012-10-24 13:44:42 +02002114 self._UpdateHooks()
2115
2116 m = self.manifest.manifestProject.config
2117 for key in ['user.name', 'user.email']:
2118 if m.Has(key, include_defaults = False):
2119 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002120 if self.manifest.IsMirror:
2121 self.config.SetString('core.bare', 'true')
2122 else:
2123 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002124
Jimmie Westera0444582012-10-24 13:44:42 +02002125 def _UpdateHooks(self):
2126 if os.path.exists(self.gitdir):
2127 # Always recreate hooks since they can have been changed
2128 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002129 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002130 try:
2131 to_rm = os.listdir(hooks)
2132 except OSError:
2133 to_rm = []
2134 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002135 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002136 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002137
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002138 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002139 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002140 if not os.path.exists(hooks):
2141 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002142 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002143 name = os.path.basename(stock_hook)
2144
Victor Boivie65e0f352011-04-18 11:23:29 +02002145 if name in ('commit-msg',) and not self.remote.review \
2146 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002147 # Don't install a Gerrit Code Review hook if this
2148 # project does not appear to use it for reviews.
2149 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002150 # Since the manifest project is one of those, but also
2151 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002152 continue
2153
2154 dst = os.path.join(hooks, name)
2155 if os.path.islink(dst):
2156 continue
2157 if os.path.exists(dst):
2158 if filecmp.cmp(stock_hook, dst, shallow=False):
2159 os.remove(dst)
2160 else:
2161 _error("%s: Not replacing %s hook", self.relpath, name)
2162 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002163 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002164 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002165 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002166 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002167 raise GitError('filesystem must support symlinks')
2168 else:
2169 raise
2170
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002171 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002172 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002173 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002174 remote.url = self.remote.url
2175 remote.review = self.remote.review
2176 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002177
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002178 if self.worktree:
2179 remote.ResetFetch(mirror=False)
2180 else:
2181 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002182 remote.Save()
2183
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002184 def _InitMRef(self):
2185 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002186 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002187
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002188 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002189 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002190
2191 def _InitAnyMRef(self, ref):
2192 cur = self.bare_ref.symref(ref)
2193
2194 if self.revisionId:
2195 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2196 msg = 'manifest set to %s' % self.revisionId
2197 dst = self.revisionId + '^0'
2198 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2199 else:
2200 remote = self.GetRemote(self.remote.name)
2201 dst = remote.ToLocal(self.revisionExpr)
2202 if cur != dst:
2203 msg = 'manifest set to %s' % self.revisionExpr
2204 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002205
David James8d201162013-10-11 17:03:19 -07002206 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2207 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2208
2209 Args:
2210 gitdir: The bare git repository. Must already be initialized.
2211 dotgit: The repository you would like to initialize.
2212 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2213 Only one work tree can store refs under a given |gitdir|.
2214 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2215 This saves you the effort of initializing |dotgit| yourself.
2216 """
2217 # These objects can be shared between several working trees.
2218 symlink_files = ['description', 'info']
2219 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2220 if share_refs:
2221 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002222 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002223 symlink_dirs += ['logs', 'refs']
2224 to_symlink = symlink_files + symlink_dirs
2225
2226 to_copy = []
2227 if copy_all:
2228 to_copy = os.listdir(gitdir)
2229
2230 for name in set(to_copy).union(to_symlink):
2231 try:
2232 src = os.path.realpath(os.path.join(gitdir, name))
2233 dst = os.path.realpath(os.path.join(dotgit, name))
2234
2235 if os.path.lexists(dst) and not os.path.islink(dst):
2236 raise GitError('cannot overwrite a local work tree')
2237
2238 # If the source dir doesn't exist, create an empty dir.
2239 if name in symlink_dirs and not os.path.lexists(src):
2240 os.makedirs(src)
2241
Conley Owens80b87fe2014-05-09 17:13:44 -07002242 # If the source file doesn't exist, ensure the destination
2243 # file doesn't either.
2244 if name in symlink_files and not os.path.lexists(src):
2245 try:
2246 os.remove(dst)
2247 except OSError:
2248 pass
2249
David James8d201162013-10-11 17:03:19 -07002250 if name in to_symlink:
2251 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2252 elif copy_all and not os.path.islink(dst):
2253 if os.path.isdir(src):
2254 shutil.copytree(src, dst)
2255 elif os.path.isfile(src):
2256 shutil.copy(src, dst)
2257 except OSError as e:
2258 if e.errno == errno.EPERM:
2259 raise GitError('filesystem must support symlinks')
2260 else:
2261 raise
2262
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002263 def _InitWorkTree(self):
2264 dotgit = os.path.join(self.worktree, '.git')
2265 if not os.path.exists(dotgit):
2266 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002267 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2268 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002269
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002270 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002271
2272 cmd = ['read-tree', '--reset', '-u']
2273 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002274 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002275 if GitCommand(self, cmd).Wait() != 0:
2276 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002277
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002278 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002279
2280 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002281 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002282
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002283 def _revlist(self, *args, **kw):
2284 a = []
2285 a.extend(args)
2286 a.append('--')
2287 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288
2289 @property
2290 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002291 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002292
Julien Camperguedd654222014-01-09 16:21:37 +01002293 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2294 """Get logs between two revisions of this project."""
2295 comp = '..'
2296 if rev1:
2297 revs = [rev1]
2298 if rev2:
2299 revs.extend([comp, rev2])
2300 cmd = ['log', ''.join(revs)]
2301 out = DiffColoring(self.config)
2302 if out.is_on and color:
2303 cmd.append('--color')
2304 if oneline:
2305 cmd.append('--oneline')
2306
2307 try:
2308 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2309 if log.Wait() == 0:
2310 return log.stdout
2311 except GitError:
2312 # worktree may not exist if groups changed for example. In that case,
2313 # try in gitdir instead.
2314 if not os.path.exists(self.worktree):
2315 return self.bare_git.log(*cmd[1:])
2316 else:
2317 raise
2318 return None
2319
2320 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2321 """Get the list of logs from this revision to given revisionId"""
2322 logs = {}
2323 selfId = self.GetRevisionId(self._allrefs)
2324 toId = toProject.GetRevisionId(toProject._allrefs)
2325
2326 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2327 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2328 return logs
2329
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002330 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002331 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002332 self._project = project
2333 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002334 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002335
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002336 def LsOthers(self):
2337 p = GitCommand(self._project,
2338 ['ls-files',
2339 '-z',
2340 '--others',
2341 '--exclude-standard'],
2342 bare = False,
David James8d201162013-10-11 17:03:19 -07002343 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002344 capture_stdout = True,
2345 capture_stderr = True)
2346 if p.Wait() == 0:
2347 out = p.stdout
2348 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002349 return out[:-1].split('\0') # pylint: disable=W1401
2350 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002351 return []
2352
2353 def DiffZ(self, name, *args):
2354 cmd = [name]
2355 cmd.append('-z')
2356 cmd.extend(args)
2357 p = GitCommand(self._project,
2358 cmd,
David James8d201162013-10-11 17:03:19 -07002359 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002360 bare = False,
2361 capture_stdout = True,
2362 capture_stderr = True)
2363 try:
2364 out = p.process.stdout.read()
2365 r = {}
2366 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002367 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002368 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002369 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002370 info = next(out)
2371 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002372 except StopIteration:
2373 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002374
2375 class _Info(object):
2376 def __init__(self, path, omode, nmode, oid, nid, state):
2377 self.path = path
2378 self.src_path = None
2379 self.old_mode = omode
2380 self.new_mode = nmode
2381 self.old_id = oid
2382 self.new_id = nid
2383
2384 if len(state) == 1:
2385 self.status = state
2386 self.level = None
2387 else:
2388 self.status = state[:1]
2389 self.level = state[1:]
2390 while self.level.startswith('0'):
2391 self.level = self.level[1:]
2392
2393 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002394 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002395 if info.status in ('R', 'C'):
2396 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002397 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002398 r[info.path] = info
2399 return r
2400 finally:
2401 p.Wait()
2402
2403 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002404 if self._bare:
2405 path = os.path.join(self._project.gitdir, HEAD)
2406 else:
2407 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002408 try:
2409 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002410 except IOError as e:
2411 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002412 try:
2413 line = fd.read()
2414 finally:
2415 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302416 try:
2417 line = line.decode()
2418 except AttributeError:
2419 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002420 if line.startswith('ref: '):
2421 return line[5:-1]
2422 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002423
2424 def SetHead(self, ref, message=None):
2425 cmdv = []
2426 if message is not None:
2427 cmdv.extend(['-m', message])
2428 cmdv.append(HEAD)
2429 cmdv.append(ref)
2430 self.symbolic_ref(*cmdv)
2431
2432 def DetachHead(self, new, message=None):
2433 cmdv = ['--no-deref']
2434 if message is not None:
2435 cmdv.extend(['-m', message])
2436 cmdv.append(HEAD)
2437 cmdv.append(new)
2438 self.update_ref(*cmdv)
2439
2440 def UpdateRef(self, name, new, old=None,
2441 message=None,
2442 detach=False):
2443 cmdv = []
2444 if message is not None:
2445 cmdv.extend(['-m', message])
2446 if detach:
2447 cmdv.append('--no-deref')
2448 cmdv.append(name)
2449 cmdv.append(new)
2450 if old is not None:
2451 cmdv.append(old)
2452 self.update_ref(*cmdv)
2453
2454 def DeleteRef(self, name, old=None):
2455 if not old:
2456 old = self.rev_parse(name)
2457 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002458 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002459
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002460 def rev_list(self, *args, **kw):
2461 if 'format' in kw:
2462 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2463 else:
2464 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002465 cmdv.extend(args)
2466 p = GitCommand(self._project,
2467 cmdv,
2468 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002469 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002470 capture_stdout = True,
2471 capture_stderr = True)
2472 r = []
2473 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002474 if line[-1] == '\n':
2475 line = line[:-1]
2476 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002477 if p.Wait() != 0:
2478 raise GitError('%s rev-list %s: %s' % (
2479 self._project.name,
2480 str(args),
2481 p.stderr))
2482 return r
2483
2484 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002485 """Allow arbitrary git commands using pythonic syntax.
2486
2487 This allows you to do things like:
2488 git_obj.rev_parse('HEAD')
2489
2490 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2491 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002492 Any other positional arguments will be passed to the git command, and the
2493 following keyword arguments are supported:
2494 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002495
2496 Args:
2497 name: The name of the git command to call. Any '_' characters will
2498 be replaced with '-'.
2499
2500 Returns:
2501 A callable object that will try to call git with the named command.
2502 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002503 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002504 def runner(*args, **kwargs):
2505 cmdv = []
2506 config = kwargs.pop('config', None)
2507 for k in kwargs:
2508 raise TypeError('%s() got an unexpected keyword argument %r'
2509 % (name, k))
2510 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002511 if not git_require((1, 7, 2)):
2512 raise ValueError('cannot set config on command line for %s()'
2513 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302514 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002515 cmdv.append('-c')
2516 cmdv.append('%s=%s' % (k, v))
2517 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002518 cmdv.extend(args)
2519 p = GitCommand(self._project,
2520 cmdv,
2521 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002522 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002523 capture_stdout = True,
2524 capture_stderr = True)
2525 if p.Wait() != 0:
2526 raise GitError('%s %s: %s' % (
2527 self._project.name,
2528 name,
2529 p.stderr))
2530 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302531 try:
Conley Owensedd01512013-09-26 12:59:58 -07002532 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302533 except AttributeError:
2534 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002535 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2536 return r[:-1]
2537 return r
2538 return runner
2539
2540
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002541class _PriorSyncFailedError(Exception):
2542 def __str__(self):
2543 return 'prior sync failed; rebase still in progress'
2544
2545class _DirtyError(Exception):
2546 def __str__(self):
2547 return 'contains uncommitted changes'
2548
2549class _InfoMessage(object):
2550 def __init__(self, project, text):
2551 self.project = project
2552 self.text = text
2553
2554 def Print(self, syncbuf):
2555 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2556 syncbuf.out.nl()
2557
2558class _Failure(object):
2559 def __init__(self, project, why):
2560 self.project = project
2561 self.why = why
2562
2563 def Print(self, syncbuf):
2564 syncbuf.out.fail('error: %s/: %s',
2565 self.project.relpath,
2566 str(self.why))
2567 syncbuf.out.nl()
2568
2569class _Later(object):
2570 def __init__(self, project, action):
2571 self.project = project
2572 self.action = action
2573
2574 def Run(self, syncbuf):
2575 out = syncbuf.out
2576 out.project('project %s/', self.project.relpath)
2577 out.nl()
2578 try:
2579 self.action()
2580 out.nl()
2581 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002582 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002583 out.nl()
2584 return False
2585
2586class _SyncColoring(Coloring):
2587 def __init__(self, config):
2588 Coloring.__init__(self, config, 'reposync')
2589 self.project = self.printer('header', attr = 'bold')
2590 self.info = self.printer('info')
2591 self.fail = self.printer('fail', fg='red')
2592
2593class SyncBuffer(object):
2594 def __init__(self, config, detach_head=False):
2595 self._messages = []
2596 self._failures = []
2597 self._later_queue1 = []
2598 self._later_queue2 = []
2599
2600 self.out = _SyncColoring(config)
2601 self.out.redirect(sys.stderr)
2602
2603 self.detach_head = detach_head
2604 self.clean = True
2605
2606 def info(self, project, fmt, *args):
2607 self._messages.append(_InfoMessage(project, fmt % args))
2608
2609 def fail(self, project, err=None):
2610 self._failures.append(_Failure(project, err))
2611 self.clean = False
2612
2613 def later1(self, project, what):
2614 self._later_queue1.append(_Later(project, what))
2615
2616 def later2(self, project, what):
2617 self._later_queue2.append(_Later(project, what))
2618
2619 def Finish(self):
2620 self._PrintMessages()
2621 self._RunLater()
2622 self._PrintMessages()
2623 return self.clean
2624
2625 def _RunLater(self):
2626 for q in ['_later_queue1', '_later_queue2']:
2627 if not self._RunQueue(q):
2628 return
2629
2630 def _RunQueue(self, queue):
2631 for m in getattr(self, queue):
2632 if not m.Run(self):
2633 self.clean = False
2634 return False
2635 setattr(self, queue, [])
2636 return True
2637
2638 def _PrintMessages(self):
2639 for m in self._messages:
2640 m.Print(self)
2641 for m in self._failures:
2642 m.Print(self)
2643
2644 self._messages = []
2645 self._failures = []
2646
2647
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002648class MetaProject(Project):
2649 """A special project housed under .repo.
2650 """
2651 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002652 Project.__init__(self,
2653 manifest = manifest,
2654 name = name,
2655 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002656 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002657 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002658 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002659 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002660 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002661 revisionId = None,
2662 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002663
2664 def PreSync(self):
2665 if self.Exists:
2666 cb = self.CurrentBranch
2667 if cb:
2668 base = self.GetBranch(cb).merge
2669 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002670 self.revisionExpr = base
2671 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002672
Florian Vallee5d016502012-06-07 17:19:26 +02002673 def MetaBranchSwitch(self, target):
2674 """ Prepare MetaProject for manifest branch switch
2675 """
2676
2677 # detach and delete manifest branch, allowing a new
2678 # branch to take over
2679 syncbuf = SyncBuffer(self.config, detach_head = True)
2680 self.Sync_LocalHalf(syncbuf)
2681 syncbuf.Finish()
2682
2683 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002684 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002685 capture_stdout = True,
2686 capture_stderr = True).Wait() == 0
2687
2688
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002689 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002690 def LastFetch(self):
2691 try:
2692 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2693 return os.path.getmtime(fh)
2694 except OSError:
2695 return 0
2696
2697 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002698 def HasChanges(self):
2699 """Has the remote received new commits not yet checked out?
2700 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002701 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002702 return False
2703
David Pursehouse8a68ff92012-09-24 12:15:13 +09002704 all_refs = self.bare_ref.all
2705 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002706 head = self.work_git.GetHead()
2707 if head.startswith(R_HEADS):
2708 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002709 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002710 except KeyError:
2711 head = None
2712
2713 if revid == head:
2714 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002715 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002716 return True
2717 return False