blob: db380a099ff44afe28eb4824289bf6f0d9e438b8 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Doug Anderson37282b42011-03-04 11:54:18 -080016import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080017import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import filecmp
19import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070029
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070031from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070032from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090033from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080034from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080035from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070036from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037
Shawn O. Pearced237b692009-04-17 18:49:50 -070038from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
David Pursehouse59bbb582013-05-17 10:49:33 +090040from pyversion import is_python3
41if not is_python3():
42 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053043 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090044 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053045
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070046def _lwrite(path, content):
47 lock = '%s.lock' % path
48
49 fd = open(lock, 'wb')
50 try:
51 fd.write(content)
52 finally:
53 fd.close()
54
55 try:
56 os.rename(lock, path)
57 except OSError:
58 os.remove(lock)
59 raise
60
Shawn O. Pearce48244782009-04-16 08:25:57 -070061def _error(fmt, *args):
62 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070063 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070064
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070065def not_rev(r):
66 return '^' + r
67
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080068def sq(r):
69 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080070
Doug Anderson8ced8642011-01-10 14:16:30 -080071_project_hook_list = None
72def _ProjectHooks():
73 """List the hooks present in the 'hooks' directory.
74
75 These hooks are project hooks and are copied to the '.git/hooks' directory
76 of all subprojects.
77
78 This function caches the list of hooks (based on the contents of the
79 'repo/hooks' directory) on the first call.
80
81 Returns:
82 A list of absolute paths to all of the files in the hooks directory.
83 """
84 global _project_hook_list
85 if _project_hook_list is None:
Jesse Hall672cc492013-11-27 11:17:13 -080086 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087 d = os.path.join(d , 'hooks')
Chirayu Desai217ea7d2013-03-01 19:14:38 +053088 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
Doug Anderson8ced8642011-01-10 14:16:30 -080089 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080090
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080091
Shawn O. Pearce632768b2008-10-23 11:58:52 -070092class DownloadedChange(object):
93 _commit_cache = None
94
95 def __init__(self, project, base, change_id, ps_id, commit):
96 self.project = project
97 self.base = base
98 self.change_id = change_id
99 self.ps_id = ps_id
100 self.commit = commit
101
102 @property
103 def commits(self):
104 if self._commit_cache is None:
105 self._commit_cache = self.project.bare_git.rev_list(
106 '--abbrev=8',
107 '--abbrev-commit',
108 '--pretty=oneline',
109 '--reverse',
110 '--date-order',
111 not_rev(self.base),
112 self.commit,
113 '--')
114 return self._commit_cache
115
116
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700117class ReviewableBranch(object):
118 _commit_cache = None
119
120 def __init__(self, project, branch, base):
121 self.project = project
122 self.branch = branch
123 self.base = base
124
125 @property
126 def name(self):
127 return self.branch.name
128
129 @property
130 def commits(self):
131 if self._commit_cache is None:
132 self._commit_cache = self.project.bare_git.rev_list(
133 '--abbrev=8',
134 '--abbrev-commit',
135 '--pretty=oneline',
136 '--reverse',
137 '--date-order',
138 not_rev(self.base),
139 R_HEADS + self.name,
140 '--')
141 return self._commit_cache
142
143 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800144 def unabbrev_commits(self):
145 r = dict()
146 for commit in self.project.bare_git.rev_list(
147 not_rev(self.base),
148 R_HEADS + self.name,
149 '--'):
150 r[commit[0:8]] = commit
151 return r
152
153 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154 def date(self):
155 return self.project.bare_git.log(
156 '--pretty=format:%cd',
157 '-n', '1',
158 R_HEADS + self.name,
159 '--')
160
Bryan Jacobsf609f912013-05-06 13:36:24 -0400161 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700163 people,
Brian Harring435370c2012-07-28 15:37:04 -0700164 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400165 draft=draft,
166 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700168 def GetPublishedRefs(self):
169 refs = {}
170 output = self.project.bare_git.ls_remote(
171 self.branch.remote.SshReviewUrl(self.project.UserEmail),
172 'refs/changes/*')
173 for line in output.split('\n'):
174 try:
175 (sha, ref) = line.split()
176 refs[sha] = ref
177 except ValueError:
178 pass
179
180 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
182class StatusColoring(Coloring):
183 def __init__(self, config):
184 Coloring.__init__(self, config, 'status')
185 self.project = self.printer('header', attr = 'bold')
186 self.branch = self.printer('header', attr = 'bold')
187 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700188 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700189
190 self.added = self.printer('added', fg = 'green')
191 self.changed = self.printer('changed', fg = 'red')
192 self.untracked = self.printer('untracked', fg = 'red')
193
194
195class DiffColoring(Coloring):
196 def __init__(self, config):
197 Coloring.__init__(self, config, 'diff')
198 self.project = self.printer('header', attr = 'bold')
199
James W. Mills24c13082012-04-12 15:04:13 -0500200class _Annotation:
201 def __init__(self, name, value, keep):
202 self.name = name
203 self.value = value
204 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205
206class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800207 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 self.src = src
209 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800210 self.abs_src = abssrc
211 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700212
213 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800214 src = self.abs_src
215 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 # copy file if it does not exist or is out of date
217 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
218 try:
219 # remove existing file first, since it might be read-only
220 if os.path.exists(dest):
221 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400222 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200223 dest_dir = os.path.dirname(dest)
224 if not os.path.isdir(dest_dir):
225 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226 shutil.copy(src, dest)
227 # make the file read-only
228 mode = os.stat(dest)[stat.ST_MODE]
229 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
230 os.chmod(dest, mode)
231 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700232 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500234class _LinkFile:
235 def __init__(self, src, dest, abssrc, absdest):
236 self.src = src
237 self.dest = dest
238 self.abs_src = abssrc
239 self.abs_dest = absdest
240
241 def _Link(self):
242 src = self.abs_src
243 dest = self.abs_dest
244 # link file if it does not exist or is out of date
245 if not os.path.islink(dest) or os.readlink(dest) != src:
246 try:
247 # remove existing file first, since it might be read-only
248 if os.path.exists(dest):
249 os.remove(dest)
250 else:
251 dest_dir = os.path.dirname(dest)
252 if not os.path.isdir(dest_dir):
253 os.makedirs(dest_dir)
254 os.symlink(src, dest)
255 except IOError:
256 _error('Cannot link file %s to %s', src, dest)
257
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700258class RemoteSpec(object):
259 def __init__(self,
260 name,
261 url = None,
Anthony King36ea2fb2014-05-06 11:54:01 +0100262 review = None,
263 revision = None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700264 self.name = name
265 self.url = url
266 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100267 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700268
Doug Anderson37282b42011-03-04 11:54:18 -0800269class RepoHook(object):
270 """A RepoHook contains information about a script to run as a hook.
271
272 Hooks are used to run a python script before running an upload (for instance,
273 to run presubmit checks). Eventually, we may have hooks for other actions.
274
275 This shouldn't be confused with files in the 'repo/hooks' directory. Those
276 files are copied into each '.git/hooks' folder for each project. Repo-level
277 hooks are associated instead with repo actions.
278
279 Hooks are always python. When a hook is run, we will load the hook into the
280 interpreter and execute its main() function.
281 """
282 def __init__(self,
283 hook_type,
284 hooks_project,
285 topdir,
286 abort_if_user_denies=False):
287 """RepoHook constructor.
288
289 Params:
290 hook_type: A string representing the type of hook. This is also used
291 to figure out the name of the file containing the hook. For
292 example: 'pre-upload'.
293 hooks_project: The project containing the repo hooks. If you have a
294 manifest, this is manifest.repo_hooks_project. OK if this is None,
295 which will make the hook a no-op.
296 topdir: Repo's top directory (the one containing the .repo directory).
297 Scripts will run with CWD as this directory. If you have a manifest,
298 this is manifest.topdir
299 abort_if_user_denies: If True, we'll throw a HookError() if the user
300 doesn't allow us to run the hook.
301 """
302 self._hook_type = hook_type
303 self._hooks_project = hooks_project
304 self._topdir = topdir
305 self._abort_if_user_denies = abort_if_user_denies
306
307 # Store the full path to the script for convenience.
308 if self._hooks_project:
309 self._script_fullpath = os.path.join(self._hooks_project.worktree,
310 self._hook_type + '.py')
311 else:
312 self._script_fullpath = None
313
314 def _GetHash(self):
315 """Return a hash of the contents of the hooks directory.
316
317 We'll just use git to do this. This hash has the property that if anything
318 changes in the directory we will return a different has.
319
320 SECURITY CONSIDERATION:
321 This hash only represents the contents of files in the hook directory, not
322 any other files imported or called by hooks. Changes to imported files
323 can change the script behavior without affecting the hash.
324
325 Returns:
326 A string representing the hash. This will always be ASCII so that it can
327 be printed to the user easily.
328 """
329 assert self._hooks_project, "Must have hooks to calculate their hash."
330
331 # We will use the work_git object rather than just calling GetRevisionId().
332 # That gives us a hash of the latest checked in version of the files that
333 # the user will actually be executing. Specifically, GetRevisionId()
334 # doesn't appear to change even if a user checks out a different version
335 # of the hooks repo (via git checkout) nor if a user commits their own revs.
336 #
337 # NOTE: Local (non-committed) changes will not be factored into this hash.
338 # I think this is OK, since we're really only worried about warning the user
339 # about upstream changes.
340 return self._hooks_project.work_git.rev_parse('HEAD')
341
342 def _GetMustVerb(self):
343 """Return 'must' if the hook is required; 'should' if not."""
344 if self._abort_if_user_denies:
345 return 'must'
346 else:
347 return 'should'
348
349 def _CheckForHookApproval(self):
350 """Check to see whether this hook has been approved.
351
352 We'll look at the hash of all of the hooks. If this matches the hash that
353 the user last approved, we're done. If it doesn't, we'll ask the user
354 about approval.
355
356 Note that we ask permission for each individual hook even though we use
357 the hash of all hooks when detecting changes. We'd like the user to be
358 able to approve / deny each hook individually. We only use the hash of all
359 hooks because there is no other easy way to detect changes to local imports.
360
361 Returns:
362 True if this hook is approved to run; False otherwise.
363
364 Raises:
365 HookError: Raised if the user doesn't approve and abort_if_user_denies
366 was passed to the consturctor.
367 """
Doug Anderson37282b42011-03-04 11:54:18 -0800368 hooks_config = self._hooks_project.config
369 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
370
371 # Get the last hash that the user approved for this hook; may be None.
372 old_hash = hooks_config.GetString(git_approval_key)
373
374 # Get the current hash so we can tell if scripts changed since approval.
375 new_hash = self._GetHash()
376
377 if old_hash is not None:
378 # User previously approved hook and asked not to be prompted again.
379 if new_hash == old_hash:
380 # Approval matched. We're done.
381 return True
382 else:
383 # Give the user a reason why we're prompting, since they last told
384 # us to "never ask again".
385 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
386 self._hook_type)
387 else:
388 prompt = ''
389
390 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
391 if sys.stdout.isatty():
392 prompt += ('Repo %s run the script:\n'
393 ' %s\n'
394 '\n'
395 'Do you want to allow this script to run '
396 '(yes/yes-never-ask-again/NO)? ') % (
397 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530398 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900399 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800400
401 # User is doing a one-time approval.
402 if response in ('y', 'yes'):
403 return True
404 elif response == 'yes-never-ask-again':
405 hooks_config.SetString(git_approval_key, new_hash)
406 return True
407
408 # For anything else, we'll assume no approval.
409 if self._abort_if_user_denies:
410 raise HookError('You must allow the %s hook or use --no-verify.' %
411 self._hook_type)
412
413 return False
414
415 def _ExecuteHook(self, **kwargs):
416 """Actually execute the given hook.
417
418 This will run the hook's 'main' function in our python interpreter.
419
420 Args:
421 kwargs: Keyword arguments to pass to the hook. These are often specific
422 to the hook type. For instance, pre-upload hooks will contain
423 a project_list.
424 """
425 # Keep sys.path and CWD stashed away so that we can always restore them
426 # upon function exit.
427 orig_path = os.getcwd()
428 orig_syspath = sys.path
429
430 try:
431 # Always run hooks with CWD as topdir.
432 os.chdir(self._topdir)
433
434 # Put the hook dir as the first item of sys.path so hooks can do
435 # relative imports. We want to replace the repo dir as [0] so
436 # hooks can't import repo files.
437 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
438
439 # Exec, storing global context in the context dict. We catch exceptions
440 # and convert to a HookError w/ just the failing traceback.
441 context = {}
442 try:
443 execfile(self._script_fullpath, context)
444 except Exception:
445 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
446 traceback.format_exc(), self._hook_type))
447
448 # Running the script should have defined a main() function.
449 if 'main' not in context:
450 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
451
452
453 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
454 # We don't actually want hooks to define their main with this argument--
455 # it's there to remind them that their hook should always take **kwargs.
456 # For instance, a pre-upload hook should be defined like:
457 # def main(project_list, **kwargs):
458 #
459 # This allows us to later expand the API without breaking old hooks.
460 kwargs = kwargs.copy()
461 kwargs['hook_should_take_kwargs'] = True
462
463 # Call the main function in the hook. If the hook should cause the
464 # build to fail, it will raise an Exception. We'll catch that convert
465 # to a HookError w/ just the failing traceback.
466 try:
467 context['main'](**kwargs)
468 except Exception:
469 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
470 'above.' % (
471 traceback.format_exc(), self._hook_type))
472 finally:
473 # Restore sys.path and CWD.
474 sys.path = orig_syspath
475 os.chdir(orig_path)
476
477 def Run(self, user_allows_all_hooks, **kwargs):
478 """Run the hook.
479
480 If the hook doesn't exist (because there is no hooks project or because
481 this particular hook is not enabled), this is a no-op.
482
483 Args:
484 user_allows_all_hooks: If True, we will never prompt about running the
485 hook--we'll just assume it's OK to run it.
486 kwargs: Keyword arguments to pass to the hook. These are often specific
487 to the hook type. For instance, pre-upload hooks will contain
488 a project_list.
489
490 Raises:
491 HookError: If there was a problem finding the hook or the user declined
492 to run a required hook (from _CheckForHookApproval).
493 """
494 # No-op if there is no hooks project or if hook is disabled.
495 if ((not self._hooks_project) or
496 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
497 return
498
499 # Bail with a nice error if we can't find the hook.
500 if not os.path.isfile(self._script_fullpath):
501 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
502
503 # Make sure the user is OK with running the hook.
504 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
505 return
506
507 # Run the hook with the same version of python we're using.
508 self._ExecuteHook(**kwargs)
509
510
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700511class Project(object):
512 def __init__(self,
513 manifest,
514 name,
515 remote,
516 gitdir,
David James8d201162013-10-11 17:03:19 -0700517 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700518 worktree,
519 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700520 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800521 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700522 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700523 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700524 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800525 sync_s = False,
David Pursehouseede7f122012-11-27 22:25:30 +0900526 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800527 upstream = None,
528 parent = None,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400529 is_derived = False,
530 dest_branch = None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800531 """Init a Project object.
532
533 Args:
534 manifest: The XmlManifest object.
535 name: The `name` attribute of manifest.xml's project element.
536 remote: RemoteSpec object specifying its remote's properties.
537 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700538 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800539 worktree: Absolute path of git working tree.
540 relpath: Relative path of git working tree to repo's top directory.
541 revisionExpr: The `revision` attribute of manifest.xml's project element.
542 revisionId: git commit id for checking out.
543 rebase: The `rebase` attribute of manifest.xml's project element.
544 groups: The `groups` attribute of manifest.xml's project element.
545 sync_c: The `sync-c` attribute of manifest.xml's project element.
546 sync_s: The `sync-s` attribute of manifest.xml's project element.
547 upstream: The `upstream` attribute of manifest.xml's project element.
548 parent: The parent Project object.
549 is_derived: False if the project was explicitly defined in the manifest;
550 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400551 dest_branch: The branch to which to push changes for review by default.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800552 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700553 self.manifest = manifest
554 self.name = name
555 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800556 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700557 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800558 if worktree:
559 self.worktree = worktree.replace('\\', '/')
560 else:
561 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700562 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700563 self.revisionExpr = revisionExpr
564
565 if revisionId is None \
566 and revisionExpr \
567 and IsId(revisionExpr):
568 self.revisionId = revisionExpr
569 else:
570 self.revisionId = revisionId
571
Mike Pontillod3153822012-02-28 11:53:24 -0800572 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700573 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700574 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800575 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900576 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700577 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800578 self.parent = parent
579 self.is_derived = is_derived
580 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800581
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700582 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500584 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500585 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700586 self.config = GitConfig.ForRepository(
587 gitdir = self.gitdir,
588 defaults = self.manifest.globalConfig)
589
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800590 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700591 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800592 else:
593 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700594 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700595 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700596 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400597 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700598
Doug Anderson37282b42011-03-04 11:54:18 -0800599 # This will be filled in if a project is later identified to be the
600 # project containing repo hooks.
601 self.enabled_repo_hooks = []
602
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700603 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800604 def Derived(self):
605 return self.is_derived
606
607 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700608 def Exists(self):
609 return os.path.isdir(self.gitdir)
610
611 @property
612 def CurrentBranch(self):
613 """Obtain the name of the currently checked out branch.
614 The branch name omits the 'refs/heads/' prefix.
615 None is returned if the project is on a detached HEAD.
616 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700617 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700618 if b.startswith(R_HEADS):
619 return b[len(R_HEADS):]
620 return None
621
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700622 def IsRebaseInProgress(self):
623 w = self.worktree
624 g = os.path.join(w, '.git')
625 return os.path.exists(os.path.join(g, 'rebase-apply')) \
626 or os.path.exists(os.path.join(g, 'rebase-merge')) \
627 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200628
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700629 def IsDirty(self, consider_untracked=True):
630 """Is the working directory modified in some way?
631 """
632 self.work_git.update_index('-q',
633 '--unmerged',
634 '--ignore-missing',
635 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900636 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700637 return True
638 if self.work_git.DiffZ('diff-files'):
639 return True
640 if consider_untracked and self.work_git.LsOthers():
641 return True
642 return False
643
644 _userident_name = None
645 _userident_email = None
646
647 @property
648 def UserName(self):
649 """Obtain the user's personal name.
650 """
651 if self._userident_name is None:
652 self._LoadUserIdentity()
653 return self._userident_name
654
655 @property
656 def UserEmail(self):
657 """Obtain the user's email address. This is very likely
658 to be their Gerrit login.
659 """
660 if self._userident_email is None:
661 self._LoadUserIdentity()
662 return self._userident_email
663
664 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900665 u = self.bare_git.var('GIT_COMMITTER_IDENT')
666 m = re.compile("^(.*) <([^>]*)> ").match(u)
667 if m:
668 self._userident_name = m.group(1)
669 self._userident_email = m.group(2)
670 else:
671 self._userident_name = ''
672 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700673
674 def GetRemote(self, name):
675 """Get the configuration for a single remote.
676 """
677 return self.config.GetRemote(name)
678
679 def GetBranch(self, name):
680 """Get the configuration for a single branch.
681 """
682 return self.config.GetBranch(name)
683
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700684 def GetBranches(self):
685 """Get all existing local branches.
686 """
687 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900688 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700689 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700690
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530691 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700692 if name.startswith(R_HEADS):
693 name = name[len(R_HEADS):]
694 b = self.GetBranch(name)
695 b.current = name == current
696 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900697 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700698 heads[name] = b
699
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530700 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700701 if name.startswith(R_PUB):
702 name = name[len(R_PUB):]
703 b = heads.get(name)
704 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900705 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700706
707 return heads
708
Colin Cross5acde752012-03-28 20:15:45 -0700709 def MatchesGroups(self, manifest_groups):
710 """Returns true if the manifest groups specified at init should cause
711 this project to be synced.
712 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700713 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700714
715 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700716 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700717 manifest_groups: "-group1,group2"
718 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500719
720 The special manifest group "default" will match any project that
721 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700722 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500723 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700724 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500725 if not 'notdefault' in expanded_project_groups:
726 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700727
Conley Owens971de8e2012-04-16 10:36:08 -0700728 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700729 for group in expanded_manifest_groups:
730 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700731 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700732 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700733 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700734
Conley Owens971de8e2012-04-16 10:36:08 -0700735 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736
737## Status Display ##
738
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500739 def HasChanges(self):
740 """Returns true if there are uncommitted changes.
741 """
742 self.work_git.update_index('-q',
743 '--unmerged',
744 '--ignore-missing',
745 '--refresh')
746 if self.IsRebaseInProgress():
747 return True
748
749 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
750 return True
751
752 if self.work_git.DiffZ('diff-files'):
753 return True
754
755 if self.work_git.LsOthers():
756 return True
757
758 return False
759
Terence Haddock4655e812011-03-31 12:33:34 +0200760 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700761 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200762
763 Args:
764 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700765 """
766 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200767 if output_redir == None:
768 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700769 print(file=output_redir)
770 print('project %s/' % self.relpath, file=output_redir)
771 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700772 return
773
774 self.work_git.update_index('-q',
775 '--unmerged',
776 '--ignore-missing',
777 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700778 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700779 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
780 df = self.work_git.DiffZ('diff-files')
781 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100782 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700783 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784
785 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200786 if not output_redir == None:
787 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788 out.project('project %-40s', self.relpath + '/')
789
790 branch = self.CurrentBranch
791 if branch is None:
792 out.nobranch('(*** NO BRANCH ***)')
793 else:
794 out.branch('branch %s', branch)
795 out.nl()
796
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700797 if rb:
798 out.important('prior sync failed; rebase still in progress')
799 out.nl()
800
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700801 paths = list()
802 paths.extend(di.keys())
803 paths.extend(df.keys())
804 paths.extend(do)
805
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530806 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900807 try:
808 i = di[p]
809 except KeyError:
810 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900812 try:
813 f = df[p]
814 except KeyError:
815 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200816
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900817 if i:
818 i_status = i.status.upper()
819 else:
820 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700821
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900822 if f:
823 f_status = f.status.lower()
824 else:
825 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700826
827 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800828 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829 i.src_path, p, i.level)
830 else:
831 line = ' %s%s\t%s' % (i_status, f_status, p)
832
833 if i and not f:
834 out.added('%s', line)
835 elif (i and f) or (not i and f):
836 out.changed('%s', line)
837 elif not i and not f:
838 out.untracked('%s', line)
839 else:
840 out.write('%s', line)
841 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200842
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700843 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844
pelyad67872d2012-03-28 14:49:58 +0300845 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 """Prints the status of the repository to stdout.
847 """
848 out = DiffColoring(self.config)
849 cmd = ['diff']
850 if out.is_on:
851 cmd.append('--color')
852 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300853 if absolute_paths:
854 cmd.append('--src-prefix=a/%s/' % self.relpath)
855 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700856 cmd.append('--')
857 p = GitCommand(self,
858 cmd,
859 capture_stdout = True,
860 capture_stderr = True)
861 has_diff = False
862 for line in p.process.stdout:
863 if not has_diff:
864 out.nl()
865 out.project('project %s/' % self.relpath)
866 out.nl()
867 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700868 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 p.Wait()
870
871
872## Publish / Upload ##
873
David Pursehouse8a68ff92012-09-24 12:15:13 +0900874 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700875 """Was the branch published (uploaded) for code review?
876 If so, returns the SHA-1 hash of the last published
877 state for the branch.
878 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700879 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900880 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700881 try:
882 return self.bare_git.rev_parse(key)
883 except GitError:
884 return None
885 else:
886 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900887 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700888 except KeyError:
889 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890
David Pursehouse8a68ff92012-09-24 12:15:13 +0900891 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892 """Prunes any stale published refs.
893 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900894 if all_refs is None:
895 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896 heads = set()
897 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530898 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899 if name.startswith(R_HEADS):
900 heads.add(name)
901 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900902 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700903
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530904 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905 n = name[len(R_PUB):]
906 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900907 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700908
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700909 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700910 """List any branches which can be uploaded for review.
911 """
912 heads = {}
913 pubed = {}
914
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530915 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900917 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900919 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920
921 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530922 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900923 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700925 if selected_branch and branch != selected_branch:
926 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800928 rb = self.GetUploadableBranch(branch)
929 if rb:
930 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931 return ready
932
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800933 def GetUploadableBranch(self, branch_name):
934 """Get a single uploadable branch, or None.
935 """
936 branch = self.GetBranch(branch_name)
937 base = branch.LocalMerge
938 if branch.LocalMerge:
939 rb = ReviewableBranch(self, branch, base)
940 if rb.commits:
941 return rb
942 return None
943
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700944 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700945 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700946 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400947 draft=False,
948 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949 """Uploads the named branch for code review.
950 """
951 if branch is None:
952 branch = self.CurrentBranch
953 if branch is None:
954 raise GitError('not currently on a branch')
955
956 branch = self.GetBranch(branch)
957 if not branch.LocalMerge:
958 raise GitError('branch %s does not track a remote' % branch.name)
959 if not branch.remote.review:
960 raise GitError('remote %s has no review url' % branch.remote.name)
961
Bryan Jacobsf609f912013-05-06 13:36:24 -0400962 if dest_branch is None:
963 dest_branch = self.dest_branch
964 if dest_branch is None:
965 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966 if not dest_branch.startswith(R_HEADS):
967 dest_branch = R_HEADS + dest_branch
968
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800969 if not branch.remote.projectname:
970 branch.remote.projectname = self.name
971 branch.remote.Save()
972
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800973 url = branch.remote.ReviewUrl(self.UserEmail)
974 if url is None:
975 raise UploadError('review not configured')
976 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800977
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800978 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800979 rp = ['gerrit receive-pack']
980 for e in people[0]:
981 rp.append('--reviewer=%s' % sq(e))
982 for e in people[1]:
983 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800984 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700985
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800986 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800987
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800988 if dest_branch.startswith(R_HEADS):
989 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700990
991 upload_type = 'for'
992 if draft:
993 upload_type = 'drafts'
994
995 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
996 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800997 if auto_topic:
998 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800999 if not url.startswith('ssh://'):
1000 rp = ['r=%s' % p for p in people[0]] + \
1001 ['cc=%s' % p for p in people[1]]
1002 if rp:
1003 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001004 cmd.append(ref_spec)
1005
1006 if GitCommand(self, cmd, bare = True).Wait() != 0:
1007 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008
1009 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1010 self.bare_git.UpdateRef(R_PUB + branch.name,
1011 R_HEADS + branch.name,
1012 message = msg)
1013
1014
1015## Sync ##
1016
Julien Campergue335f5ef2013-10-16 11:02:35 +02001017 def _ExtractArchive(self, tarpath, path=None):
1018 """Extract the given tar on its current location
1019
1020 Args:
1021 - tarpath: The path to the actual tar file
1022
1023 """
1024 try:
1025 with tarfile.open(tarpath, 'r') as tar:
1026 tar.extractall(path=path)
1027 return True
1028 except (IOError, tarfile.TarError) as e:
1029 print("error: Cannot extract archive %s: "
1030 "%s" % (tarpath, str(e)), file=sys.stderr)
1031 return False
1032
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001033 def Sync_NetworkHalf(self,
1034 quiet=False,
1035 is_new=None,
1036 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001037 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001038 no_tags=False,
1039 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001040 """Perform only the network IO portion of the sync process.
1041 Local working directory/branch state is not affected.
1042 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001043 if archive and not isinstance(self, MetaProject):
1044 if self.remote.url.startswith(('http://', 'https://')):
1045 print("error: %s: Cannot fetch archives from http/https "
1046 "remotes." % self.name, file=sys.stderr)
1047 return False
1048
1049 name = self.relpath.replace('\\', '/')
1050 name = name.replace('/', '_')
1051 tarpath = '%s.tar' % name
1052 topdir = self.manifest.topdir
1053
1054 try:
1055 self._FetchArchive(tarpath, cwd=topdir)
1056 except GitError as e:
1057 print('error: %s' % str(e), file=sys.stderr)
1058 return False
1059
1060 # From now on, we only need absolute tarpath
1061 tarpath = os.path.join(topdir, tarpath)
1062
1063 if not self._ExtractArchive(tarpath, path=topdir):
1064 return False
1065 try:
1066 os.remove(tarpath)
1067 except OSError as e:
1068 print("warn: Cannot remove archive %s: "
1069 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001070 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001071 return True
1072
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001073 if is_new is None:
1074 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001075 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001077 else:
1078 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001080
1081 if is_new:
1082 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1083 try:
1084 fd = open(alt, 'rb')
1085 try:
1086 alt_dir = fd.readline().rstrip()
1087 finally:
1088 fd.close()
1089 except IOError:
1090 alt_dir = None
1091 else:
1092 alt_dir = None
1093
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001094 if clone_bundle \
1095 and alt_dir is None \
1096 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001097 is_new = False
1098
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001099 if not current_branch_only:
1100 if self.sync_c:
1101 current_branch_only = True
1102 elif not self.manifest._loaded:
1103 # Manifest cannot check defaults until it syncs.
1104 current_branch_only = False
1105 elif self.manifest.default.sync_c:
1106 current_branch_only = True
1107
Conley Owens666d5342014-05-01 13:09:57 -07001108 has_sha1 = ID_RE.match(self.revisionExpr) and self._CheckForSha1()
1109 if (not has_sha1 #Need to fetch since we don't already have this revision
1110 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1111 current_branch_only=current_branch_only,
1112 no_tags=no_tags)):
1113 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001114
1115 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001116 self._InitMRef()
1117 else:
1118 self._InitMirrorHead()
1119 try:
1120 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1121 except OSError:
1122 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001123 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001124
1125 def PostRepoUpgrade(self):
1126 self._InitHooks()
1127
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001128 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001129 for copyfile in self.copyfiles:
1130 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001131 for linkfile in self.linkfiles:
1132 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001133
Julien Camperguedd654222014-01-09 16:21:37 +01001134 def GetCommitRevisionId(self):
1135 """Get revisionId of a commit.
1136
1137 Use this method instead of GetRevisionId to get the id of the commit rather
1138 than the id of the current git object (for example, a tag)
1139
1140 """
1141 if not self.revisionExpr.startswith(R_TAGS):
1142 return self.GetRevisionId(self._allrefs)
1143
1144 try:
1145 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1146 except GitError:
1147 raise ManifestInvalidRevisionError(
1148 'revision %s in %s not found' % (self.revisionExpr,
1149 self.name))
1150
David Pursehouse8a68ff92012-09-24 12:15:13 +09001151 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001152 if self.revisionId:
1153 return self.revisionId
1154
1155 rem = self.GetRemote(self.remote.name)
1156 rev = rem.ToLocal(self.revisionExpr)
1157
David Pursehouse8a68ff92012-09-24 12:15:13 +09001158 if all_refs is not None and rev in all_refs:
1159 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001160
1161 try:
1162 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1163 except GitError:
1164 raise ManifestInvalidRevisionError(
1165 'revision %s in %s not found' % (self.revisionExpr,
1166 self.name))
1167
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001168 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169 """Perform only the local IO portion of the sync process.
1170 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001171 """
David James8d201162013-10-11 17:03:19 -07001172 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001173 all_refs = self.bare_ref.all
1174 self.CleanPublishedCache(all_refs)
1175 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001176
David Pursehouse1d947b32012-10-25 12:23:11 +09001177 def _doff():
1178 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001179 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001180
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001181 head = self.work_git.GetHead()
1182 if head.startswith(R_HEADS):
1183 branch = head[len(R_HEADS):]
1184 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001185 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001186 except KeyError:
1187 head = None
1188 else:
1189 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001191 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192 # Currently on a detached HEAD. The user is assumed to
1193 # not have any local modifications worth worrying about.
1194 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001195 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001196 syncbuf.fail(self, _PriorSyncFailedError())
1197 return
1198
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001199 if head == revid:
1200 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001201 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001202 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001203 if not syncbuf.detach_head:
1204 return
1205 else:
1206 lost = self._revlist(not_rev(revid), HEAD)
1207 if lost:
1208 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001209
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001210 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001211 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001212 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001213 syncbuf.fail(self, e)
1214 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001215 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001216 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001217
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001218 if head == revid:
1219 # No changes; don't do anything further.
1220 #
1221 return
1222
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001223 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001225 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001226 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001227 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001228 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001229 syncbuf.info(self,
1230 "leaving %s; does not track upstream",
1231 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001232 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001233 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001234 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001235 syncbuf.fail(self, e)
1236 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001237 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001238 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001239
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001240 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001241 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001243 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001244 if not_merged:
1245 if upstream_gain:
1246 # The user has published this branch and some of those
1247 # commits are not yet merged upstream. We do not want
1248 # to rewrite the published commits so we punt.
1249 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001250 syncbuf.fail(self,
1251 "branch %s is published (but not merged) and is now %d commits behind"
1252 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001253 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001254 elif pub == head:
1255 # All published commits are merged, and thus we are a
1256 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001257 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001258 syncbuf.later1(self, _doff)
1259 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001260
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001261 # Examine the local commits not in the remote. Find the
1262 # last one attributed to this user, if any.
1263 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001264 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001265 last_mine = None
1266 cnt_mine = 0
1267 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301268 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001269 if committer_email == self.UserEmail:
1270 last_mine = commit_id
1271 cnt_mine += 1
1272
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001273 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001274 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001275
1276 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001277 syncbuf.fail(self, _DirtyError())
1278 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001280 # If the upstream switched on us, warn the user.
1281 #
1282 if branch.merge != self.revisionExpr:
1283 if branch.merge and self.revisionExpr:
1284 syncbuf.info(self,
1285 'manifest switched %s...%s',
1286 branch.merge,
1287 self.revisionExpr)
1288 elif branch.merge:
1289 syncbuf.info(self,
1290 'manifest no longer tracks %s',
1291 branch.merge)
1292
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001293 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001295 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001297 syncbuf.info(self,
1298 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001299 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001301 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001302 if not ID_RE.match(self.revisionExpr):
1303 # in case of manifest sync the revisionExpr might be a SHA1
1304 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305 branch.Save()
1306
Mike Pontillod3153822012-02-28 11:53:24 -08001307 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001308 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001309 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001310 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001311 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001312 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001313 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001314 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001315 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001316 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001317 syncbuf.fail(self, e)
1318 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001320 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001322 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323 # dest should already be an absolute path, but src is project relative
1324 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001325 abssrc = os.path.join(self.worktree, src)
1326 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001328 def AddLinkFile(self, src, dest, absdest):
1329 # dest should already be an absolute path, but src is project relative
1330 # make src an absolute path
1331 abssrc = os.path.join(self.worktree, src)
1332 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1333
James W. Mills24c13082012-04-12 15:04:13 -05001334 def AddAnnotation(self, name, value, keep):
1335 self.annotations.append(_Annotation(name, value, keep))
1336
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001337 def DownloadPatchSet(self, change_id, patch_id):
1338 """Download a single patch set of a single change to FETCH_HEAD.
1339 """
1340 remote = self.GetRemote(self.remote.name)
1341
1342 cmd = ['fetch', remote.name]
1343 cmd.append('refs/changes/%2.2d/%d/%d' \
1344 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001345 if GitCommand(self, cmd, bare=True).Wait() != 0:
1346 return None
1347 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001348 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001349 change_id,
1350 patch_id,
1351 self.bare_git.rev_parse('FETCH_HEAD'))
1352
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353
1354## Branch Management ##
1355
1356 def StartBranch(self, name):
1357 """Create a new branch off the manifest's revision.
1358 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001359 head = self.work_git.GetHead()
1360 if head == (R_HEADS + name):
1361 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001362
David Pursehouse8a68ff92012-09-24 12:15:13 +09001363 all_refs = self.bare_ref.all
1364 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001365 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001366 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001367 capture_stdout = True,
1368 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001369
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001370 branch = self.GetBranch(name)
1371 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001372 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001373 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001374
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001375 if head.startswith(R_HEADS):
1376 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001377 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001378 except KeyError:
1379 head = None
1380
1381 if revid and head and revid == head:
1382 ref = os.path.join(self.gitdir, R_HEADS + name)
1383 try:
1384 os.makedirs(os.path.dirname(ref))
1385 except OSError:
1386 pass
1387 _lwrite(ref, '%s\n' % revid)
1388 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1389 'ref: %s%s\n' % (R_HEADS, name))
1390 branch.Save()
1391 return True
1392
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001393 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001394 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001395 capture_stdout = True,
1396 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001397 branch.Save()
1398 return True
1399 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001400
Wink Saville02d79452009-04-10 13:01:24 -07001401 def CheckoutBranch(self, name):
1402 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001403
1404 Args:
1405 name: The name of the branch to checkout.
1406
1407 Returns:
1408 True if the checkout succeeded; False if it didn't; None if the branch
1409 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001410 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001411 rev = R_HEADS + name
1412 head = self.work_git.GetHead()
1413 if head == rev:
1414 # Already on the branch
1415 #
1416 return True
Wink Saville02d79452009-04-10 13:01:24 -07001417
David Pursehouse8a68ff92012-09-24 12:15:13 +09001418 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001419 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001420 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001421 except KeyError:
1422 # Branch does not exist in this project
1423 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001424 return None
Wink Saville02d79452009-04-10 13:01:24 -07001425
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001426 if head.startswith(R_HEADS):
1427 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001428 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001429 except KeyError:
1430 head = None
1431
1432 if head == revid:
1433 # Same revision; just update HEAD to point to the new
1434 # target branch, but otherwise take no other action.
1435 #
1436 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1437 'ref: %s%s\n' % (R_HEADS, name))
1438 return True
1439
1440 return GitCommand(self,
1441 ['checkout', name, '--'],
1442 capture_stdout = True,
1443 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001444
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001445 def AbandonBranch(self, name):
1446 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001447
1448 Args:
1449 name: The name of the branch to abandon.
1450
1451 Returns:
1452 True if the abandon succeeded; False if it didn't; None if the branch
1453 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001454 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001455 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001456 all_refs = self.bare_ref.all
1457 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001458 # Doesn't exist
1459 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001460
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001461 head = self.work_git.GetHead()
1462 if head == rev:
1463 # We can't destroy the branch while we are sitting
1464 # on it. Switch to a detached HEAD.
1465 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001466 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001467
David Pursehouse8a68ff92012-09-24 12:15:13 +09001468 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001469 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001470 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1471 '%s\n' % revid)
1472 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001473 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001474
1475 return GitCommand(self,
1476 ['branch', '-D', name],
1477 capture_stdout = True,
1478 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001479
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001480 def PruneHeads(self):
1481 """Prune any topic branches already merged into upstream.
1482 """
1483 cb = self.CurrentBranch
1484 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001485 left = self._allrefs
1486 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001487 if name.startswith(R_HEADS):
1488 name = name[len(R_HEADS):]
1489 if cb is None or name != cb:
1490 kill.append(name)
1491
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001492 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001493 if cb is not None \
1494 and not self._revlist(HEAD + '...' + rev) \
1495 and not self.IsDirty(consider_untracked = False):
1496 self.work_git.DetachHead(HEAD)
1497 kill.append(cb)
1498
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001499 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001500 old = self.bare_git.GetHead()
1501 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001502 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1503
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001504 try:
1505 self.bare_git.DetachHead(rev)
1506
1507 b = ['branch', '-d']
1508 b.extend(kill)
1509 b = GitCommand(self, b, bare=True,
1510 capture_stdout=True,
1511 capture_stderr=True)
1512 b.Wait()
1513 finally:
1514 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001515 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001516
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001517 for branch in kill:
1518 if (R_HEADS + branch) not in left:
1519 self.CleanPublishedCache()
1520 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001521
1522 if cb and cb not in kill:
1523 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001524 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001525
1526 kept = []
1527 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001528 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001529 branch = self.GetBranch(branch)
1530 base = branch.LocalMerge
1531 if not base:
1532 base = rev
1533 kept.append(ReviewableBranch(self, branch, base))
1534 return kept
1535
1536
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001537## Submodule Management ##
1538
1539 def GetRegisteredSubprojects(self):
1540 result = []
1541 def rec(subprojects):
1542 if not subprojects:
1543 return
1544 result.extend(subprojects)
1545 for p in subprojects:
1546 rec(p.subprojects)
1547 rec(self.subprojects)
1548 return result
1549
1550 def _GetSubmodules(self):
1551 # Unfortunately we cannot call `git submodule status --recursive` here
1552 # because the working tree might not exist yet, and it cannot be used
1553 # without a working tree in its current implementation.
1554
1555 def get_submodules(gitdir, rev):
1556 # Parse .gitmodules for submodule sub_paths and sub_urls
1557 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1558 if not sub_paths:
1559 return []
1560 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1561 # revision of submodule repository
1562 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1563 submodules = []
1564 for sub_path, sub_url in zip(sub_paths, sub_urls):
1565 try:
1566 sub_rev = sub_revs[sub_path]
1567 except KeyError:
1568 # Ignore non-exist submodules
1569 continue
1570 submodules.append((sub_rev, sub_path, sub_url))
1571 return submodules
1572
1573 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1574 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1575 def parse_gitmodules(gitdir, rev):
1576 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1577 try:
1578 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1579 bare = True, gitdir = gitdir)
1580 except GitError:
1581 return [], []
1582 if p.Wait() != 0:
1583 return [], []
1584
1585 gitmodules_lines = []
1586 fd, temp_gitmodules_path = tempfile.mkstemp()
1587 try:
1588 os.write(fd, p.stdout)
1589 os.close(fd)
1590 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1591 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1592 bare = True, gitdir = gitdir)
1593 if p.Wait() != 0:
1594 return [], []
1595 gitmodules_lines = p.stdout.split('\n')
1596 except GitError:
1597 return [], []
1598 finally:
1599 os.remove(temp_gitmodules_path)
1600
1601 names = set()
1602 paths = {}
1603 urls = {}
1604 for line in gitmodules_lines:
1605 if not line:
1606 continue
1607 m = re_path.match(line)
1608 if m:
1609 names.add(m.group(1))
1610 paths[m.group(1)] = m.group(2)
1611 continue
1612 m = re_url.match(line)
1613 if m:
1614 names.add(m.group(1))
1615 urls[m.group(1)] = m.group(2)
1616 continue
1617 names = sorted(names)
1618 return ([paths.get(name, '') for name in names],
1619 [urls.get(name, '') for name in names])
1620
1621 def git_ls_tree(gitdir, rev, paths):
1622 cmd = ['ls-tree', rev, '--']
1623 cmd.extend(paths)
1624 try:
1625 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1626 bare = True, gitdir = gitdir)
1627 except GitError:
1628 return []
1629 if p.Wait() != 0:
1630 return []
1631 objects = {}
1632 for line in p.stdout.split('\n'):
1633 if not line.strip():
1634 continue
1635 object_rev, object_path = line.split()[2:4]
1636 objects[object_path] = object_rev
1637 return objects
1638
1639 try:
1640 rev = self.GetRevisionId()
1641 except GitError:
1642 return []
1643 return get_submodules(self.gitdir, rev)
1644
1645 def GetDerivedSubprojects(self):
1646 result = []
1647 if not self.Exists:
1648 # If git repo does not exist yet, querying its submodules will
1649 # mess up its states; so return here.
1650 return result
1651 for rev, path, url in self._GetSubmodules():
1652 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001653 relpath, worktree, gitdir, objdir = \
1654 self.manifest.GetSubprojectPaths(self, name, path)
1655 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001656 if project:
1657 result.extend(project.GetDerivedSubprojects())
1658 continue
David James8d201162013-10-11 17:03:19 -07001659
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001660 remote = RemoteSpec(self.remote.name,
1661 url = url,
Anthony King36ea2fb2014-05-06 11:54:01 +01001662 review = self.remote.review,
1663 revision = self.remote.revision)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001664 subproject = Project(manifest = self.manifest,
1665 name = name,
1666 remote = remote,
1667 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001668 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001669 worktree = worktree,
1670 relpath = relpath,
1671 revisionExpr = self.revisionExpr,
1672 revisionId = rev,
1673 rebase = self.rebase,
1674 groups = self.groups,
1675 sync_c = self.sync_c,
1676 sync_s = self.sync_s,
1677 parent = self,
1678 is_derived = True)
1679 result.append(subproject)
1680 result.extend(subproject.GetDerivedSubprojects())
1681 return result
1682
1683
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001684## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001685 def _CheckForSha1(self):
1686 try:
1687 # if revision (sha or tag) is not present then following function
1688 # throws an error.
1689 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1690 return True
1691 except GitError:
1692 # There is no such persistent revision. We have to fetch it.
1693 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001694
Julien Campergue335f5ef2013-10-16 11:02:35 +02001695 def _FetchArchive(self, tarpath, cwd=None):
1696 cmd = ['archive', '-v', '-o', tarpath]
1697 cmd.append('--remote=%s' % self.remote.url)
1698 cmd.append('--prefix=%s/' % self.relpath)
1699 cmd.append(self.revisionExpr)
1700
1701 command = GitCommand(self, cmd, cwd=cwd,
1702 capture_stdout=True,
1703 capture_stderr=True)
1704
1705 if command.Wait() != 0:
1706 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1707
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001708 def _RemoteFetch(self, name=None,
1709 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001710 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001711 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001712 alt_dir=None,
1713 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001714
1715 is_sha1 = False
1716 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001717 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001718
David Pursehouse9bc422f2014-04-15 10:28:56 +09001719 # The depth should not be used when fetching to a mirror because
1720 # it will result in a shallow repository that cannot be cloned or
1721 # fetched from.
1722 if not self.manifest.IsMirror:
1723 if self.clone_depth:
1724 depth = self.clone_depth
1725 else:
1726 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1727
Shawn Pearce69e04d82014-01-29 12:48:54 -08001728 if depth:
1729 current_branch_only = True
1730
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001731 if current_branch_only:
1732 if ID_RE.match(self.revisionExpr) is not None:
1733 is_sha1 = True
1734 elif self.revisionExpr.startswith(R_TAGS):
1735 # this is a tag and its sha1 value should never change
1736 tag_name = self.revisionExpr[len(R_TAGS):]
1737
1738 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001739 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001740 return True
Brian Harring14a66742012-09-28 20:21:57 -07001741 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1742 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001743
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001744 if not name:
1745 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001746
1747 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001748 remote = self.GetRemote(name)
1749 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001750 ssh_proxy = True
1751
Shawn O. Pearce88443382010-10-08 10:02:09 +02001752 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001753 if alt_dir and 'objects' == os.path.basename(alt_dir):
1754 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001755 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1756 remote = self.GetRemote(name)
1757
David Pursehouse8a68ff92012-09-24 12:15:13 +09001758 all_refs = self.bare_ref.all
1759 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001760 tmp = set()
1761
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301762 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001763 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001764 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001765 all_refs[r] = ref_id
1766 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001767 continue
1768
David Pursehouse8a68ff92012-09-24 12:15:13 +09001769 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001770 continue
1771
David Pursehouse8a68ff92012-09-24 12:15:13 +09001772 r = 'refs/_alt/%s' % ref_id
1773 all_refs[r] = ref_id
1774 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001775 tmp.add(r)
1776
Shawn O. Pearce88443382010-10-08 10:02:09 +02001777 tmp_packed = ''
1778 old_packed = ''
1779
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301780 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001781 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001782 tmp_packed += line
1783 if r not in tmp:
1784 old_packed += line
1785
1786 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001787 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001788 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001789
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001790 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001791
1792 # The --depth option only affects the initial fetch; after that we'll do
1793 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001794 if depth and initial:
1795 cmd.append('--depth=%s' % depth)
1796
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001797 if quiet:
1798 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001799 if not self.worktree:
1800 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001801 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001802
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001803 # If using depth then we should not get all the tags since they may
1804 # be outside of the depth.
1805 if no_tags or depth:
1806 cmd.append('--no-tags')
1807 else:
1808 cmd.append('--tags')
1809
Brian Harring14a66742012-09-28 20:21:57 -07001810 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001811 # Fetch whole repo
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301812 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001813 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001814 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001815 cmd.append(tag_name)
1816 else:
1817 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001818 if is_sha1:
1819 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001820 if branch.startswith(R_HEADS):
1821 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301822 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001823
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001824 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001825 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001826 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1827 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001828 ok = True
1829 break
Brian Harring14a66742012-09-28 20:21:57 -07001830 elif current_branch_only and is_sha1 and ret == 128:
1831 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1832 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1833 # abort the optimization attempt and do a full sync.
1834 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001835 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001836
1837 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001838 # Ensure that some refs exist. Otherwise, we probably aren't looking
1839 # at a real git repository and may have a bad url.
1840 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001841 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001842
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001843 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001844 if old_packed != '':
1845 _lwrite(packed_refs, old_packed)
1846 else:
1847 os.remove(packed_refs)
1848 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001849
1850 if is_sha1 and current_branch_only and self.upstream:
1851 # We just synced the upstream given branch; verify we
1852 # got what we wanted, else trigger a second run of all
1853 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001854 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001855 return self._RemoteFetch(name=name, current_branch_only=False,
1856 initial=False, quiet=quiet, alt_dir=alt_dir)
1857
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001858 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001859
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001860 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001861 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001862 return False
1863
1864 remote = self.GetRemote(self.remote.name)
1865 bundle_url = remote.url + '/clone.bundle'
1866 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001867 if GetSchemeFromUrl(bundle_url) not in (
1868 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001869 return False
1870
1871 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1872 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1873
1874 exist_dst = os.path.exists(bundle_dst)
1875 exist_tmp = os.path.exists(bundle_tmp)
1876
1877 if not initial and not exist_dst and not exist_tmp:
1878 return False
1879
1880 if not exist_dst:
1881 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1882 if not exist_dst:
1883 return False
1884
1885 cmd = ['fetch']
1886 if quiet:
1887 cmd.append('--quiet')
1888 if not self.worktree:
1889 cmd.append('--update-head-ok')
1890 cmd.append(bundle_dst)
1891 for f in remote.fetch:
1892 cmd.append(str(f))
1893 cmd.append('refs/tags/*:refs/tags/*')
1894
1895 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001896 if os.path.exists(bundle_dst):
1897 os.remove(bundle_dst)
1898 if os.path.exists(bundle_tmp):
1899 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001900 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001901
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001902 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001903 if os.path.exists(dstPath):
1904 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001905
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001906 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001907 if quiet:
1908 cmd += ['--silent']
1909 if os.path.exists(tmpPath):
1910 size = os.stat(tmpPath).st_size
1911 if size >= 1024:
1912 cmd += ['--continue-at', '%d' % (size,)]
1913 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001914 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001915 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1916 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001917 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001918 if cookiefile:
1919 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001920 if srcUrl.startswith('persistent-'):
1921 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001922 cmd += [srcUrl]
1923
1924 if IsTrace():
1925 Trace('%s', ' '.join(cmd))
1926 try:
1927 proc = subprocess.Popen(cmd)
1928 except OSError:
1929 return False
1930
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001931 curlret = proc.wait()
1932
1933 if curlret == 22:
1934 # From curl man page:
1935 # 22: HTTP page not retrieved. The requested url was not found or
1936 # returned another error with the HTTP error code being 400 or above.
1937 # This return code only appears if -f, --fail is used.
1938 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001939 print("Server does not provide clone.bundle; ignoring.",
1940 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001941 return False
1942
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001943 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001944 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001945 os.rename(tmpPath, dstPath)
1946 return True
1947 else:
1948 os.remove(tmpPath)
1949 return False
1950 else:
1951 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001952
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001953 def _IsValidBundle(self, path):
1954 try:
1955 with open(path) as f:
1956 if f.read(16) == '# v2 git bundle\n':
1957 return True
1958 else:
1959 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1960 return False
1961 except OSError:
1962 return False
1963
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001964 def _GetBundleCookieFile(self, url):
1965 if url.startswith('persistent-'):
1966 try:
1967 p = subprocess.Popen(
1968 ['git-remote-persistent-https', '-print_config', url],
1969 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1970 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001971 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001972 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001973 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001974 for line in p.stdout:
1975 line = line.strip()
1976 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001977 cookiefile = line[len(prefix):]
1978 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001979 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001980 err_msg = p.stderr.read()
1981 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001982 pass # Persistent proxy doesn't support -print_config.
1983 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001984 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001985 if cookiefile:
1986 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001987 except OSError as e:
1988 if e.errno == errno.ENOENT:
1989 pass # No persistent proxy.
1990 raise
1991 return GitConfig.ForUser().GetString('http.cookiefile')
1992
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001993 def _Checkout(self, rev, quiet=False):
1994 cmd = ['checkout']
1995 if quiet:
1996 cmd.append('-q')
1997 cmd.append(rev)
1998 cmd.append('--')
1999 if GitCommand(self, cmd).Wait() != 0:
2000 if self._allrefs:
2001 raise GitError('%s checkout %s ' % (self.name, rev))
2002
Pierre Tardye5a21222011-03-24 16:28:18 +01002003 def _CherryPick(self, rev, quiet=False):
2004 cmd = ['cherry-pick']
2005 cmd.append(rev)
2006 cmd.append('--')
2007 if GitCommand(self, cmd).Wait() != 0:
2008 if self._allrefs:
2009 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2010
Erwan Mahea94f1622011-08-19 13:56:09 +02002011 def _Revert(self, rev, quiet=False):
2012 cmd = ['revert']
2013 cmd.append('--no-edit')
2014 cmd.append(rev)
2015 cmd.append('--')
2016 if GitCommand(self, cmd).Wait() != 0:
2017 if self._allrefs:
2018 raise GitError('%s revert %s ' % (self.name, rev))
2019
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002020 def _ResetHard(self, rev, quiet=True):
2021 cmd = ['reset', '--hard']
2022 if quiet:
2023 cmd.append('-q')
2024 cmd.append(rev)
2025 if GitCommand(self, cmd).Wait() != 0:
2026 raise GitError('%s reset --hard %s ' % (self.name, rev))
2027
2028 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002029 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002030 if onto is not None:
2031 cmd.extend(['--onto', onto])
2032 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002033 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002034 raise GitError('%s rebase %s ' % (self.name, upstream))
2035
Pierre Tardy3d125942012-05-04 12:18:12 +02002036 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002037 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002038 if ffonly:
2039 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002040 if GitCommand(self, cmd).Wait() != 0:
2041 raise GitError('%s merge %s ' % (self.name, head))
2042
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002043 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002044 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002045
2046 # Initialize the bare repository, which contains all of the objects.
2047 if not os.path.exists(self.objdir):
2048 os.makedirs(self.objdir)
2049 self.bare_objdir.init()
2050
2051 # If we have a separate directory to hold refs, initialize it as well.
2052 if self.objdir != self.gitdir:
2053 os.makedirs(self.gitdir)
2054 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2055 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002056
Shawn O. Pearce88443382010-10-08 10:02:09 +02002057 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002058 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002059
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002060 if ref_dir or mirror_git:
2061 if not mirror_git:
2062 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002063 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2064 self.relpath + '.git')
2065
2066 if os.path.exists(mirror_git):
2067 ref_dir = mirror_git
2068
2069 elif os.path.exists(repo_git):
2070 ref_dir = repo_git
2071
2072 else:
2073 ref_dir = None
2074
2075 if ref_dir:
2076 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2077 os.path.join(ref_dir, 'objects') + '\n')
2078
Jimmie Westera0444582012-10-24 13:44:42 +02002079 self._UpdateHooks()
2080
2081 m = self.manifest.manifestProject.config
2082 for key in ['user.name', 'user.email']:
2083 if m.Has(key, include_defaults = False):
2084 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002085 if self.manifest.IsMirror:
2086 self.config.SetString('core.bare', 'true')
2087 else:
2088 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002089
Jimmie Westera0444582012-10-24 13:44:42 +02002090 def _UpdateHooks(self):
2091 if os.path.exists(self.gitdir):
2092 # Always recreate hooks since they can have been changed
2093 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002094 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002095 try:
2096 to_rm = os.listdir(hooks)
2097 except OSError:
2098 to_rm = []
2099 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002100 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002101 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002102
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002103 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002104 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002105 if not os.path.exists(hooks):
2106 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002107 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002108 name = os.path.basename(stock_hook)
2109
Victor Boivie65e0f352011-04-18 11:23:29 +02002110 if name in ('commit-msg',) and not self.remote.review \
2111 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002112 # Don't install a Gerrit Code Review hook if this
2113 # project does not appear to use it for reviews.
2114 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002115 # Since the manifest project is one of those, but also
2116 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002117 continue
2118
2119 dst = os.path.join(hooks, name)
2120 if os.path.islink(dst):
2121 continue
2122 if os.path.exists(dst):
2123 if filecmp.cmp(stock_hook, dst, shallow=False):
2124 os.remove(dst)
2125 else:
2126 _error("%s: Not replacing %s hook", self.relpath, name)
2127 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002128 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002129 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002130 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002131 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002132 raise GitError('filesystem must support symlinks')
2133 else:
2134 raise
2135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002136 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002137 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002138 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002139 remote.url = self.remote.url
2140 remote.review = self.remote.review
2141 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002142
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002143 if self.worktree:
2144 remote.ResetFetch(mirror=False)
2145 else:
2146 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002147 remote.Save()
2148
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002149 def _InitMRef(self):
2150 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002151 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002152
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002153 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002154 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002155
2156 def _InitAnyMRef(self, ref):
2157 cur = self.bare_ref.symref(ref)
2158
2159 if self.revisionId:
2160 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2161 msg = 'manifest set to %s' % self.revisionId
2162 dst = self.revisionId + '^0'
2163 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2164 else:
2165 remote = self.GetRemote(self.remote.name)
2166 dst = remote.ToLocal(self.revisionExpr)
2167 if cur != dst:
2168 msg = 'manifest set to %s' % self.revisionExpr
2169 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002170
David James8d201162013-10-11 17:03:19 -07002171 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2172 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2173
2174 Args:
2175 gitdir: The bare git repository. Must already be initialized.
2176 dotgit: The repository you would like to initialize.
2177 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2178 Only one work tree can store refs under a given |gitdir|.
2179 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2180 This saves you the effort of initializing |dotgit| yourself.
2181 """
2182 # These objects can be shared between several working trees.
2183 symlink_files = ['description', 'info']
2184 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2185 if share_refs:
2186 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002187 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002188 symlink_dirs += ['logs', 'refs']
2189 to_symlink = symlink_files + symlink_dirs
2190
2191 to_copy = []
2192 if copy_all:
2193 to_copy = os.listdir(gitdir)
2194
2195 for name in set(to_copy).union(to_symlink):
2196 try:
2197 src = os.path.realpath(os.path.join(gitdir, name))
2198 dst = os.path.realpath(os.path.join(dotgit, name))
2199
2200 if os.path.lexists(dst) and not os.path.islink(dst):
2201 raise GitError('cannot overwrite a local work tree')
2202
2203 # If the source dir doesn't exist, create an empty dir.
2204 if name in symlink_dirs and not os.path.lexists(src):
2205 os.makedirs(src)
2206
2207 if name in to_symlink:
2208 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2209 elif copy_all and not os.path.islink(dst):
2210 if os.path.isdir(src):
2211 shutil.copytree(src, dst)
2212 elif os.path.isfile(src):
2213 shutil.copy(src, dst)
2214 except OSError as e:
2215 if e.errno == errno.EPERM:
2216 raise GitError('filesystem must support symlinks')
2217 else:
2218 raise
2219
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002220 def _InitWorkTree(self):
2221 dotgit = os.path.join(self.worktree, '.git')
2222 if not os.path.exists(dotgit):
2223 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002224 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2225 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002226
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002227 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002228
2229 cmd = ['read-tree', '--reset', '-u']
2230 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002231 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002232 if GitCommand(self, cmd).Wait() != 0:
2233 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002234
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002235 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002236
2237 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002238 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002239
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002240 def _revlist(self, *args, **kw):
2241 a = []
2242 a.extend(args)
2243 a.append('--')
2244 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002245
2246 @property
2247 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002248 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002249
Julien Camperguedd654222014-01-09 16:21:37 +01002250 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2251 """Get logs between two revisions of this project."""
2252 comp = '..'
2253 if rev1:
2254 revs = [rev1]
2255 if rev2:
2256 revs.extend([comp, rev2])
2257 cmd = ['log', ''.join(revs)]
2258 out = DiffColoring(self.config)
2259 if out.is_on and color:
2260 cmd.append('--color')
2261 if oneline:
2262 cmd.append('--oneline')
2263
2264 try:
2265 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2266 if log.Wait() == 0:
2267 return log.stdout
2268 except GitError:
2269 # worktree may not exist if groups changed for example. In that case,
2270 # try in gitdir instead.
2271 if not os.path.exists(self.worktree):
2272 return self.bare_git.log(*cmd[1:])
2273 else:
2274 raise
2275 return None
2276
2277 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2278 """Get the list of logs from this revision to given revisionId"""
2279 logs = {}
2280 selfId = self.GetRevisionId(self._allrefs)
2281 toId = toProject.GetRevisionId(toProject._allrefs)
2282
2283 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2284 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2285 return logs
2286
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002287 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002288 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002289 self._project = project
2290 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002291 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002292
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002293 def LsOthers(self):
2294 p = GitCommand(self._project,
2295 ['ls-files',
2296 '-z',
2297 '--others',
2298 '--exclude-standard'],
2299 bare = False,
David James8d201162013-10-11 17:03:19 -07002300 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002301 capture_stdout = True,
2302 capture_stderr = True)
2303 if p.Wait() == 0:
2304 out = p.stdout
2305 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002306 return out[:-1].split('\0') # pylint: disable=W1401
2307 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002308 return []
2309
2310 def DiffZ(self, name, *args):
2311 cmd = [name]
2312 cmd.append('-z')
2313 cmd.extend(args)
2314 p = GitCommand(self._project,
2315 cmd,
David James8d201162013-10-11 17:03:19 -07002316 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002317 bare = False,
2318 capture_stdout = True,
2319 capture_stderr = True)
2320 try:
2321 out = p.process.stdout.read()
2322 r = {}
2323 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002324 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002325 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002326 try:
2327 info = out.next()
2328 path = out.next()
2329 except StopIteration:
2330 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002331
2332 class _Info(object):
2333 def __init__(self, path, omode, nmode, oid, nid, state):
2334 self.path = path
2335 self.src_path = None
2336 self.old_mode = omode
2337 self.new_mode = nmode
2338 self.old_id = oid
2339 self.new_id = nid
2340
2341 if len(state) == 1:
2342 self.status = state
2343 self.level = None
2344 else:
2345 self.status = state[:1]
2346 self.level = state[1:]
2347 while self.level.startswith('0'):
2348 self.level = self.level[1:]
2349
2350 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002351 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002352 if info.status in ('R', 'C'):
2353 info.src_path = info.path
2354 info.path = out.next()
2355 r[info.path] = info
2356 return r
2357 finally:
2358 p.Wait()
2359
2360 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002361 if self._bare:
2362 path = os.path.join(self._project.gitdir, HEAD)
2363 else:
2364 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002365 try:
2366 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002367 except IOError as e:
2368 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002369 try:
2370 line = fd.read()
2371 finally:
2372 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302373 try:
2374 line = line.decode()
2375 except AttributeError:
2376 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002377 if line.startswith('ref: '):
2378 return line[5:-1]
2379 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002380
2381 def SetHead(self, ref, message=None):
2382 cmdv = []
2383 if message is not None:
2384 cmdv.extend(['-m', message])
2385 cmdv.append(HEAD)
2386 cmdv.append(ref)
2387 self.symbolic_ref(*cmdv)
2388
2389 def DetachHead(self, new, message=None):
2390 cmdv = ['--no-deref']
2391 if message is not None:
2392 cmdv.extend(['-m', message])
2393 cmdv.append(HEAD)
2394 cmdv.append(new)
2395 self.update_ref(*cmdv)
2396
2397 def UpdateRef(self, name, new, old=None,
2398 message=None,
2399 detach=False):
2400 cmdv = []
2401 if message is not None:
2402 cmdv.extend(['-m', message])
2403 if detach:
2404 cmdv.append('--no-deref')
2405 cmdv.append(name)
2406 cmdv.append(new)
2407 if old is not None:
2408 cmdv.append(old)
2409 self.update_ref(*cmdv)
2410
2411 def DeleteRef(self, name, old=None):
2412 if not old:
2413 old = self.rev_parse(name)
2414 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002415 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002416
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002417 def rev_list(self, *args, **kw):
2418 if 'format' in kw:
2419 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2420 else:
2421 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002422 cmdv.extend(args)
2423 p = GitCommand(self._project,
2424 cmdv,
2425 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002426 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002427 capture_stdout = True,
2428 capture_stderr = True)
2429 r = []
2430 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002431 if line[-1] == '\n':
2432 line = line[:-1]
2433 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002434 if p.Wait() != 0:
2435 raise GitError('%s rev-list %s: %s' % (
2436 self._project.name,
2437 str(args),
2438 p.stderr))
2439 return r
2440
2441 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002442 """Allow arbitrary git commands using pythonic syntax.
2443
2444 This allows you to do things like:
2445 git_obj.rev_parse('HEAD')
2446
2447 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2448 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002449 Any other positional arguments will be passed to the git command, and the
2450 following keyword arguments are supported:
2451 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002452
2453 Args:
2454 name: The name of the git command to call. Any '_' characters will
2455 be replaced with '-'.
2456
2457 Returns:
2458 A callable object that will try to call git with the named command.
2459 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002460 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002461 def runner(*args, **kwargs):
2462 cmdv = []
2463 config = kwargs.pop('config', None)
2464 for k in kwargs:
2465 raise TypeError('%s() got an unexpected keyword argument %r'
2466 % (name, k))
2467 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002468 if not git_require((1, 7, 2)):
2469 raise ValueError('cannot set config on command line for %s()'
2470 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302471 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002472 cmdv.append('-c')
2473 cmdv.append('%s=%s' % (k, v))
2474 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002475 cmdv.extend(args)
2476 p = GitCommand(self._project,
2477 cmdv,
2478 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002479 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002480 capture_stdout = True,
2481 capture_stderr = True)
2482 if p.Wait() != 0:
2483 raise GitError('%s %s: %s' % (
2484 self._project.name,
2485 name,
2486 p.stderr))
2487 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302488 try:
Conley Owensedd01512013-09-26 12:59:58 -07002489 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302490 except AttributeError:
2491 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002492 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2493 return r[:-1]
2494 return r
2495 return runner
2496
2497
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002498class _PriorSyncFailedError(Exception):
2499 def __str__(self):
2500 return 'prior sync failed; rebase still in progress'
2501
2502class _DirtyError(Exception):
2503 def __str__(self):
2504 return 'contains uncommitted changes'
2505
2506class _InfoMessage(object):
2507 def __init__(self, project, text):
2508 self.project = project
2509 self.text = text
2510
2511 def Print(self, syncbuf):
2512 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2513 syncbuf.out.nl()
2514
2515class _Failure(object):
2516 def __init__(self, project, why):
2517 self.project = project
2518 self.why = why
2519
2520 def Print(self, syncbuf):
2521 syncbuf.out.fail('error: %s/: %s',
2522 self.project.relpath,
2523 str(self.why))
2524 syncbuf.out.nl()
2525
2526class _Later(object):
2527 def __init__(self, project, action):
2528 self.project = project
2529 self.action = action
2530
2531 def Run(self, syncbuf):
2532 out = syncbuf.out
2533 out.project('project %s/', self.project.relpath)
2534 out.nl()
2535 try:
2536 self.action()
2537 out.nl()
2538 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002539 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002540 out.nl()
2541 return False
2542
2543class _SyncColoring(Coloring):
2544 def __init__(self, config):
2545 Coloring.__init__(self, config, 'reposync')
2546 self.project = self.printer('header', attr = 'bold')
2547 self.info = self.printer('info')
2548 self.fail = self.printer('fail', fg='red')
2549
2550class SyncBuffer(object):
2551 def __init__(self, config, detach_head=False):
2552 self._messages = []
2553 self._failures = []
2554 self._later_queue1 = []
2555 self._later_queue2 = []
2556
2557 self.out = _SyncColoring(config)
2558 self.out.redirect(sys.stderr)
2559
2560 self.detach_head = detach_head
2561 self.clean = True
2562
2563 def info(self, project, fmt, *args):
2564 self._messages.append(_InfoMessage(project, fmt % args))
2565
2566 def fail(self, project, err=None):
2567 self._failures.append(_Failure(project, err))
2568 self.clean = False
2569
2570 def later1(self, project, what):
2571 self._later_queue1.append(_Later(project, what))
2572
2573 def later2(self, project, what):
2574 self._later_queue2.append(_Later(project, what))
2575
2576 def Finish(self):
2577 self._PrintMessages()
2578 self._RunLater()
2579 self._PrintMessages()
2580 return self.clean
2581
2582 def _RunLater(self):
2583 for q in ['_later_queue1', '_later_queue2']:
2584 if not self._RunQueue(q):
2585 return
2586
2587 def _RunQueue(self, queue):
2588 for m in getattr(self, queue):
2589 if not m.Run(self):
2590 self.clean = False
2591 return False
2592 setattr(self, queue, [])
2593 return True
2594
2595 def _PrintMessages(self):
2596 for m in self._messages:
2597 m.Print(self)
2598 for m in self._failures:
2599 m.Print(self)
2600
2601 self._messages = []
2602 self._failures = []
2603
2604
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002605class MetaProject(Project):
2606 """A special project housed under .repo.
2607 """
2608 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002609 Project.__init__(self,
2610 manifest = manifest,
2611 name = name,
2612 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002613 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002614 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002615 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002616 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002617 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002618 revisionId = None,
2619 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002620
2621 def PreSync(self):
2622 if self.Exists:
2623 cb = self.CurrentBranch
2624 if cb:
2625 base = self.GetBranch(cb).merge
2626 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002627 self.revisionExpr = base
2628 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002629
Florian Vallee5d016502012-06-07 17:19:26 +02002630 def MetaBranchSwitch(self, target):
2631 """ Prepare MetaProject for manifest branch switch
2632 """
2633
2634 # detach and delete manifest branch, allowing a new
2635 # branch to take over
2636 syncbuf = SyncBuffer(self.config, detach_head = True)
2637 self.Sync_LocalHalf(syncbuf)
2638 syncbuf.Finish()
2639
2640 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002641 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002642 capture_stdout = True,
2643 capture_stderr = True).Wait() == 0
2644
2645
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002646 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002647 def LastFetch(self):
2648 try:
2649 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2650 return os.path.getmtime(fh)
2651 except OSError:
2652 return 0
2653
2654 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002655 def HasChanges(self):
2656 """Has the remote received new commits not yet checked out?
2657 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002658 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002659 return False
2660
David Pursehouse8a68ff92012-09-24 12:15:13 +09002661 all_refs = self.bare_ref.all
2662 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002663 head = self.work_git.GetHead()
2664 if head.startswith(R_HEADS):
2665 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002666 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002667 except KeyError:
2668 head = None
2669
2670 if revid == head:
2671 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002672 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002673 return True
2674 return False