blob: f43bcc52d0d9b32c72c8399987a2b547721e1bd9 [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,
262 review = None):
263 self.name = name
264 self.url = url
265 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700266
Doug Anderson37282b42011-03-04 11:54:18 -0800267class RepoHook(object):
268 """A RepoHook contains information about a script to run as a hook.
269
270 Hooks are used to run a python script before running an upload (for instance,
271 to run presubmit checks). Eventually, we may have hooks for other actions.
272
273 This shouldn't be confused with files in the 'repo/hooks' directory. Those
274 files are copied into each '.git/hooks' folder for each project. Repo-level
275 hooks are associated instead with repo actions.
276
277 Hooks are always python. When a hook is run, we will load the hook into the
278 interpreter and execute its main() function.
279 """
280 def __init__(self,
281 hook_type,
282 hooks_project,
283 topdir,
284 abort_if_user_denies=False):
285 """RepoHook constructor.
286
287 Params:
288 hook_type: A string representing the type of hook. This is also used
289 to figure out the name of the file containing the hook. For
290 example: 'pre-upload'.
291 hooks_project: The project containing the repo hooks. If you have a
292 manifest, this is manifest.repo_hooks_project. OK if this is None,
293 which will make the hook a no-op.
294 topdir: Repo's top directory (the one containing the .repo directory).
295 Scripts will run with CWD as this directory. If you have a manifest,
296 this is manifest.topdir
297 abort_if_user_denies: If True, we'll throw a HookError() if the user
298 doesn't allow us to run the hook.
299 """
300 self._hook_type = hook_type
301 self._hooks_project = hooks_project
302 self._topdir = topdir
303 self._abort_if_user_denies = abort_if_user_denies
304
305 # Store the full path to the script for convenience.
306 if self._hooks_project:
307 self._script_fullpath = os.path.join(self._hooks_project.worktree,
308 self._hook_type + '.py')
309 else:
310 self._script_fullpath = None
311
312 def _GetHash(self):
313 """Return a hash of the contents of the hooks directory.
314
315 We'll just use git to do this. This hash has the property that if anything
316 changes in the directory we will return a different has.
317
318 SECURITY CONSIDERATION:
319 This hash only represents the contents of files in the hook directory, not
320 any other files imported or called by hooks. Changes to imported files
321 can change the script behavior without affecting the hash.
322
323 Returns:
324 A string representing the hash. This will always be ASCII so that it can
325 be printed to the user easily.
326 """
327 assert self._hooks_project, "Must have hooks to calculate their hash."
328
329 # We will use the work_git object rather than just calling GetRevisionId().
330 # That gives us a hash of the latest checked in version of the files that
331 # the user will actually be executing. Specifically, GetRevisionId()
332 # doesn't appear to change even if a user checks out a different version
333 # of the hooks repo (via git checkout) nor if a user commits their own revs.
334 #
335 # NOTE: Local (non-committed) changes will not be factored into this hash.
336 # I think this is OK, since we're really only worried about warning the user
337 # about upstream changes.
338 return self._hooks_project.work_git.rev_parse('HEAD')
339
340 def _GetMustVerb(self):
341 """Return 'must' if the hook is required; 'should' if not."""
342 if self._abort_if_user_denies:
343 return 'must'
344 else:
345 return 'should'
346
347 def _CheckForHookApproval(self):
348 """Check to see whether this hook has been approved.
349
350 We'll look at the hash of all of the hooks. If this matches the hash that
351 the user last approved, we're done. If it doesn't, we'll ask the user
352 about approval.
353
354 Note that we ask permission for each individual hook even though we use
355 the hash of all hooks when detecting changes. We'd like the user to be
356 able to approve / deny each hook individually. We only use the hash of all
357 hooks because there is no other easy way to detect changes to local imports.
358
359 Returns:
360 True if this hook is approved to run; False otherwise.
361
362 Raises:
363 HookError: Raised if the user doesn't approve and abort_if_user_denies
364 was passed to the consturctor.
365 """
Doug Anderson37282b42011-03-04 11:54:18 -0800366 hooks_config = self._hooks_project.config
367 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
368
369 # Get the last hash that the user approved for this hook; may be None.
370 old_hash = hooks_config.GetString(git_approval_key)
371
372 # Get the current hash so we can tell if scripts changed since approval.
373 new_hash = self._GetHash()
374
375 if old_hash is not None:
376 # User previously approved hook and asked not to be prompted again.
377 if new_hash == old_hash:
378 # Approval matched. We're done.
379 return True
380 else:
381 # Give the user a reason why we're prompting, since they last told
382 # us to "never ask again".
383 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
384 self._hook_type)
385 else:
386 prompt = ''
387
388 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
389 if sys.stdout.isatty():
390 prompt += ('Repo %s run the script:\n'
391 ' %s\n'
392 '\n'
393 'Do you want to allow this script to run '
394 '(yes/yes-never-ask-again/NO)? ') % (
395 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530396 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900397 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800398
399 # User is doing a one-time approval.
400 if response in ('y', 'yes'):
401 return True
402 elif response == 'yes-never-ask-again':
403 hooks_config.SetString(git_approval_key, new_hash)
404 return True
405
406 # For anything else, we'll assume no approval.
407 if self._abort_if_user_denies:
408 raise HookError('You must allow the %s hook or use --no-verify.' %
409 self._hook_type)
410
411 return False
412
413 def _ExecuteHook(self, **kwargs):
414 """Actually execute the given hook.
415
416 This will run the hook's 'main' function in our python interpreter.
417
418 Args:
419 kwargs: Keyword arguments to pass to the hook. These are often specific
420 to the hook type. For instance, pre-upload hooks will contain
421 a project_list.
422 """
423 # Keep sys.path and CWD stashed away so that we can always restore them
424 # upon function exit.
425 orig_path = os.getcwd()
426 orig_syspath = sys.path
427
428 try:
429 # Always run hooks with CWD as topdir.
430 os.chdir(self._topdir)
431
432 # Put the hook dir as the first item of sys.path so hooks can do
433 # relative imports. We want to replace the repo dir as [0] so
434 # hooks can't import repo files.
435 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
436
437 # Exec, storing global context in the context dict. We catch exceptions
438 # and convert to a HookError w/ just the failing traceback.
439 context = {}
440 try:
441 execfile(self._script_fullpath, context)
442 except Exception:
443 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
444 traceback.format_exc(), self._hook_type))
445
446 # Running the script should have defined a main() function.
447 if 'main' not in context:
448 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
449
450
451 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
452 # We don't actually want hooks to define their main with this argument--
453 # it's there to remind them that their hook should always take **kwargs.
454 # For instance, a pre-upload hook should be defined like:
455 # def main(project_list, **kwargs):
456 #
457 # This allows us to later expand the API without breaking old hooks.
458 kwargs = kwargs.copy()
459 kwargs['hook_should_take_kwargs'] = True
460
461 # Call the main function in the hook. If the hook should cause the
462 # build to fail, it will raise an Exception. We'll catch that convert
463 # to a HookError w/ just the failing traceback.
464 try:
465 context['main'](**kwargs)
466 except Exception:
467 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
468 'above.' % (
469 traceback.format_exc(), self._hook_type))
470 finally:
471 # Restore sys.path and CWD.
472 sys.path = orig_syspath
473 os.chdir(orig_path)
474
475 def Run(self, user_allows_all_hooks, **kwargs):
476 """Run the hook.
477
478 If the hook doesn't exist (because there is no hooks project or because
479 this particular hook is not enabled), this is a no-op.
480
481 Args:
482 user_allows_all_hooks: If True, we will never prompt about running the
483 hook--we'll just assume it's OK to run it.
484 kwargs: Keyword arguments to pass to the hook. These are often specific
485 to the hook type. For instance, pre-upload hooks will contain
486 a project_list.
487
488 Raises:
489 HookError: If there was a problem finding the hook or the user declined
490 to run a required hook (from _CheckForHookApproval).
491 """
492 # No-op if there is no hooks project or if hook is disabled.
493 if ((not self._hooks_project) or
494 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
495 return
496
497 # Bail with a nice error if we can't find the hook.
498 if not os.path.isfile(self._script_fullpath):
499 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
500
501 # Make sure the user is OK with running the hook.
502 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
503 return
504
505 # Run the hook with the same version of python we're using.
506 self._ExecuteHook(**kwargs)
507
508
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700509class Project(object):
510 def __init__(self,
511 manifest,
512 name,
513 remote,
514 gitdir,
David James8d201162013-10-11 17:03:19 -0700515 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700516 worktree,
517 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700518 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800519 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700520 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700521 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700522 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800523 sync_s = False,
David Pursehouseede7f122012-11-27 22:25:30 +0900524 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800525 upstream = None,
526 parent = None,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400527 is_derived = False,
528 dest_branch = None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800529 """Init a Project object.
530
531 Args:
532 manifest: The XmlManifest object.
533 name: The `name` attribute of manifest.xml's project element.
534 remote: RemoteSpec object specifying its remote's properties.
535 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700536 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800537 worktree: Absolute path of git working tree.
538 relpath: Relative path of git working tree to repo's top directory.
539 revisionExpr: The `revision` attribute of manifest.xml's project element.
540 revisionId: git commit id for checking out.
541 rebase: The `rebase` attribute of manifest.xml's project element.
542 groups: The `groups` attribute of manifest.xml's project element.
543 sync_c: The `sync-c` attribute of manifest.xml's project element.
544 sync_s: The `sync-s` attribute of manifest.xml's project element.
545 upstream: The `upstream` attribute of manifest.xml's project element.
546 parent: The parent Project object.
547 is_derived: False if the project was explicitly defined in the manifest;
548 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400549 dest_branch: The branch to which to push changes for review by default.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800550 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700551 self.manifest = manifest
552 self.name = name
553 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800554 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700555 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800556 if worktree:
557 self.worktree = worktree.replace('\\', '/')
558 else:
559 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700560 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700561 self.revisionExpr = revisionExpr
562
563 if revisionId is None \
564 and revisionExpr \
565 and IsId(revisionExpr):
566 self.revisionId = revisionExpr
567 else:
568 self.revisionId = revisionId
569
Mike Pontillod3153822012-02-28 11:53:24 -0800570 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700571 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700572 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800573 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900574 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700575 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800576 self.parent = parent
577 self.is_derived = is_derived
578 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800579
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700580 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700581 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500582 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500583 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700584 self.config = GitConfig.ForRepository(
585 gitdir = self.gitdir,
586 defaults = self.manifest.globalConfig)
587
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800588 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700589 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800590 else:
591 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700592 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700593 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700594 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400595 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700596
Doug Anderson37282b42011-03-04 11:54:18 -0800597 # This will be filled in if a project is later identified to be the
598 # project containing repo hooks.
599 self.enabled_repo_hooks = []
600
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700601 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800602 def Derived(self):
603 return self.is_derived
604
605 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700606 def Exists(self):
607 return os.path.isdir(self.gitdir)
608
609 @property
610 def CurrentBranch(self):
611 """Obtain the name of the currently checked out branch.
612 The branch name omits the 'refs/heads/' prefix.
613 None is returned if the project is on a detached HEAD.
614 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700615 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700616 if b.startswith(R_HEADS):
617 return b[len(R_HEADS):]
618 return None
619
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700620 def IsRebaseInProgress(self):
621 w = self.worktree
622 g = os.path.join(w, '.git')
623 return os.path.exists(os.path.join(g, 'rebase-apply')) \
624 or os.path.exists(os.path.join(g, 'rebase-merge')) \
625 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200626
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700627 def IsDirty(self, consider_untracked=True):
628 """Is the working directory modified in some way?
629 """
630 self.work_git.update_index('-q',
631 '--unmerged',
632 '--ignore-missing',
633 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900634 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635 return True
636 if self.work_git.DiffZ('diff-files'):
637 return True
638 if consider_untracked and self.work_git.LsOthers():
639 return True
640 return False
641
642 _userident_name = None
643 _userident_email = None
644
645 @property
646 def UserName(self):
647 """Obtain the user's personal name.
648 """
649 if self._userident_name is None:
650 self._LoadUserIdentity()
651 return self._userident_name
652
653 @property
654 def UserEmail(self):
655 """Obtain the user's email address. This is very likely
656 to be their Gerrit login.
657 """
658 if self._userident_email is None:
659 self._LoadUserIdentity()
660 return self._userident_email
661
662 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900663 u = self.bare_git.var('GIT_COMMITTER_IDENT')
664 m = re.compile("^(.*) <([^>]*)> ").match(u)
665 if m:
666 self._userident_name = m.group(1)
667 self._userident_email = m.group(2)
668 else:
669 self._userident_name = ''
670 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700671
672 def GetRemote(self, name):
673 """Get the configuration for a single remote.
674 """
675 return self.config.GetRemote(name)
676
677 def GetBranch(self, name):
678 """Get the configuration for a single branch.
679 """
680 return self.config.GetBranch(name)
681
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700682 def GetBranches(self):
683 """Get all existing local branches.
684 """
685 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900686 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700687 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700688
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530689 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700690 if name.startswith(R_HEADS):
691 name = name[len(R_HEADS):]
692 b = self.GetBranch(name)
693 b.current = name == current
694 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900695 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700696 heads[name] = b
697
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530698 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700699 if name.startswith(R_PUB):
700 name = name[len(R_PUB):]
701 b = heads.get(name)
702 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900703 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700704
705 return heads
706
Colin Cross5acde752012-03-28 20:15:45 -0700707 def MatchesGroups(self, manifest_groups):
708 """Returns true if the manifest groups specified at init should cause
709 this project to be synced.
710 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700711 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700712
Conley Owens971de8e2012-04-16 10:36:08 -0700713 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700714 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700715 manifest_groups: "-group1,group2"
716 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500717
718 The special manifest group "default" will match any project that
719 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700720 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500721 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700722 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500723 if not 'notdefault' in expanded_project_groups:
724 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700725
Conley Owens971de8e2012-04-16 10:36:08 -0700726 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700727 for group in expanded_manifest_groups:
728 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700729 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700730 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700731 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700732
Conley Owens971de8e2012-04-16 10:36:08 -0700733 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700734
735## Status Display ##
736
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500737 def HasChanges(self):
738 """Returns true if there are uncommitted changes.
739 """
740 self.work_git.update_index('-q',
741 '--unmerged',
742 '--ignore-missing',
743 '--refresh')
744 if self.IsRebaseInProgress():
745 return True
746
747 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
748 return True
749
750 if self.work_git.DiffZ('diff-files'):
751 return True
752
753 if self.work_git.LsOthers():
754 return True
755
756 return False
757
Terence Haddock4655e812011-03-31 12:33:34 +0200758 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200760
761 Args:
762 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700763 """
764 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200765 if output_redir == None:
766 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700767 print(file=output_redir)
768 print('project %s/' % self.relpath, file=output_redir)
769 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770 return
771
772 self.work_git.update_index('-q',
773 '--unmerged',
774 '--ignore-missing',
775 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700776 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700777 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
778 df = self.work_git.DiffZ('diff-files')
779 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100780 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700781 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782
783 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200784 if not output_redir == None:
785 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700786 out.project('project %-40s', self.relpath + '/')
787
788 branch = self.CurrentBranch
789 if branch is None:
790 out.nobranch('(*** NO BRANCH ***)')
791 else:
792 out.branch('branch %s', branch)
793 out.nl()
794
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700795 if rb:
796 out.important('prior sync failed; rebase still in progress')
797 out.nl()
798
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700799 paths = list()
800 paths.extend(di.keys())
801 paths.extend(df.keys())
802 paths.extend(do)
803
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530804 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900805 try:
806 i = di[p]
807 except KeyError:
808 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900810 try:
811 f = df[p]
812 except KeyError:
813 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200814
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900815 if i:
816 i_status = i.status.upper()
817 else:
818 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700819
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900820 if f:
821 f_status = f.status.lower()
822 else:
823 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824
825 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800826 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700827 i.src_path, p, i.level)
828 else:
829 line = ' %s%s\t%s' % (i_status, f_status, p)
830
831 if i and not f:
832 out.added('%s', line)
833 elif (i and f) or (not i and f):
834 out.changed('%s', line)
835 elif not i and not f:
836 out.untracked('%s', line)
837 else:
838 out.write('%s', line)
839 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200840
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700841 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700842
pelyad67872d2012-03-28 14:49:58 +0300843 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844 """Prints the status of the repository to stdout.
845 """
846 out = DiffColoring(self.config)
847 cmd = ['diff']
848 if out.is_on:
849 cmd.append('--color')
850 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300851 if absolute_paths:
852 cmd.append('--src-prefix=a/%s/' % self.relpath)
853 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854 cmd.append('--')
855 p = GitCommand(self,
856 cmd,
857 capture_stdout = True,
858 capture_stderr = True)
859 has_diff = False
860 for line in p.process.stdout:
861 if not has_diff:
862 out.nl()
863 out.project('project %s/' % self.relpath)
864 out.nl()
865 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700866 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867 p.Wait()
868
869
870## Publish / Upload ##
871
David Pursehouse8a68ff92012-09-24 12:15:13 +0900872 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700873 """Was the branch published (uploaded) for code review?
874 If so, returns the SHA-1 hash of the last published
875 state for the branch.
876 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700877 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900878 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700879 try:
880 return self.bare_git.rev_parse(key)
881 except GitError:
882 return None
883 else:
884 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900885 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700886 except KeyError:
887 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888
David Pursehouse8a68ff92012-09-24 12:15:13 +0900889 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890 """Prunes any stale published refs.
891 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900892 if all_refs is None:
893 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894 heads = set()
895 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530896 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700897 if name.startswith(R_HEADS):
898 heads.add(name)
899 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900900 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530902 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700903 n = name[len(R_PUB):]
904 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900905 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700907 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700908 """List any branches which can be uploaded for review.
909 """
910 heads = {}
911 pubed = {}
912
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530913 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900915 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900917 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918
919 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530920 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900921 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700923 if selected_branch and branch != selected_branch:
924 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800926 rb = self.GetUploadableBranch(branch)
927 if rb:
928 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929 return ready
930
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800931 def GetUploadableBranch(self, branch_name):
932 """Get a single uploadable branch, or None.
933 """
934 branch = self.GetBranch(branch_name)
935 base = branch.LocalMerge
936 if branch.LocalMerge:
937 rb = ReviewableBranch(self, branch, base)
938 if rb.commits:
939 return rb
940 return None
941
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700942 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700943 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700944 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400945 draft=False,
946 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947 """Uploads the named branch for code review.
948 """
949 if branch is None:
950 branch = self.CurrentBranch
951 if branch is None:
952 raise GitError('not currently on a branch')
953
954 branch = self.GetBranch(branch)
955 if not branch.LocalMerge:
956 raise GitError('branch %s does not track a remote' % branch.name)
957 if not branch.remote.review:
958 raise GitError('remote %s has no review url' % branch.remote.name)
959
Bryan Jacobsf609f912013-05-06 13:36:24 -0400960 if dest_branch is None:
961 dest_branch = self.dest_branch
962 if dest_branch is None:
963 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964 if not dest_branch.startswith(R_HEADS):
965 dest_branch = R_HEADS + dest_branch
966
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800967 if not branch.remote.projectname:
968 branch.remote.projectname = self.name
969 branch.remote.Save()
970
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800971 url = branch.remote.ReviewUrl(self.UserEmail)
972 if url is None:
973 raise UploadError('review not configured')
974 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800975
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800976 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800977 rp = ['gerrit receive-pack']
978 for e in people[0]:
979 rp.append('--reviewer=%s' % sq(e))
980 for e in people[1]:
981 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800982 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700983
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800984 cmd.append(url)
985
986 if dest_branch.startswith(R_HEADS):
987 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700988
989 upload_type = 'for'
990 if draft:
991 upload_type = 'drafts'
992
993 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
994 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800995 if auto_topic:
996 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800997 if not url.startswith('ssh://'):
998 rp = ['r=%s' % p for p in people[0]] + \
999 ['cc=%s' % p for p in people[1]]
1000 if rp:
1001 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001002 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001003
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001004 if GitCommand(self, cmd, bare = True).Wait() != 0:
1005 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001006
1007 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1008 self.bare_git.UpdateRef(R_PUB + branch.name,
1009 R_HEADS + branch.name,
1010 message = msg)
1011
1012
1013## Sync ##
1014
Julien Campergue335f5ef2013-10-16 11:02:35 +02001015 def _ExtractArchive(self, tarpath, path=None):
1016 """Extract the given tar on its current location
1017
1018 Args:
1019 - tarpath: The path to the actual tar file
1020
1021 """
1022 try:
1023 with tarfile.open(tarpath, 'r') as tar:
1024 tar.extractall(path=path)
1025 return True
1026 except (IOError, tarfile.TarError) as e:
1027 print("error: Cannot extract archive %s: "
1028 "%s" % (tarpath, str(e)), file=sys.stderr)
1029 return False
1030
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001031 def Sync_NetworkHalf(self,
1032 quiet=False,
1033 is_new=None,
1034 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001035 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001036 no_tags=False,
1037 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038 """Perform only the network IO portion of the sync process.
1039 Local working directory/branch state is not affected.
1040 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001041 if archive and not isinstance(self, MetaProject):
1042 if self.remote.url.startswith(('http://', 'https://')):
1043 print("error: %s: Cannot fetch archives from http/https "
1044 "remotes." % self.name, file=sys.stderr)
1045 return False
1046
1047 name = self.relpath.replace('\\', '/')
1048 name = name.replace('/', '_')
1049 tarpath = '%s.tar' % name
1050 topdir = self.manifest.topdir
1051
1052 try:
1053 self._FetchArchive(tarpath, cwd=topdir)
1054 except GitError as e:
1055 print('error: %s' % str(e), file=sys.stderr)
1056 return False
1057
1058 # From now on, we only need absolute tarpath
1059 tarpath = os.path.join(topdir, tarpath)
1060
1061 if not self._ExtractArchive(tarpath, path=topdir):
1062 return False
1063 try:
1064 os.remove(tarpath)
1065 except OSError as e:
1066 print("warn: Cannot remove archive %s: "
1067 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001068 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001069 return True
1070
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001071 if is_new is None:
1072 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001073 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001075 else:
1076 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001078
1079 if is_new:
1080 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1081 try:
1082 fd = open(alt, 'rb')
1083 try:
1084 alt_dir = fd.readline().rstrip()
1085 finally:
1086 fd.close()
1087 except IOError:
1088 alt_dir = None
1089 else:
1090 alt_dir = None
1091
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001092 if clone_bundle \
1093 and alt_dir is None \
1094 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001095 is_new = False
1096
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001097 if not current_branch_only:
1098 if self.sync_c:
1099 current_branch_only = True
1100 elif not self.manifest._loaded:
1101 # Manifest cannot check defaults until it syncs.
1102 current_branch_only = False
1103 elif self.manifest.default.sync_c:
1104 current_branch_only = True
1105
Conley Owens666d5342014-05-01 13:09:57 -07001106 has_sha1 = ID_RE.match(self.revisionExpr) and self._CheckForSha1()
1107 if (not has_sha1 #Need to fetch since we don't already have this revision
1108 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1109 current_branch_only=current_branch_only,
1110 no_tags=no_tags)):
1111 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001112
1113 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001114 self._InitMRef()
1115 else:
1116 self._InitMirrorHead()
1117 try:
1118 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1119 except OSError:
1120 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001122
1123 def PostRepoUpgrade(self):
1124 self._InitHooks()
1125
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001126 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001127 for copyfile in self.copyfiles:
1128 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001129 for linkfile in self.linkfiles:
1130 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001131
Julien Camperguedd654222014-01-09 16:21:37 +01001132 def GetCommitRevisionId(self):
1133 """Get revisionId of a commit.
1134
1135 Use this method instead of GetRevisionId to get the id of the commit rather
1136 than the id of the current git object (for example, a tag)
1137
1138 """
1139 if not self.revisionExpr.startswith(R_TAGS):
1140 return self.GetRevisionId(self._allrefs)
1141
1142 try:
1143 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1144 except GitError:
1145 raise ManifestInvalidRevisionError(
1146 'revision %s in %s not found' % (self.revisionExpr,
1147 self.name))
1148
David Pursehouse8a68ff92012-09-24 12:15:13 +09001149 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001150 if self.revisionId:
1151 return self.revisionId
1152
1153 rem = self.GetRemote(self.remote.name)
1154 rev = rem.ToLocal(self.revisionExpr)
1155
David Pursehouse8a68ff92012-09-24 12:15:13 +09001156 if all_refs is not None and rev in all_refs:
1157 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001158
1159 try:
1160 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1161 except GitError:
1162 raise ManifestInvalidRevisionError(
1163 'revision %s in %s not found' % (self.revisionExpr,
1164 self.name))
1165
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001166 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167 """Perform only the local IO portion of the sync process.
1168 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169 """
David James8d201162013-10-11 17:03:19 -07001170 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001171 all_refs = self.bare_ref.all
1172 self.CleanPublishedCache(all_refs)
1173 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001174
David Pursehouse1d947b32012-10-25 12:23:11 +09001175 def _doff():
1176 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001177 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001178
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001179 head = self.work_git.GetHead()
1180 if head.startswith(R_HEADS):
1181 branch = head[len(R_HEADS):]
1182 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001183 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001184 except KeyError:
1185 head = None
1186 else:
1187 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001188
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001189 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190 # Currently on a detached HEAD. The user is assumed to
1191 # not have any local modifications worth worrying about.
1192 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001193 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001194 syncbuf.fail(self, _PriorSyncFailedError())
1195 return
1196
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001197 if head == revid:
1198 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001199 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001200 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001201 if not syncbuf.detach_head:
1202 return
1203 else:
1204 lost = self._revlist(not_rev(revid), HEAD)
1205 if lost:
1206 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001207
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001208 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001209 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001210 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001211 syncbuf.fail(self, e)
1212 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001213 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001214 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001215
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001216 if head == revid:
1217 # No changes; don't do anything further.
1218 #
1219 return
1220
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001221 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001222
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001223 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001225 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001226 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001227 syncbuf.info(self,
1228 "leaving %s; does not track upstream",
1229 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001230 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001231 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001232 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001233 syncbuf.fail(self, e)
1234 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001235 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001236 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001237
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001238 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001239 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001240 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001241 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242 if not_merged:
1243 if upstream_gain:
1244 # The user has published this branch and some of those
1245 # commits are not yet merged upstream. We do not want
1246 # to rewrite the published commits so we punt.
1247 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001248 syncbuf.fail(self,
1249 "branch %s is published (but not merged) and is now %d commits behind"
1250 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001251 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001252 elif pub == head:
1253 # All published commits are merged, and thus we are a
1254 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001255 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001256 syncbuf.later1(self, _doff)
1257 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001259 # Examine the local commits not in the remote. Find the
1260 # last one attributed to this user, if any.
1261 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001262 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001263 last_mine = None
1264 cnt_mine = 0
1265 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301266 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001267 if committer_email == self.UserEmail:
1268 last_mine = commit_id
1269 cnt_mine += 1
1270
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001271 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001272 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001273
1274 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001275 syncbuf.fail(self, _DirtyError())
1276 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001277
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001278 # If the upstream switched on us, warn the user.
1279 #
1280 if branch.merge != self.revisionExpr:
1281 if branch.merge and self.revisionExpr:
1282 syncbuf.info(self,
1283 'manifest switched %s...%s',
1284 branch.merge,
1285 self.revisionExpr)
1286 elif branch.merge:
1287 syncbuf.info(self,
1288 'manifest no longer tracks %s',
1289 branch.merge)
1290
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001291 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001293 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001295 syncbuf.info(self,
1296 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001297 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001299 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001300 if not ID_RE.match(self.revisionExpr):
1301 # in case of manifest sync the revisionExpr might be a SHA1
1302 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303 branch.Save()
1304
Mike Pontillod3153822012-02-28 11:53:24 -08001305 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001306 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001307 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001308 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001309 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001310 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001312 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001313 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001314 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001315 syncbuf.fail(self, e)
1316 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001318 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001320 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 # dest should already be an absolute path, but src is project relative
1322 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001323 abssrc = os.path.join(self.worktree, src)
1324 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001325
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001326 def AddLinkFile(self, src, dest, absdest):
1327 # dest should already be an absolute path, but src is project relative
1328 # make src an absolute path
1329 abssrc = os.path.join(self.worktree, src)
1330 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1331
James W. Mills24c13082012-04-12 15:04:13 -05001332 def AddAnnotation(self, name, value, keep):
1333 self.annotations.append(_Annotation(name, value, keep))
1334
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001335 def DownloadPatchSet(self, change_id, patch_id):
1336 """Download a single patch set of a single change to FETCH_HEAD.
1337 """
1338 remote = self.GetRemote(self.remote.name)
1339
1340 cmd = ['fetch', remote.name]
1341 cmd.append('refs/changes/%2.2d/%d/%d' \
1342 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001343 if GitCommand(self, cmd, bare=True).Wait() != 0:
1344 return None
1345 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001346 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001347 change_id,
1348 patch_id,
1349 self.bare_git.rev_parse('FETCH_HEAD'))
1350
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001351
1352## Branch Management ##
1353
1354 def StartBranch(self, name):
1355 """Create a new branch off the manifest's revision.
1356 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001357 head = self.work_git.GetHead()
1358 if head == (R_HEADS + name):
1359 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001360
David Pursehouse8a68ff92012-09-24 12:15:13 +09001361 all_refs = self.bare_ref.all
1362 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001363 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001364 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001365 capture_stdout = True,
1366 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001367
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001368 branch = self.GetBranch(name)
1369 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001370 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001371 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001372
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001373 if head.startswith(R_HEADS):
1374 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001375 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001376 except KeyError:
1377 head = None
1378
1379 if revid and head and revid == head:
1380 ref = os.path.join(self.gitdir, R_HEADS + name)
1381 try:
1382 os.makedirs(os.path.dirname(ref))
1383 except OSError:
1384 pass
1385 _lwrite(ref, '%s\n' % revid)
1386 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1387 'ref: %s%s\n' % (R_HEADS, name))
1388 branch.Save()
1389 return True
1390
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001391 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001392 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001393 capture_stdout = True,
1394 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001395 branch.Save()
1396 return True
1397 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398
Wink Saville02d79452009-04-10 13:01:24 -07001399 def CheckoutBranch(self, name):
1400 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001401
1402 Args:
1403 name: The name of the branch to checkout.
1404
1405 Returns:
1406 True if the checkout succeeded; False if it didn't; None if the branch
1407 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001408 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001409 rev = R_HEADS + name
1410 head = self.work_git.GetHead()
1411 if head == rev:
1412 # Already on the branch
1413 #
1414 return True
Wink Saville02d79452009-04-10 13:01:24 -07001415
David Pursehouse8a68ff92012-09-24 12:15:13 +09001416 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001417 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001418 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001419 except KeyError:
1420 # Branch does not exist in this project
1421 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001422 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001423
1424 if head.startswith(R_HEADS):
1425 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001426 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001427 except KeyError:
1428 head = None
1429
1430 if head == revid:
1431 # Same revision; just update HEAD to point to the new
1432 # target branch, but otherwise take no other action.
1433 #
1434 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1435 'ref: %s%s\n' % (R_HEADS, name))
1436 return True
Wink Saville02d79452009-04-10 13:01:24 -07001437
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001438 return GitCommand(self,
1439 ['checkout', name, '--'],
1440 capture_stdout = True,
1441 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001442
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001443 def AbandonBranch(self, name):
1444 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001445
1446 Args:
1447 name: The name of the branch to abandon.
1448
1449 Returns:
1450 True if the abandon succeeded; False if it didn't; None if the branch
1451 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001452 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001453 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001454 all_refs = self.bare_ref.all
1455 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001456 # Doesn't exist
1457 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001458
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001459 head = self.work_git.GetHead()
1460 if head == rev:
1461 # We can't destroy the branch while we are sitting
1462 # on it. Switch to a detached HEAD.
1463 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001464 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001465
David Pursehouse8a68ff92012-09-24 12:15:13 +09001466 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001467 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001468 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1469 '%s\n' % revid)
1470 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001471 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001472
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001473 return GitCommand(self,
1474 ['branch', '-D', name],
1475 capture_stdout = True,
1476 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001477
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001478 def PruneHeads(self):
1479 """Prune any topic branches already merged into upstream.
1480 """
1481 cb = self.CurrentBranch
1482 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001483 left = self._allrefs
1484 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001485 if name.startswith(R_HEADS):
1486 name = name[len(R_HEADS):]
1487 if cb is None or name != cb:
1488 kill.append(name)
1489
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001490 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 if cb is not None \
1492 and not self._revlist(HEAD + '...' + rev) \
1493 and not self.IsDirty(consider_untracked = False):
1494 self.work_git.DetachHead(HEAD)
1495 kill.append(cb)
1496
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001497 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001498 old = self.bare_git.GetHead()
1499 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001500 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1501
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001502 try:
1503 self.bare_git.DetachHead(rev)
1504
1505 b = ['branch', '-d']
1506 b.extend(kill)
1507 b = GitCommand(self, b, bare=True,
1508 capture_stdout=True,
1509 capture_stderr=True)
1510 b.Wait()
1511 finally:
1512 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001513 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001514
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001515 for branch in kill:
1516 if (R_HEADS + branch) not in left:
1517 self.CleanPublishedCache()
1518 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001519
1520 if cb and cb not in kill:
1521 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001522 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001523
1524 kept = []
1525 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001526 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001527 branch = self.GetBranch(branch)
1528 base = branch.LocalMerge
1529 if not base:
1530 base = rev
1531 kept.append(ReviewableBranch(self, branch, base))
1532 return kept
1533
1534
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001535## Submodule Management ##
1536
1537 def GetRegisteredSubprojects(self):
1538 result = []
1539 def rec(subprojects):
1540 if not subprojects:
1541 return
1542 result.extend(subprojects)
1543 for p in subprojects:
1544 rec(p.subprojects)
1545 rec(self.subprojects)
1546 return result
1547
1548 def _GetSubmodules(self):
1549 # Unfortunately we cannot call `git submodule status --recursive` here
1550 # because the working tree might not exist yet, and it cannot be used
1551 # without a working tree in its current implementation.
1552
1553 def get_submodules(gitdir, rev):
1554 # Parse .gitmodules for submodule sub_paths and sub_urls
1555 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1556 if not sub_paths:
1557 return []
1558 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1559 # revision of submodule repository
1560 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1561 submodules = []
1562 for sub_path, sub_url in zip(sub_paths, sub_urls):
1563 try:
1564 sub_rev = sub_revs[sub_path]
1565 except KeyError:
1566 # Ignore non-exist submodules
1567 continue
1568 submodules.append((sub_rev, sub_path, sub_url))
1569 return submodules
1570
1571 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1572 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1573 def parse_gitmodules(gitdir, rev):
1574 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1575 try:
1576 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1577 bare = True, gitdir = gitdir)
1578 except GitError:
1579 return [], []
1580 if p.Wait() != 0:
1581 return [], []
1582
1583 gitmodules_lines = []
1584 fd, temp_gitmodules_path = tempfile.mkstemp()
1585 try:
1586 os.write(fd, p.stdout)
1587 os.close(fd)
1588 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1589 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1590 bare = True, gitdir = gitdir)
1591 if p.Wait() != 0:
1592 return [], []
1593 gitmodules_lines = p.stdout.split('\n')
1594 except GitError:
1595 return [], []
1596 finally:
1597 os.remove(temp_gitmodules_path)
1598
1599 names = set()
1600 paths = {}
1601 urls = {}
1602 for line in gitmodules_lines:
1603 if not line:
1604 continue
1605 m = re_path.match(line)
1606 if m:
1607 names.add(m.group(1))
1608 paths[m.group(1)] = m.group(2)
1609 continue
1610 m = re_url.match(line)
1611 if m:
1612 names.add(m.group(1))
1613 urls[m.group(1)] = m.group(2)
1614 continue
1615 names = sorted(names)
1616 return ([paths.get(name, '') for name in names],
1617 [urls.get(name, '') for name in names])
1618
1619 def git_ls_tree(gitdir, rev, paths):
1620 cmd = ['ls-tree', rev, '--']
1621 cmd.extend(paths)
1622 try:
1623 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1624 bare = True, gitdir = gitdir)
1625 except GitError:
1626 return []
1627 if p.Wait() != 0:
1628 return []
1629 objects = {}
1630 for line in p.stdout.split('\n'):
1631 if not line.strip():
1632 continue
1633 object_rev, object_path = line.split()[2:4]
1634 objects[object_path] = object_rev
1635 return objects
1636
1637 try:
1638 rev = self.GetRevisionId()
1639 except GitError:
1640 return []
1641 return get_submodules(self.gitdir, rev)
1642
1643 def GetDerivedSubprojects(self):
1644 result = []
1645 if not self.Exists:
1646 # If git repo does not exist yet, querying its submodules will
1647 # mess up its states; so return here.
1648 return result
1649 for rev, path, url in self._GetSubmodules():
1650 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001651 relpath, worktree, gitdir, objdir = \
1652 self.manifest.GetSubprojectPaths(self, name, path)
1653 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001654 if project:
1655 result.extend(project.GetDerivedSubprojects())
1656 continue
David James8d201162013-10-11 17:03:19 -07001657
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001658 remote = RemoteSpec(self.remote.name,
1659 url = url,
1660 review = self.remote.review)
1661 subproject = Project(manifest = self.manifest,
1662 name = name,
1663 remote = remote,
1664 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001665 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001666 worktree = worktree,
1667 relpath = relpath,
1668 revisionExpr = self.revisionExpr,
1669 revisionId = rev,
1670 rebase = self.rebase,
1671 groups = self.groups,
1672 sync_c = self.sync_c,
1673 sync_s = self.sync_s,
1674 parent = self,
1675 is_derived = True)
1676 result.append(subproject)
1677 result.extend(subproject.GetDerivedSubprojects())
1678 return result
1679
1680
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001681## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001682 def _CheckForSha1(self):
1683 try:
1684 # if revision (sha or tag) is not present then following function
1685 # throws an error.
1686 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1687 return True
1688 except GitError:
1689 # There is no such persistent revision. We have to fetch it.
1690 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691
Julien Campergue335f5ef2013-10-16 11:02:35 +02001692 def _FetchArchive(self, tarpath, cwd=None):
1693 cmd = ['archive', '-v', '-o', tarpath]
1694 cmd.append('--remote=%s' % self.remote.url)
1695 cmd.append('--prefix=%s/' % self.relpath)
1696 cmd.append(self.revisionExpr)
1697
1698 command = GitCommand(self, cmd, cwd=cwd,
1699 capture_stdout=True,
1700 capture_stderr=True)
1701
1702 if command.Wait() != 0:
1703 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1704
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001705 def _RemoteFetch(self, name=None,
1706 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001707 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001708 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001709 alt_dir=None,
1710 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001711
1712 is_sha1 = False
1713 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001714 depth = None
1715
1716 # The depth should not be used when fetching to a mirror because
1717 # it will result in a shallow repository that cannot be cloned or
1718 # fetched from.
1719 if not self.manifest.IsMirror:
1720 if self.clone_depth:
1721 depth = self.clone_depth
1722 else:
1723 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001724
Shawn Pearce69e04d82014-01-29 12:48:54 -08001725 if depth:
1726 current_branch_only = True
1727
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001728 if current_branch_only:
1729 if ID_RE.match(self.revisionExpr) is not None:
1730 is_sha1 = True
1731 elif self.revisionExpr.startswith(R_TAGS):
1732 # this is a tag and its sha1 value should never change
1733 tag_name = self.revisionExpr[len(R_TAGS):]
1734
1735 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001736 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001737 return True
Brian Harring14a66742012-09-28 20:21:57 -07001738 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1739 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001740
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001741 if not name:
1742 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001743
1744 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001745 remote = self.GetRemote(name)
1746 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001747 ssh_proxy = True
1748
Shawn O. Pearce88443382010-10-08 10:02:09 +02001749 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001750 if alt_dir and 'objects' == os.path.basename(alt_dir):
1751 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001752 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1753 remote = self.GetRemote(name)
1754
David Pursehouse8a68ff92012-09-24 12:15:13 +09001755 all_refs = self.bare_ref.all
1756 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001757 tmp = set()
1758
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301759 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001760 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001761 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001762 all_refs[r] = ref_id
1763 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001764 continue
1765
David Pursehouse8a68ff92012-09-24 12:15:13 +09001766 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001767 continue
1768
David Pursehouse8a68ff92012-09-24 12:15:13 +09001769 r = 'refs/_alt/%s' % ref_id
1770 all_refs[r] = ref_id
1771 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001772 tmp.add(r)
1773
Shawn O. Pearce88443382010-10-08 10:02:09 +02001774 tmp_packed = ''
1775 old_packed = ''
1776
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301777 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001778 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001779 tmp_packed += line
1780 if r not in tmp:
1781 old_packed += line
1782
1783 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001784 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001785 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001786
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001787 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001788
1789 # The --depth option only affects the initial fetch; after that we'll do
1790 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001791 if depth and initial:
1792 cmd.append('--depth=%s' % depth)
1793
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001794 if quiet:
1795 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001796 if not self.worktree:
1797 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001798 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001799
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001800 # If using depth then we should not get all the tags since they may
1801 # be outside of the depth.
1802 if no_tags or depth:
1803 cmd.append('--no-tags')
1804 else:
1805 cmd.append('--tags')
1806
Brian Harring14a66742012-09-28 20:21:57 -07001807 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001808 # Fetch whole repo
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301809 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
natalie.chen59964f02014-12-19 20:06:47 +08001810 if self.upstream and self.upstream.startswith('refs/builds'):
1811 cmd.append((u'+%s:' % self.upstream) + remote.ToLocal(self.upstream))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001812 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001813 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001814 cmd.append(tag_name)
1815 else:
1816 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001817 if is_sha1:
1818 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001819 if branch.startswith(R_HEADS):
1820 branch = branch[len(R_HEADS):]
natalie.chen59964f02014-12-19 20:06:47 +08001821 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
1822 else:
1823 cmd.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001824
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001825 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001826 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001827 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1828 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001829 ok = True
1830 break
Brian Harring14a66742012-09-28 20:21:57 -07001831 elif current_branch_only and is_sha1 and ret == 128:
1832 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1833 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1834 # abort the optimization attempt and do a full sync.
1835 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001836 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001837
1838 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001839 # Ensure that some refs exist. Otherwise, we probably aren't looking
1840 # at a real git repository and may have a bad url.
1841 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001842 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001843
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001844 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001845 if old_packed != '':
1846 _lwrite(packed_refs, old_packed)
1847 else:
1848 os.remove(packed_refs)
1849 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001850
1851 if is_sha1 and current_branch_only and self.upstream:
1852 # We just synced the upstream given branch; verify we
1853 # got what we wanted, else trigger a second run of all
1854 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001855 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001856 return self._RemoteFetch(name=name, current_branch_only=False,
1857 initial=False, quiet=quiet, alt_dir=alt_dir)
1858
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001859 return ok
1860
1861 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001862 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001863 return False
1864
1865 remote = self.GetRemote(self.remote.name)
1866 bundle_url = remote.url + '/clone.bundle'
1867 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001868 if GetSchemeFromUrl(bundle_url) not in (
1869 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001870 return False
1871
1872 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1873 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001874
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001875 exist_dst = os.path.exists(bundle_dst)
1876 exist_tmp = os.path.exists(bundle_tmp)
1877
1878 if not initial and not exist_dst and not exist_tmp:
1879 return False
1880
1881 if not exist_dst:
1882 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1883 if not exist_dst:
1884 return False
1885
1886 cmd = ['fetch']
1887 if quiet:
1888 cmd.append('--quiet')
1889 if not self.worktree:
1890 cmd.append('--update-head-ok')
1891 cmd.append(bundle_dst)
1892 for f in remote.fetch:
1893 cmd.append(str(f))
1894 cmd.append('refs/tags/*:refs/tags/*')
1895
1896 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001897 if os.path.exists(bundle_dst):
1898 os.remove(bundle_dst)
1899 if os.path.exists(bundle_tmp):
1900 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001901 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001902
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001903 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001904 if os.path.exists(dstPath):
1905 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001906
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001907 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001908 if quiet:
1909 cmd += ['--silent']
1910 if os.path.exists(tmpPath):
1911 size = os.stat(tmpPath).st_size
1912 if size >= 1024:
1913 cmd += ['--continue-at', '%d' % (size,)]
1914 else:
1915 os.remove(tmpPath)
1916 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1917 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001918 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001919 if cookiefile:
1920 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001921 if srcUrl.startswith('persistent-'):
1922 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001923 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001924
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001925 if IsTrace():
1926 Trace('%s', ' '.join(cmd))
1927 try:
1928 proc = subprocess.Popen(cmd)
1929 except OSError:
1930 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001931
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001932 curlret = proc.wait()
1933
1934 if curlret == 22:
1935 # From curl man page:
1936 # 22: HTTP page not retrieved. The requested url was not found or
1937 # returned another error with the HTTP error code being 400 or above.
1938 # This return code only appears if -f, --fail is used.
1939 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001940 print("Server does not provide clone.bundle; ignoring.",
1941 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001942 return False
1943
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001944 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001945 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001946 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001947 return True
1948 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001949 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001950 return False
1951 else:
1952 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001953
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001954 def _IsValidBundle(self, path):
1955 try:
1956 with open(path) as f:
1957 if f.read(16) == '# v2 git bundle\n':
1958 return True
1959 else:
1960 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1961 return False
1962 except OSError:
1963 return False
1964
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001965 def _GetBundleCookieFile(self, url):
1966 if url.startswith('persistent-'):
1967 try:
1968 p = subprocess.Popen(
1969 ['git-remote-persistent-https', '-print_config', url],
1970 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1971 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001972 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001973 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001974 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001975 for line in p.stdout:
1976 line = line.strip()
1977 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001978 cookiefile = line[len(prefix):]
1979 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001980 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001981 err_msg = p.stderr.read()
1982 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001983 pass # Persistent proxy doesn't support -print_config.
1984 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001985 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001986 if cookiefile:
1987 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001988 except OSError as e:
1989 if e.errno == errno.ENOENT:
1990 pass # No persistent proxy.
1991 raise
1992 return GitConfig.ForUser().GetString('http.cookiefile')
1993
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001994 def _Checkout(self, rev, quiet=False):
1995 cmd = ['checkout']
1996 if quiet:
1997 cmd.append('-q')
1998 cmd.append(rev)
1999 cmd.append('--')
2000 if GitCommand(self, cmd).Wait() != 0:
2001 if self._allrefs:
2002 raise GitError('%s checkout %s ' % (self.name, rev))
2003
Pierre Tardye5a21222011-03-24 16:28:18 +01002004 def _CherryPick(self, rev, quiet=False):
2005 cmd = ['cherry-pick']
2006 cmd.append(rev)
2007 cmd.append('--')
2008 if GitCommand(self, cmd).Wait() != 0:
2009 if self._allrefs:
2010 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2011
Erwan Mahea94f1622011-08-19 13:56:09 +02002012 def _Revert(self, rev, quiet=False):
2013 cmd = ['revert']
2014 cmd.append('--no-edit')
2015 cmd.append(rev)
2016 cmd.append('--')
2017 if GitCommand(self, cmd).Wait() != 0:
2018 if self._allrefs:
2019 raise GitError('%s revert %s ' % (self.name, rev))
2020
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002021 def _ResetHard(self, rev, quiet=True):
2022 cmd = ['reset', '--hard']
2023 if quiet:
2024 cmd.append('-q')
2025 cmd.append(rev)
2026 if GitCommand(self, cmd).Wait() != 0:
2027 raise GitError('%s reset --hard %s ' % (self.name, rev))
2028
2029 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002030 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002031 if onto is not None:
2032 cmd.extend(['--onto', onto])
2033 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002034 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002035 raise GitError('%s rebase %s ' % (self.name, upstream))
2036
Pierre Tardy3d125942012-05-04 12:18:12 +02002037 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002038 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002039 if ffonly:
2040 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002041 if GitCommand(self, cmd).Wait() != 0:
2042 raise GitError('%s merge %s ' % (self.name, head))
2043
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002044 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002045 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002046
2047 # Initialize the bare repository, which contains all of the objects.
2048 if not os.path.exists(self.objdir):
2049 os.makedirs(self.objdir)
2050 self.bare_objdir.init()
2051
2052 # If we have a separate directory to hold refs, initialize it as well.
2053 if self.objdir != self.gitdir:
2054 os.makedirs(self.gitdir)
2055 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2056 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002057
Shawn O. Pearce88443382010-10-08 10:02:09 +02002058 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002059 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002060
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002061 if ref_dir or mirror_git:
2062 if not mirror_git:
2063 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002064 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2065 self.relpath + '.git')
2066
2067 if os.path.exists(mirror_git):
2068 ref_dir = mirror_git
2069
2070 elif os.path.exists(repo_git):
2071 ref_dir = repo_git
2072
2073 else:
2074 ref_dir = None
2075
2076 if ref_dir:
2077 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2078 os.path.join(ref_dir, 'objects') + '\n')
2079
Jimmie Westera0444582012-10-24 13:44:42 +02002080 self._UpdateHooks()
2081
2082 m = self.manifest.manifestProject.config
2083 for key in ['user.name', 'user.email']:
2084 if m.Has(key, include_defaults = False):
2085 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002086 if self.manifest.IsMirror:
2087 self.config.SetString('core.bare', 'true')
2088 else:
2089 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002090
Jimmie Westera0444582012-10-24 13:44:42 +02002091 def _UpdateHooks(self):
2092 if os.path.exists(self.gitdir):
2093 # Always recreate hooks since they can have been changed
2094 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002095 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002096 try:
2097 to_rm = os.listdir(hooks)
2098 except OSError:
2099 to_rm = []
2100 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002101 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002102 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002103
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002104 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002105 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002106 if not os.path.exists(hooks):
2107 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002108 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002109 name = os.path.basename(stock_hook)
2110
Victor Boivie65e0f352011-04-18 11:23:29 +02002111 if name in ('commit-msg',) and not self.remote.review \
2112 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002113 # Don't install a Gerrit Code Review hook if this
2114 # project does not appear to use it for reviews.
2115 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002116 # Since the manifest project is one of those, but also
2117 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002118 continue
2119
2120 dst = os.path.join(hooks, name)
2121 if os.path.islink(dst):
2122 continue
2123 if os.path.exists(dst):
2124 if filecmp.cmp(stock_hook, dst, shallow=False):
2125 os.remove(dst)
2126 else:
2127 _error("%s: Not replacing %s hook", self.relpath, name)
2128 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002129 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002130 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002131 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002132 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002133 raise GitError('filesystem must support symlinks')
2134 else:
2135 raise
2136
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002137 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002138 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002139 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002140 remote.url = self.remote.url
2141 remote.review = self.remote.review
2142 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002143
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002144 if self.worktree:
2145 remote.ResetFetch(mirror=False)
2146 else:
2147 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002148 remote.Save()
2149
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002150 def _InitMRef(self):
2151 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002152 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002153
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002154 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002155 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002156
2157 def _InitAnyMRef(self, ref):
2158 cur = self.bare_ref.symref(ref)
2159
2160 if self.revisionId:
2161 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2162 msg = 'manifest set to %s' % self.revisionId
2163 dst = self.revisionId + '^0'
2164 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2165 else:
2166 remote = self.GetRemote(self.remote.name)
2167 dst = remote.ToLocal(self.revisionExpr)
2168 if cur != dst:
2169 msg = 'manifest set to %s' % self.revisionExpr
2170 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002171
David James8d201162013-10-11 17:03:19 -07002172 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2173 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2174
2175 Args:
2176 gitdir: The bare git repository. Must already be initialized.
2177 dotgit: The repository you would like to initialize.
2178 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2179 Only one work tree can store refs under a given |gitdir|.
2180 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2181 This saves you the effort of initializing |dotgit| yourself.
2182 """
2183 # These objects can be shared between several working trees.
2184 symlink_files = ['description', 'info']
2185 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2186 if share_refs:
2187 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002188 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002189 symlink_dirs += ['logs', 'refs']
2190 to_symlink = symlink_files + symlink_dirs
2191
2192 to_copy = []
2193 if copy_all:
2194 to_copy = os.listdir(gitdir)
2195
2196 for name in set(to_copy).union(to_symlink):
2197 try:
2198 src = os.path.realpath(os.path.join(gitdir, name))
2199 dst = os.path.realpath(os.path.join(dotgit, name))
2200
2201 if os.path.lexists(dst) and not os.path.islink(dst):
2202 raise GitError('cannot overwrite a local work tree')
2203
2204 # If the source dir doesn't exist, create an empty dir.
2205 if name in symlink_dirs and not os.path.lexists(src):
2206 os.makedirs(src)
2207
2208 if name in to_symlink:
2209 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2210 elif copy_all and not os.path.islink(dst):
2211 if os.path.isdir(src):
2212 shutil.copytree(src, dst)
2213 elif os.path.isfile(src):
2214 shutil.copy(src, dst)
2215 except OSError as e:
2216 if e.errno == errno.EPERM:
2217 raise GitError('filesystem must support symlinks')
2218 else:
2219 raise
2220
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002221 def _InitWorkTree(self):
2222 dotgit = os.path.join(self.worktree, '.git')
2223 if not os.path.exists(dotgit):
2224 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002225 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2226 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002227
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002228 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002229
2230 cmd = ['read-tree', '--reset', '-u']
2231 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002232 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002233 if GitCommand(self, cmd).Wait() != 0:
2234 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002235
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002236 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002237
2238 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002239 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002240
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002241 def _revlist(self, *args, **kw):
2242 a = []
2243 a.extend(args)
2244 a.append('--')
2245 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002246
2247 @property
2248 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002249 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002250
Julien Camperguedd654222014-01-09 16:21:37 +01002251 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2252 """Get logs between two revisions of this project."""
2253 comp = '..'
2254 if rev1:
2255 revs = [rev1]
2256 if rev2:
2257 revs.extend([comp, rev2])
2258 cmd = ['log', ''.join(revs)]
2259 out = DiffColoring(self.config)
2260 if out.is_on and color:
2261 cmd.append('--color')
2262 if oneline:
2263 cmd.append('--oneline')
2264
2265 try:
2266 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2267 if log.Wait() == 0:
2268 return log.stdout
2269 except GitError:
2270 # worktree may not exist if groups changed for example. In that case,
2271 # try in gitdir instead.
2272 if not os.path.exists(self.worktree):
2273 return self.bare_git.log(*cmd[1:])
2274 else:
2275 raise
2276 return None
2277
2278 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2279 """Get the list of logs from this revision to given revisionId"""
2280 logs = {}
2281 selfId = self.GetRevisionId(self._allrefs)
2282 toId = toProject.GetRevisionId(toProject._allrefs)
2283
2284 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2285 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2286 return logs
2287
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002289 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002290 self._project = project
2291 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002292 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002293
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002294 def LsOthers(self):
2295 p = GitCommand(self._project,
2296 ['ls-files',
2297 '-z',
2298 '--others',
2299 '--exclude-standard'],
2300 bare = False,
David James8d201162013-10-11 17:03:19 -07002301 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002302 capture_stdout = True,
2303 capture_stderr = True)
2304 if p.Wait() == 0:
2305 out = p.stdout
2306 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002307 return out[:-1].split('\0') # pylint: disable=W1401
2308 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002309 return []
2310
2311 def DiffZ(self, name, *args):
2312 cmd = [name]
2313 cmd.append('-z')
2314 cmd.extend(args)
2315 p = GitCommand(self._project,
2316 cmd,
David James8d201162013-10-11 17:03:19 -07002317 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002318 bare = False,
2319 capture_stdout = True,
2320 capture_stderr = True)
2321 try:
2322 out = p.process.stdout.read()
2323 r = {}
2324 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002325 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002326 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002327 try:
2328 info = out.next()
2329 path = out.next()
2330 except StopIteration:
2331 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002332
2333 class _Info(object):
2334 def __init__(self, path, omode, nmode, oid, nid, state):
2335 self.path = path
2336 self.src_path = None
2337 self.old_mode = omode
2338 self.new_mode = nmode
2339 self.old_id = oid
2340 self.new_id = nid
2341
2342 if len(state) == 1:
2343 self.status = state
2344 self.level = None
2345 else:
2346 self.status = state[:1]
2347 self.level = state[1:]
2348 while self.level.startswith('0'):
2349 self.level = self.level[1:]
2350
2351 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002352 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002353 if info.status in ('R', 'C'):
2354 info.src_path = info.path
2355 info.path = out.next()
2356 r[info.path] = info
2357 return r
2358 finally:
2359 p.Wait()
2360
2361 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002362 if self._bare:
2363 path = os.path.join(self._project.gitdir, HEAD)
2364 else:
2365 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002366 try:
2367 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002368 except IOError as e:
2369 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002370 try:
2371 line = fd.read()
2372 finally:
2373 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302374 try:
2375 line = line.decode()
2376 except AttributeError:
2377 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002378 if line.startswith('ref: '):
2379 return line[5:-1]
2380 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002381
2382 def SetHead(self, ref, message=None):
2383 cmdv = []
2384 if message is not None:
2385 cmdv.extend(['-m', message])
2386 cmdv.append(HEAD)
2387 cmdv.append(ref)
2388 self.symbolic_ref(*cmdv)
2389
2390 def DetachHead(self, new, message=None):
2391 cmdv = ['--no-deref']
2392 if message is not None:
2393 cmdv.extend(['-m', message])
2394 cmdv.append(HEAD)
2395 cmdv.append(new)
2396 self.update_ref(*cmdv)
2397
2398 def UpdateRef(self, name, new, old=None,
2399 message=None,
2400 detach=False):
2401 cmdv = []
2402 if message is not None:
2403 cmdv.extend(['-m', message])
2404 if detach:
2405 cmdv.append('--no-deref')
2406 cmdv.append(name)
2407 cmdv.append(new)
2408 if old is not None:
2409 cmdv.append(old)
2410 self.update_ref(*cmdv)
2411
2412 def DeleteRef(self, name, old=None):
2413 if not old:
2414 old = self.rev_parse(name)
2415 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002416 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002417
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002418 def rev_list(self, *args, **kw):
2419 if 'format' in kw:
2420 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2421 else:
2422 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002423 cmdv.extend(args)
2424 p = GitCommand(self._project,
2425 cmdv,
2426 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002427 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002428 capture_stdout = True,
2429 capture_stderr = True)
2430 r = []
2431 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002432 if line[-1] == '\n':
2433 line = line[:-1]
2434 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002435 if p.Wait() != 0:
2436 raise GitError('%s rev-list %s: %s' % (
2437 self._project.name,
2438 str(args),
2439 p.stderr))
2440 return r
2441
2442 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002443 """Allow arbitrary git commands using pythonic syntax.
2444
2445 This allows you to do things like:
2446 git_obj.rev_parse('HEAD')
2447
2448 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2449 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002450 Any other positional arguments will be passed to the git command, and the
2451 following keyword arguments are supported:
2452 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002453
2454 Args:
2455 name: The name of the git command to call. Any '_' characters will
2456 be replaced with '-'.
2457
2458 Returns:
2459 A callable object that will try to call git with the named command.
2460 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002461 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002462 def runner(*args, **kwargs):
2463 cmdv = []
2464 config = kwargs.pop('config', None)
2465 for k in kwargs:
2466 raise TypeError('%s() got an unexpected keyword argument %r'
2467 % (name, k))
2468 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002469 if not git_require((1, 7, 2)):
2470 raise ValueError('cannot set config on command line for %s()'
2471 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302472 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002473 cmdv.append('-c')
2474 cmdv.append('%s=%s' % (k, v))
2475 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002476 cmdv.extend(args)
2477 p = GitCommand(self._project,
2478 cmdv,
2479 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002480 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002481 capture_stdout = True,
2482 capture_stderr = True)
2483 if p.Wait() != 0:
2484 raise GitError('%s %s: %s' % (
2485 self._project.name,
2486 name,
2487 p.stderr))
2488 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302489 try:
Conley Owensedd01512013-09-26 12:59:58 -07002490 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302491 except AttributeError:
2492 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002493 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2494 return r[:-1]
2495 return r
2496 return runner
2497
2498
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002499class _PriorSyncFailedError(Exception):
2500 def __str__(self):
2501 return 'prior sync failed; rebase still in progress'
2502
2503class _DirtyError(Exception):
2504 def __str__(self):
2505 return 'contains uncommitted changes'
2506
2507class _InfoMessage(object):
2508 def __init__(self, project, text):
2509 self.project = project
2510 self.text = text
2511
2512 def Print(self, syncbuf):
2513 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2514 syncbuf.out.nl()
2515
2516class _Failure(object):
2517 def __init__(self, project, why):
2518 self.project = project
2519 self.why = why
2520
2521 def Print(self, syncbuf):
2522 syncbuf.out.fail('error: %s/: %s',
2523 self.project.relpath,
2524 str(self.why))
2525 syncbuf.out.nl()
2526
2527class _Later(object):
2528 def __init__(self, project, action):
2529 self.project = project
2530 self.action = action
2531
2532 def Run(self, syncbuf):
2533 out = syncbuf.out
2534 out.project('project %s/', self.project.relpath)
2535 out.nl()
2536 try:
2537 self.action()
2538 out.nl()
2539 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002540 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002541 out.nl()
2542 return False
2543
2544class _SyncColoring(Coloring):
2545 def __init__(self, config):
2546 Coloring.__init__(self, config, 'reposync')
2547 self.project = self.printer('header', attr = 'bold')
2548 self.info = self.printer('info')
2549 self.fail = self.printer('fail', fg='red')
2550
2551class SyncBuffer(object):
2552 def __init__(self, config, detach_head=False):
2553 self._messages = []
2554 self._failures = []
2555 self._later_queue1 = []
2556 self._later_queue2 = []
2557
2558 self.out = _SyncColoring(config)
2559 self.out.redirect(sys.stderr)
2560
2561 self.detach_head = detach_head
2562 self.clean = True
2563
2564 def info(self, project, fmt, *args):
2565 self._messages.append(_InfoMessage(project, fmt % args))
2566
2567 def fail(self, project, err=None):
2568 self._failures.append(_Failure(project, err))
2569 self.clean = False
2570
2571 def later1(self, project, what):
2572 self._later_queue1.append(_Later(project, what))
2573
2574 def later2(self, project, what):
2575 self._later_queue2.append(_Later(project, what))
2576
2577 def Finish(self):
2578 self._PrintMessages()
2579 self._RunLater()
2580 self._PrintMessages()
2581 return self.clean
2582
2583 def _RunLater(self):
2584 for q in ['_later_queue1', '_later_queue2']:
2585 if not self._RunQueue(q):
2586 return
2587
2588 def _RunQueue(self, queue):
2589 for m in getattr(self, queue):
2590 if not m.Run(self):
2591 self.clean = False
2592 return False
2593 setattr(self, queue, [])
2594 return True
2595
2596 def _PrintMessages(self):
2597 for m in self._messages:
2598 m.Print(self)
2599 for m in self._failures:
2600 m.Print(self)
2601
2602 self._messages = []
2603 self._failures = []
2604
2605
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002606class MetaProject(Project):
2607 """A special project housed under .repo.
2608 """
2609 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002610 Project.__init__(self,
2611 manifest = manifest,
2612 name = name,
2613 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002614 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002615 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002616 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002617 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002618 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002619 revisionId = None,
2620 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002621
2622 def PreSync(self):
2623 if self.Exists:
2624 cb = self.CurrentBranch
2625 if cb:
2626 base = self.GetBranch(cb).merge
2627 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002628 self.revisionExpr = base
2629 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002630
Florian Vallee5d016502012-06-07 17:19:26 +02002631 def MetaBranchSwitch(self, target):
2632 """ Prepare MetaProject for manifest branch switch
2633 """
2634
2635 # detach and delete manifest branch, allowing a new
2636 # branch to take over
2637 syncbuf = SyncBuffer(self.config, detach_head = True)
2638 self.Sync_LocalHalf(syncbuf)
2639 syncbuf.Finish()
2640
2641 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002642 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002643 capture_stdout = True,
2644 capture_stderr = True).Wait() == 0
2645
2646
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002647 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002648 def LastFetch(self):
2649 try:
2650 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2651 return os.path.getmtime(fh)
2652 except OSError:
2653 return 0
2654
2655 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002656 def HasChanges(self):
2657 """Has the remote received new commits not yet checked out?
2658 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002659 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002660 return False
2661
David Pursehouse8a68ff92012-09-24 12:15:13 +09002662 all_refs = self.bare_ref.all
2663 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002664 head = self.work_git.GetHead()
2665 if head.startswith(R_HEADS):
2666 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002667 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002668 except KeyError:
2669 head = None
2670
2671 if revid == head:
2672 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002673 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002674 return True
2675 return False