blob: 0f110961587bfe62fa1e2d9cd1a21f2675fc3f98 [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
Chris AtLee2fb64662014-01-16 21:32:33 -05001106 is_sha1 = False
1107 if ID_RE.match(self.revisionExpr) is not None:
1108 is_sha1 = True
1109 if is_sha1 and self._CheckForSha1():
1110 # Don't need to fetch since we already have this revision
1111 return True
1112
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001113 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001114 current_branch_only=current_branch_only,
1115 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001116 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001117
1118 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001119 self._InitMRef()
1120 else:
1121 self._InitMirrorHead()
1122 try:
1123 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1124 except OSError:
1125 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001127
1128 def PostRepoUpgrade(self):
1129 self._InitHooks()
1130
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001131 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001132 for copyfile in self.copyfiles:
1133 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001134 for linkfile in self.linkfiles:
1135 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136
Julien Camperguedd654222014-01-09 16:21:37 +01001137 def GetCommitRevisionId(self):
1138 """Get revisionId of a commit.
1139
1140 Use this method instead of GetRevisionId to get the id of the commit rather
1141 than the id of the current git object (for example, a tag)
1142
1143 """
1144 if not self.revisionExpr.startswith(R_TAGS):
1145 return self.GetRevisionId(self._allrefs)
1146
1147 try:
1148 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1149 except GitError:
1150 raise ManifestInvalidRevisionError(
1151 'revision %s in %s not found' % (self.revisionExpr,
1152 self.name))
1153
David Pursehouse8a68ff92012-09-24 12:15:13 +09001154 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001155 if self.revisionId:
1156 return self.revisionId
1157
1158 rem = self.GetRemote(self.remote.name)
1159 rev = rem.ToLocal(self.revisionExpr)
1160
David Pursehouse8a68ff92012-09-24 12:15:13 +09001161 if all_refs is not None and rev in all_refs:
1162 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001163
1164 try:
1165 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1166 except GitError:
1167 raise ManifestInvalidRevisionError(
1168 'revision %s in %s not found' % (self.revisionExpr,
1169 self.name))
1170
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001171 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172 """Perform only the local IO portion of the sync process.
1173 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001174 """
David James8d201162013-10-11 17:03:19 -07001175 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001176 all_refs = self.bare_ref.all
1177 self.CleanPublishedCache(all_refs)
1178 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001179
David Pursehouse1d947b32012-10-25 12:23:11 +09001180 def _doff():
1181 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001182 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001183
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001184 head = self.work_git.GetHead()
1185 if head.startswith(R_HEADS):
1186 branch = head[len(R_HEADS):]
1187 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001188 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001189 except KeyError:
1190 head = None
1191 else:
1192 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001194 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195 # Currently on a detached HEAD. The user is assumed to
1196 # not have any local modifications worth worrying about.
1197 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001198 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001199 syncbuf.fail(self, _PriorSyncFailedError())
1200 return
1201
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001202 if head == revid:
1203 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001204 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001205 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001206 if not syncbuf.detach_head:
1207 return
1208 else:
1209 lost = self._revlist(not_rev(revid), HEAD)
1210 if lost:
1211 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001212
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001214 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001215 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001216 syncbuf.fail(self, e)
1217 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001218 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001219 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001220
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001221 if head == revid:
1222 # No changes; don't do anything further.
1223 #
1224 return
1225
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001226 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001228 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001229 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001230 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001231 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001232 syncbuf.info(self,
1233 "leaving %s; does not track upstream",
1234 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001235 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001236 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001237 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001238 syncbuf.fail(self, e)
1239 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001240 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001241 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001243 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001244 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001245 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001246 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247 if not_merged:
1248 if upstream_gain:
1249 # The user has published this branch and some of those
1250 # commits are not yet merged upstream. We do not want
1251 # to rewrite the published commits so we punt.
1252 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001253 syncbuf.fail(self,
1254 "branch %s is published (but not merged) and is now %d commits behind"
1255 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001256 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001257 elif pub == head:
1258 # All published commits are merged, and thus we are a
1259 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001260 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001261 syncbuf.later1(self, _doff)
1262 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001264 # Examine the local commits not in the remote. Find the
1265 # last one attributed to this user, if any.
1266 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001267 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001268 last_mine = None
1269 cnt_mine = 0
1270 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301271 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001272 if committer_email == self.UserEmail:
1273 last_mine = commit_id
1274 cnt_mine += 1
1275
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001276 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001277 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001278
1279 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001280 syncbuf.fail(self, _DirtyError())
1281 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001282
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001283 # If the upstream switched on us, warn the user.
1284 #
1285 if branch.merge != self.revisionExpr:
1286 if branch.merge and self.revisionExpr:
1287 syncbuf.info(self,
1288 'manifest switched %s...%s',
1289 branch.merge,
1290 self.revisionExpr)
1291 elif branch.merge:
1292 syncbuf.info(self,
1293 'manifest no longer tracks %s',
1294 branch.merge)
1295
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001296 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001298 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001300 syncbuf.info(self,
1301 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001302 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001304 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001305 if not ID_RE.match(self.revisionExpr):
1306 # in case of manifest sync the revisionExpr might be a SHA1
1307 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308 branch.Save()
1309
Mike Pontillod3153822012-02-28 11:53:24 -08001310 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001311 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001312 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001313 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001314 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001315 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001317 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001318 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001319 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001320 syncbuf.fail(self, e)
1321 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001323 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001325 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326 # dest should already be an absolute path, but src is project relative
1327 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001328 abssrc = os.path.join(self.worktree, src)
1329 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001331 def AddLinkFile(self, src, dest, absdest):
1332 # dest should already be an absolute path, but src is project relative
1333 # make src an absolute path
1334 abssrc = os.path.join(self.worktree, src)
1335 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1336
James W. Mills24c13082012-04-12 15:04:13 -05001337 def AddAnnotation(self, name, value, keep):
1338 self.annotations.append(_Annotation(name, value, keep))
1339
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001340 def DownloadPatchSet(self, change_id, patch_id):
1341 """Download a single patch set of a single change to FETCH_HEAD.
1342 """
1343 remote = self.GetRemote(self.remote.name)
1344
1345 cmd = ['fetch', remote.name]
1346 cmd.append('refs/changes/%2.2d/%d/%d' \
1347 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001348 if GitCommand(self, cmd, bare=True).Wait() != 0:
1349 return None
1350 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001351 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001352 change_id,
1353 patch_id,
1354 self.bare_git.rev_parse('FETCH_HEAD'))
1355
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001356
1357## Branch Management ##
1358
1359 def StartBranch(self, name):
1360 """Create a new branch off the manifest's revision.
1361 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001362 head = self.work_git.GetHead()
1363 if head == (R_HEADS + name):
1364 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365
David Pursehouse8a68ff92012-09-24 12:15:13 +09001366 all_refs = self.bare_ref.all
1367 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001368 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001369 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001370 capture_stdout = True,
1371 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001372
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001373 branch = self.GetBranch(name)
1374 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001375 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001376 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001377
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001378 if head.startswith(R_HEADS):
1379 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001380 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001381 except KeyError:
1382 head = None
1383
1384 if revid and head and revid == head:
1385 ref = os.path.join(self.gitdir, R_HEADS + name)
1386 try:
1387 os.makedirs(os.path.dirname(ref))
1388 except OSError:
1389 pass
1390 _lwrite(ref, '%s\n' % revid)
1391 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1392 'ref: %s%s\n' % (R_HEADS, name))
1393 branch.Save()
1394 return True
1395
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001396 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001397 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001398 capture_stdout = True,
1399 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001400 branch.Save()
1401 return True
1402 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403
Wink Saville02d79452009-04-10 13:01:24 -07001404 def CheckoutBranch(self, name):
1405 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001406
1407 Args:
1408 name: The name of the branch to checkout.
1409
1410 Returns:
1411 True if the checkout succeeded; False if it didn't; None if the branch
1412 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001413 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001414 rev = R_HEADS + name
1415 head = self.work_git.GetHead()
1416 if head == rev:
1417 # Already on the branch
1418 #
1419 return True
Wink Saville02d79452009-04-10 13:01:24 -07001420
David Pursehouse8a68ff92012-09-24 12:15:13 +09001421 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001422 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001423 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001424 except KeyError:
1425 # Branch does not exist in this project
1426 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001427 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001428
1429 if head.startswith(R_HEADS):
1430 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001431 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001432 except KeyError:
1433 head = None
1434
1435 if head == revid:
1436 # Same revision; just update HEAD to point to the new
1437 # target branch, but otherwise take no other action.
1438 #
1439 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1440 'ref: %s%s\n' % (R_HEADS, name))
1441 return True
Wink Saville02d79452009-04-10 13:01:24 -07001442
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001443 return GitCommand(self,
1444 ['checkout', name, '--'],
1445 capture_stdout = True,
1446 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001447
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001448 def AbandonBranch(self, name):
1449 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001450
1451 Args:
1452 name: The name of the branch to abandon.
1453
1454 Returns:
1455 True if the abandon succeeded; False if it didn't; None if the branch
1456 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001457 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001458 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001459 all_refs = self.bare_ref.all
1460 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001461 # Doesn't exist
1462 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001463
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001464 head = self.work_git.GetHead()
1465 if head == rev:
1466 # We can't destroy the branch while we are sitting
1467 # on it. Switch to a detached HEAD.
1468 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001469 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001470
David Pursehouse8a68ff92012-09-24 12:15:13 +09001471 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001472 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001473 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1474 '%s\n' % revid)
1475 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001476 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001477
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001478 return GitCommand(self,
1479 ['branch', '-D', name],
1480 capture_stdout = True,
1481 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001482
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001483 def PruneHeads(self):
1484 """Prune any topic branches already merged into upstream.
1485 """
1486 cb = self.CurrentBranch
1487 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001488 left = self._allrefs
1489 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001490 if name.startswith(R_HEADS):
1491 name = name[len(R_HEADS):]
1492 if cb is None or name != cb:
1493 kill.append(name)
1494
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001495 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001496 if cb is not None \
1497 and not self._revlist(HEAD + '...' + rev) \
1498 and not self.IsDirty(consider_untracked = False):
1499 self.work_git.DetachHead(HEAD)
1500 kill.append(cb)
1501
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001502 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001503 old = self.bare_git.GetHead()
1504 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001505 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1506
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001507 try:
1508 self.bare_git.DetachHead(rev)
1509
1510 b = ['branch', '-d']
1511 b.extend(kill)
1512 b = GitCommand(self, b, bare=True,
1513 capture_stdout=True,
1514 capture_stderr=True)
1515 b.Wait()
1516 finally:
1517 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001518 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001519
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001520 for branch in kill:
1521 if (R_HEADS + branch) not in left:
1522 self.CleanPublishedCache()
1523 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001524
1525 if cb and cb not in kill:
1526 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001527 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001528
1529 kept = []
1530 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001531 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001532 branch = self.GetBranch(branch)
1533 base = branch.LocalMerge
1534 if not base:
1535 base = rev
1536 kept.append(ReviewableBranch(self, branch, base))
1537 return kept
1538
1539
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001540## Submodule Management ##
1541
1542 def GetRegisteredSubprojects(self):
1543 result = []
1544 def rec(subprojects):
1545 if not subprojects:
1546 return
1547 result.extend(subprojects)
1548 for p in subprojects:
1549 rec(p.subprojects)
1550 rec(self.subprojects)
1551 return result
1552
1553 def _GetSubmodules(self):
1554 # Unfortunately we cannot call `git submodule status --recursive` here
1555 # because the working tree might not exist yet, and it cannot be used
1556 # without a working tree in its current implementation.
1557
1558 def get_submodules(gitdir, rev):
1559 # Parse .gitmodules for submodule sub_paths and sub_urls
1560 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1561 if not sub_paths:
1562 return []
1563 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1564 # revision of submodule repository
1565 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1566 submodules = []
1567 for sub_path, sub_url in zip(sub_paths, sub_urls):
1568 try:
1569 sub_rev = sub_revs[sub_path]
1570 except KeyError:
1571 # Ignore non-exist submodules
1572 continue
1573 submodules.append((sub_rev, sub_path, sub_url))
1574 return submodules
1575
1576 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1577 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1578 def parse_gitmodules(gitdir, rev):
1579 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1580 try:
1581 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1582 bare = True, gitdir = gitdir)
1583 except GitError:
1584 return [], []
1585 if p.Wait() != 0:
1586 return [], []
1587
1588 gitmodules_lines = []
1589 fd, temp_gitmodules_path = tempfile.mkstemp()
1590 try:
1591 os.write(fd, p.stdout)
1592 os.close(fd)
1593 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1594 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1595 bare = True, gitdir = gitdir)
1596 if p.Wait() != 0:
1597 return [], []
1598 gitmodules_lines = p.stdout.split('\n')
1599 except GitError:
1600 return [], []
1601 finally:
1602 os.remove(temp_gitmodules_path)
1603
1604 names = set()
1605 paths = {}
1606 urls = {}
1607 for line in gitmodules_lines:
1608 if not line:
1609 continue
1610 m = re_path.match(line)
1611 if m:
1612 names.add(m.group(1))
1613 paths[m.group(1)] = m.group(2)
1614 continue
1615 m = re_url.match(line)
1616 if m:
1617 names.add(m.group(1))
1618 urls[m.group(1)] = m.group(2)
1619 continue
1620 names = sorted(names)
1621 return ([paths.get(name, '') for name in names],
1622 [urls.get(name, '') for name in names])
1623
1624 def git_ls_tree(gitdir, rev, paths):
1625 cmd = ['ls-tree', rev, '--']
1626 cmd.extend(paths)
1627 try:
1628 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1629 bare = True, gitdir = gitdir)
1630 except GitError:
1631 return []
1632 if p.Wait() != 0:
1633 return []
1634 objects = {}
1635 for line in p.stdout.split('\n'):
1636 if not line.strip():
1637 continue
1638 object_rev, object_path = line.split()[2:4]
1639 objects[object_path] = object_rev
1640 return objects
1641
1642 try:
1643 rev = self.GetRevisionId()
1644 except GitError:
1645 return []
1646 return get_submodules(self.gitdir, rev)
1647
1648 def GetDerivedSubprojects(self):
1649 result = []
1650 if not self.Exists:
1651 # If git repo does not exist yet, querying its submodules will
1652 # mess up its states; so return here.
1653 return result
1654 for rev, path, url in self._GetSubmodules():
1655 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001656 relpath, worktree, gitdir, objdir = \
1657 self.manifest.GetSubprojectPaths(self, name, path)
1658 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001659 if project:
1660 result.extend(project.GetDerivedSubprojects())
1661 continue
David James8d201162013-10-11 17:03:19 -07001662
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001663 remote = RemoteSpec(self.remote.name,
1664 url = url,
1665 review = self.remote.review)
1666 subproject = Project(manifest = self.manifest,
1667 name = name,
1668 remote = remote,
1669 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001670 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001671 worktree = worktree,
1672 relpath = relpath,
1673 revisionExpr = self.revisionExpr,
1674 revisionId = rev,
1675 rebase = self.rebase,
1676 groups = self.groups,
1677 sync_c = self.sync_c,
1678 sync_s = self.sync_s,
1679 parent = self,
1680 is_derived = True)
1681 result.append(subproject)
1682 result.extend(subproject.GetDerivedSubprojects())
1683 return result
1684
1685
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001686## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001687 def _CheckForSha1(self):
1688 try:
1689 # if revision (sha or tag) is not present then following function
1690 # throws an error.
1691 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1692 return True
1693 except GitError:
1694 # There is no such persistent revision. We have to fetch it.
1695 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001696
Julien Campergue335f5ef2013-10-16 11:02:35 +02001697 def _FetchArchive(self, tarpath, cwd=None):
1698 cmd = ['archive', '-v', '-o', tarpath]
1699 cmd.append('--remote=%s' % self.remote.url)
1700 cmd.append('--prefix=%s/' % self.relpath)
1701 cmd.append(self.revisionExpr)
1702
1703 command = GitCommand(self, cmd, cwd=cwd,
1704 capture_stdout=True,
1705 capture_stderr=True)
1706
1707 if command.Wait() != 0:
1708 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1709
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001710 def _RemoteFetch(self, name=None,
1711 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001712 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001713 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001714 alt_dir=None,
1715 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001716
1717 is_sha1 = False
1718 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001719 depth = None
1720
1721 # The depth should not be used when fetching to a mirror because
1722 # it will result in a shallow repository that cannot be cloned or
1723 # fetched from.
1724 if not self.manifest.IsMirror:
1725 if self.clone_depth:
1726 depth = self.clone_depth
1727 else:
1728 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001729
Shawn Pearce69e04d82014-01-29 12:48:54 -08001730 if depth:
1731 current_branch_only = True
1732
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001733 if current_branch_only:
1734 if ID_RE.match(self.revisionExpr) is not None:
1735 is_sha1 = True
1736 elif self.revisionExpr.startswith(R_TAGS):
1737 # this is a tag and its sha1 value should never change
1738 tag_name = self.revisionExpr[len(R_TAGS):]
1739
1740 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001741 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001742 return True
Brian Harring14a66742012-09-28 20:21:57 -07001743 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1744 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001745
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001746 if not name:
1747 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001748
1749 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001750 remote = self.GetRemote(name)
1751 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001752 ssh_proxy = True
1753
Shawn O. Pearce88443382010-10-08 10:02:09 +02001754 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001755 if alt_dir and 'objects' == os.path.basename(alt_dir):
1756 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001757 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1758 remote = self.GetRemote(name)
1759
David Pursehouse8a68ff92012-09-24 12:15:13 +09001760 all_refs = self.bare_ref.all
1761 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001762 tmp = set()
1763
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301764 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001765 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001766 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001767 all_refs[r] = ref_id
1768 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001769 continue
1770
David Pursehouse8a68ff92012-09-24 12:15:13 +09001771 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001772 continue
1773
David Pursehouse8a68ff92012-09-24 12:15:13 +09001774 r = 'refs/_alt/%s' % ref_id
1775 all_refs[r] = ref_id
1776 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001777 tmp.add(r)
1778
Shawn O. Pearce88443382010-10-08 10:02:09 +02001779 tmp_packed = ''
1780 old_packed = ''
1781
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301782 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001783 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001784 tmp_packed += line
1785 if r not in tmp:
1786 old_packed += line
1787
1788 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001789 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001790 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001791
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001792 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001793
1794 # The --depth option only affects the initial fetch; after that we'll do
1795 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001796 if depth and initial:
1797 cmd.append('--depth=%s' % depth)
1798
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001799 if quiet:
1800 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001801 if not self.worktree:
1802 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001803 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001804
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001805 # If using depth then we should not get all the tags since they may
1806 # be outside of the depth.
1807 if no_tags or depth:
1808 cmd.append('--no-tags')
1809 else:
1810 cmd.append('--tags')
1811
Brian Harring14a66742012-09-28 20:21:57 -07001812 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001813 # Fetch whole repo
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301814 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001815 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001816 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001817 cmd.append(tag_name)
1818 else:
1819 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001820 if is_sha1:
1821 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001822 if branch.startswith(R_HEADS):
1823 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301824 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001825
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001826 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001827 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001828 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1829 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001830 ok = True
1831 break
Brian Harring14a66742012-09-28 20:21:57 -07001832 elif current_branch_only and is_sha1 and ret == 128:
1833 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1834 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1835 # abort the optimization attempt and do a full sync.
1836 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001837 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001838
1839 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001840 # Ensure that some refs exist. Otherwise, we probably aren't looking
1841 # at a real git repository and may have a bad url.
1842 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001843 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001844
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001845 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001846 if old_packed != '':
1847 _lwrite(packed_refs, old_packed)
1848 else:
1849 os.remove(packed_refs)
1850 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001851
1852 if is_sha1 and current_branch_only and self.upstream:
1853 # We just synced the upstream given branch; verify we
1854 # got what we wanted, else trigger a second run of all
1855 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001856 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001857 return self._RemoteFetch(name=name, current_branch_only=False,
1858 initial=False, quiet=quiet, alt_dir=alt_dir)
1859
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001860 return ok
1861
1862 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001863 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001864 return False
1865
1866 remote = self.GetRemote(self.remote.name)
1867 bundle_url = remote.url + '/clone.bundle'
1868 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001869 if GetSchemeFromUrl(bundle_url) not in (
1870 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001871 return False
1872
1873 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1874 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001875
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001876 exist_dst = os.path.exists(bundle_dst)
1877 exist_tmp = os.path.exists(bundle_tmp)
1878
1879 if not initial and not exist_dst and not exist_tmp:
1880 return False
1881
1882 if not exist_dst:
1883 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1884 if not exist_dst:
1885 return False
1886
1887 cmd = ['fetch']
1888 if quiet:
1889 cmd.append('--quiet')
1890 if not self.worktree:
1891 cmd.append('--update-head-ok')
1892 cmd.append(bundle_dst)
1893 for f in remote.fetch:
1894 cmd.append(str(f))
1895 cmd.append('refs/tags/*:refs/tags/*')
1896
1897 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001898 if os.path.exists(bundle_dst):
1899 os.remove(bundle_dst)
1900 if os.path.exists(bundle_tmp):
1901 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001902 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001903
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001904 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001905 if os.path.exists(dstPath):
1906 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001907
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001908 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001909 if quiet:
1910 cmd += ['--silent']
1911 if os.path.exists(tmpPath):
1912 size = os.stat(tmpPath).st_size
1913 if size >= 1024:
1914 cmd += ['--continue-at', '%d' % (size,)]
1915 else:
1916 os.remove(tmpPath)
1917 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1918 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001919 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001920 if cookiefile:
1921 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001922 if srcUrl.startswith('persistent-'):
1923 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001924 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001925
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001926 if IsTrace():
1927 Trace('%s', ' '.join(cmd))
1928 try:
1929 proc = subprocess.Popen(cmd)
1930 except OSError:
1931 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001932
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001933 curlret = proc.wait()
1934
1935 if curlret == 22:
1936 # From curl man page:
1937 # 22: HTTP page not retrieved. The requested url was not found or
1938 # returned another error with the HTTP error code being 400 or above.
1939 # This return code only appears if -f, --fail is used.
1940 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001941 print("Server does not provide clone.bundle; ignoring.",
1942 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001943 return False
1944
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001945 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001946 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001947 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001948 return True
1949 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001950 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001951 return False
1952 else:
1953 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001954
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001955 def _IsValidBundle(self, path):
1956 try:
1957 with open(path) as f:
1958 if f.read(16) == '# v2 git bundle\n':
1959 return True
1960 else:
1961 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1962 return False
1963 except OSError:
1964 return False
1965
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001966 def _GetBundleCookieFile(self, url):
1967 if url.startswith('persistent-'):
1968 try:
1969 p = subprocess.Popen(
1970 ['git-remote-persistent-https', '-print_config', url],
1971 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1972 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001973 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001974 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001975 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001976 for line in p.stdout:
1977 line = line.strip()
1978 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001979 cookiefile = line[len(prefix):]
1980 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001981 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001982 err_msg = p.stderr.read()
1983 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001984 pass # Persistent proxy doesn't support -print_config.
1985 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001986 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001987 if cookiefile:
1988 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001989 except OSError as e:
1990 if e.errno == errno.ENOENT:
1991 pass # No persistent proxy.
1992 raise
1993 return GitConfig.ForUser().GetString('http.cookiefile')
1994
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001995 def _Checkout(self, rev, quiet=False):
1996 cmd = ['checkout']
1997 if quiet:
1998 cmd.append('-q')
1999 cmd.append(rev)
2000 cmd.append('--')
2001 if GitCommand(self, cmd).Wait() != 0:
2002 if self._allrefs:
2003 raise GitError('%s checkout %s ' % (self.name, rev))
2004
Pierre Tardye5a21222011-03-24 16:28:18 +01002005 def _CherryPick(self, rev, quiet=False):
2006 cmd = ['cherry-pick']
2007 cmd.append(rev)
2008 cmd.append('--')
2009 if GitCommand(self, cmd).Wait() != 0:
2010 if self._allrefs:
2011 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2012
Erwan Mahea94f1622011-08-19 13:56:09 +02002013 def _Revert(self, rev, quiet=False):
2014 cmd = ['revert']
2015 cmd.append('--no-edit')
2016 cmd.append(rev)
2017 cmd.append('--')
2018 if GitCommand(self, cmd).Wait() != 0:
2019 if self._allrefs:
2020 raise GitError('%s revert %s ' % (self.name, rev))
2021
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002022 def _ResetHard(self, rev, quiet=True):
2023 cmd = ['reset', '--hard']
2024 if quiet:
2025 cmd.append('-q')
2026 cmd.append(rev)
2027 if GitCommand(self, cmd).Wait() != 0:
2028 raise GitError('%s reset --hard %s ' % (self.name, rev))
2029
2030 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002031 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002032 if onto is not None:
2033 cmd.extend(['--onto', onto])
2034 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002035 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002036 raise GitError('%s rebase %s ' % (self.name, upstream))
2037
Pierre Tardy3d125942012-05-04 12:18:12 +02002038 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002039 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002040 if ffonly:
2041 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002042 if GitCommand(self, cmd).Wait() != 0:
2043 raise GitError('%s merge %s ' % (self.name, head))
2044
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002045 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002046 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002047
2048 # Initialize the bare repository, which contains all of the objects.
2049 if not os.path.exists(self.objdir):
2050 os.makedirs(self.objdir)
2051 self.bare_objdir.init()
2052
2053 # If we have a separate directory to hold refs, initialize it as well.
2054 if self.objdir != self.gitdir:
2055 os.makedirs(self.gitdir)
2056 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2057 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002058
Shawn O. Pearce88443382010-10-08 10:02:09 +02002059 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002060 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002061
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002062 if ref_dir or mirror_git:
2063 if not mirror_git:
2064 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002065 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2066 self.relpath + '.git')
2067
2068 if os.path.exists(mirror_git):
2069 ref_dir = mirror_git
2070
2071 elif os.path.exists(repo_git):
2072 ref_dir = repo_git
2073
2074 else:
2075 ref_dir = None
2076
2077 if ref_dir:
2078 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2079 os.path.join(ref_dir, 'objects') + '\n')
2080
Jimmie Westera0444582012-10-24 13:44:42 +02002081 self._UpdateHooks()
2082
2083 m = self.manifest.manifestProject.config
2084 for key in ['user.name', 'user.email']:
2085 if m.Has(key, include_defaults = False):
2086 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002087 if self.manifest.IsMirror:
2088 self.config.SetString('core.bare', 'true')
2089 else:
2090 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002091
Jimmie Westera0444582012-10-24 13:44:42 +02002092 def _UpdateHooks(self):
2093 if os.path.exists(self.gitdir):
2094 # Always recreate hooks since they can have been changed
2095 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002096 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002097 try:
2098 to_rm = os.listdir(hooks)
2099 except OSError:
2100 to_rm = []
2101 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002102 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002103 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002104
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002105 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002106 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002107 if not os.path.exists(hooks):
2108 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002109 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002110 name = os.path.basename(stock_hook)
2111
Victor Boivie65e0f352011-04-18 11:23:29 +02002112 if name in ('commit-msg',) and not self.remote.review \
2113 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002114 # Don't install a Gerrit Code Review hook if this
2115 # project does not appear to use it for reviews.
2116 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002117 # Since the manifest project is one of those, but also
2118 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002119 continue
2120
2121 dst = os.path.join(hooks, name)
2122 if os.path.islink(dst):
2123 continue
2124 if os.path.exists(dst):
2125 if filecmp.cmp(stock_hook, dst, shallow=False):
2126 os.remove(dst)
2127 else:
2128 _error("%s: Not replacing %s hook", self.relpath, name)
2129 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002130 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002131 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002132 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002133 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002134 raise GitError('filesystem must support symlinks')
2135 else:
2136 raise
2137
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002138 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002139 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002140 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002141 remote.url = self.remote.url
2142 remote.review = self.remote.review
2143 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002144
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002145 if self.worktree:
2146 remote.ResetFetch(mirror=False)
2147 else:
2148 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002149 remote.Save()
2150
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002151 def _InitMRef(self):
2152 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002153 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002154
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002155 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002156 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002157
2158 def _InitAnyMRef(self, ref):
2159 cur = self.bare_ref.symref(ref)
2160
2161 if self.revisionId:
2162 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2163 msg = 'manifest set to %s' % self.revisionId
2164 dst = self.revisionId + '^0'
2165 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2166 else:
2167 remote = self.GetRemote(self.remote.name)
2168 dst = remote.ToLocal(self.revisionExpr)
2169 if cur != dst:
2170 msg = 'manifest set to %s' % self.revisionExpr
2171 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002172
David James8d201162013-10-11 17:03:19 -07002173 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2174 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2175
2176 Args:
2177 gitdir: The bare git repository. Must already be initialized.
2178 dotgit: The repository you would like to initialize.
2179 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2180 Only one work tree can store refs under a given |gitdir|.
2181 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2182 This saves you the effort of initializing |dotgit| yourself.
2183 """
2184 # These objects can be shared between several working trees.
2185 symlink_files = ['description', 'info']
2186 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2187 if share_refs:
2188 # These objects can only be used by a single working tree.
2189 symlink_files += ['config', 'packed-refs']
2190 symlink_dirs += ['logs', 'refs']
2191 to_symlink = symlink_files + symlink_dirs
2192
2193 to_copy = []
2194 if copy_all:
2195 to_copy = os.listdir(gitdir)
2196
2197 for name in set(to_copy).union(to_symlink):
2198 try:
2199 src = os.path.realpath(os.path.join(gitdir, name))
2200 dst = os.path.realpath(os.path.join(dotgit, name))
2201
2202 if os.path.lexists(dst) and not os.path.islink(dst):
2203 raise GitError('cannot overwrite a local work tree')
2204
2205 # If the source dir doesn't exist, create an empty dir.
2206 if name in symlink_dirs and not os.path.lexists(src):
2207 os.makedirs(src)
2208
2209 if name in to_symlink:
2210 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2211 elif copy_all and not os.path.islink(dst):
2212 if os.path.isdir(src):
2213 shutil.copytree(src, dst)
2214 elif os.path.isfile(src):
2215 shutil.copy(src, dst)
2216 except OSError as e:
2217 if e.errno == errno.EPERM:
2218 raise GitError('filesystem must support symlinks')
2219 else:
2220 raise
2221
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002222 def _InitWorkTree(self):
2223 dotgit = os.path.join(self.worktree, '.git')
2224 if not os.path.exists(dotgit):
2225 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002226 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2227 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002228
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002229 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002230
2231 cmd = ['read-tree', '--reset', '-u']
2232 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002233 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002234 if GitCommand(self, cmd).Wait() != 0:
2235 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002236
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002237 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002238
2239 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002240 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002241
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002242 def _revlist(self, *args, **kw):
2243 a = []
2244 a.extend(args)
2245 a.append('--')
2246 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002247
2248 @property
2249 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002250 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002251
Julien Camperguedd654222014-01-09 16:21:37 +01002252 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2253 """Get logs between two revisions of this project."""
2254 comp = '..'
2255 if rev1:
2256 revs = [rev1]
2257 if rev2:
2258 revs.extend([comp, rev2])
2259 cmd = ['log', ''.join(revs)]
2260 out = DiffColoring(self.config)
2261 if out.is_on and color:
2262 cmd.append('--color')
2263 if oneline:
2264 cmd.append('--oneline')
2265
2266 try:
2267 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2268 if log.Wait() == 0:
2269 return log.stdout
2270 except GitError:
2271 # worktree may not exist if groups changed for example. In that case,
2272 # try in gitdir instead.
2273 if not os.path.exists(self.worktree):
2274 return self.bare_git.log(*cmd[1:])
2275 else:
2276 raise
2277 return None
2278
2279 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2280 """Get the list of logs from this revision to given revisionId"""
2281 logs = {}
2282 selfId = self.GetRevisionId(self._allrefs)
2283 toId = toProject.GetRevisionId(toProject._allrefs)
2284
2285 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2286 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2287 return logs
2288
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002289 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002290 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002291 self._project = project
2292 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002293 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002294
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002295 def LsOthers(self):
2296 p = GitCommand(self._project,
2297 ['ls-files',
2298 '-z',
2299 '--others',
2300 '--exclude-standard'],
2301 bare = False,
David James8d201162013-10-11 17:03:19 -07002302 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002303 capture_stdout = True,
2304 capture_stderr = True)
2305 if p.Wait() == 0:
2306 out = p.stdout
2307 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002308 return out[:-1].split('\0') # pylint: disable=W1401
2309 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002310 return []
2311
2312 def DiffZ(self, name, *args):
2313 cmd = [name]
2314 cmd.append('-z')
2315 cmd.extend(args)
2316 p = GitCommand(self._project,
2317 cmd,
David James8d201162013-10-11 17:03:19 -07002318 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002319 bare = False,
2320 capture_stdout = True,
2321 capture_stderr = True)
2322 try:
2323 out = p.process.stdout.read()
2324 r = {}
2325 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002326 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002327 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002328 try:
2329 info = out.next()
2330 path = out.next()
2331 except StopIteration:
2332 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002333
2334 class _Info(object):
2335 def __init__(self, path, omode, nmode, oid, nid, state):
2336 self.path = path
2337 self.src_path = None
2338 self.old_mode = omode
2339 self.new_mode = nmode
2340 self.old_id = oid
2341 self.new_id = nid
2342
2343 if len(state) == 1:
2344 self.status = state
2345 self.level = None
2346 else:
2347 self.status = state[:1]
2348 self.level = state[1:]
2349 while self.level.startswith('0'):
2350 self.level = self.level[1:]
2351
2352 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002353 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002354 if info.status in ('R', 'C'):
2355 info.src_path = info.path
2356 info.path = out.next()
2357 r[info.path] = info
2358 return r
2359 finally:
2360 p.Wait()
2361
2362 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002363 if self._bare:
2364 path = os.path.join(self._project.gitdir, HEAD)
2365 else:
2366 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002367 try:
2368 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002369 except IOError as e:
2370 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002371 try:
2372 line = fd.read()
2373 finally:
2374 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302375 try:
2376 line = line.decode()
2377 except AttributeError:
2378 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002379 if line.startswith('ref: '):
2380 return line[5:-1]
2381 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002382
2383 def SetHead(self, ref, message=None):
2384 cmdv = []
2385 if message is not None:
2386 cmdv.extend(['-m', message])
2387 cmdv.append(HEAD)
2388 cmdv.append(ref)
2389 self.symbolic_ref(*cmdv)
2390
2391 def DetachHead(self, new, message=None):
2392 cmdv = ['--no-deref']
2393 if message is not None:
2394 cmdv.extend(['-m', message])
2395 cmdv.append(HEAD)
2396 cmdv.append(new)
2397 self.update_ref(*cmdv)
2398
2399 def UpdateRef(self, name, new, old=None,
2400 message=None,
2401 detach=False):
2402 cmdv = []
2403 if message is not None:
2404 cmdv.extend(['-m', message])
2405 if detach:
2406 cmdv.append('--no-deref')
2407 cmdv.append(name)
2408 cmdv.append(new)
2409 if old is not None:
2410 cmdv.append(old)
2411 self.update_ref(*cmdv)
2412
2413 def DeleteRef(self, name, old=None):
2414 if not old:
2415 old = self.rev_parse(name)
2416 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002417 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002418
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002419 def rev_list(self, *args, **kw):
2420 if 'format' in kw:
2421 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2422 else:
2423 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002424 cmdv.extend(args)
2425 p = GitCommand(self._project,
2426 cmdv,
2427 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002428 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002429 capture_stdout = True,
2430 capture_stderr = True)
2431 r = []
2432 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002433 if line[-1] == '\n':
2434 line = line[:-1]
2435 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002436 if p.Wait() != 0:
2437 raise GitError('%s rev-list %s: %s' % (
2438 self._project.name,
2439 str(args),
2440 p.stderr))
2441 return r
2442
2443 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002444 """Allow arbitrary git commands using pythonic syntax.
2445
2446 This allows you to do things like:
2447 git_obj.rev_parse('HEAD')
2448
2449 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2450 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002451 Any other positional arguments will be passed to the git command, and the
2452 following keyword arguments are supported:
2453 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002454
2455 Args:
2456 name: The name of the git command to call. Any '_' characters will
2457 be replaced with '-'.
2458
2459 Returns:
2460 A callable object that will try to call git with the named command.
2461 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002462 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002463 def runner(*args, **kwargs):
2464 cmdv = []
2465 config = kwargs.pop('config', None)
2466 for k in kwargs:
2467 raise TypeError('%s() got an unexpected keyword argument %r'
2468 % (name, k))
2469 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002470 if not git_require((1, 7, 2)):
2471 raise ValueError('cannot set config on command line for %s()'
2472 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302473 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002474 cmdv.append('-c')
2475 cmdv.append('%s=%s' % (k, v))
2476 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002477 cmdv.extend(args)
2478 p = GitCommand(self._project,
2479 cmdv,
2480 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002481 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002482 capture_stdout = True,
2483 capture_stderr = True)
2484 if p.Wait() != 0:
2485 raise GitError('%s %s: %s' % (
2486 self._project.name,
2487 name,
2488 p.stderr))
2489 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302490 try:
Conley Owensedd01512013-09-26 12:59:58 -07002491 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302492 except AttributeError:
2493 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002494 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2495 return r[:-1]
2496 return r
2497 return runner
2498
2499
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002500class _PriorSyncFailedError(Exception):
2501 def __str__(self):
2502 return 'prior sync failed; rebase still in progress'
2503
2504class _DirtyError(Exception):
2505 def __str__(self):
2506 return 'contains uncommitted changes'
2507
2508class _InfoMessage(object):
2509 def __init__(self, project, text):
2510 self.project = project
2511 self.text = text
2512
2513 def Print(self, syncbuf):
2514 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2515 syncbuf.out.nl()
2516
2517class _Failure(object):
2518 def __init__(self, project, why):
2519 self.project = project
2520 self.why = why
2521
2522 def Print(self, syncbuf):
2523 syncbuf.out.fail('error: %s/: %s',
2524 self.project.relpath,
2525 str(self.why))
2526 syncbuf.out.nl()
2527
2528class _Later(object):
2529 def __init__(self, project, action):
2530 self.project = project
2531 self.action = action
2532
2533 def Run(self, syncbuf):
2534 out = syncbuf.out
2535 out.project('project %s/', self.project.relpath)
2536 out.nl()
2537 try:
2538 self.action()
2539 out.nl()
2540 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002541 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002542 out.nl()
2543 return False
2544
2545class _SyncColoring(Coloring):
2546 def __init__(self, config):
2547 Coloring.__init__(self, config, 'reposync')
2548 self.project = self.printer('header', attr = 'bold')
2549 self.info = self.printer('info')
2550 self.fail = self.printer('fail', fg='red')
2551
2552class SyncBuffer(object):
2553 def __init__(self, config, detach_head=False):
2554 self._messages = []
2555 self._failures = []
2556 self._later_queue1 = []
2557 self._later_queue2 = []
2558
2559 self.out = _SyncColoring(config)
2560 self.out.redirect(sys.stderr)
2561
2562 self.detach_head = detach_head
2563 self.clean = True
2564
2565 def info(self, project, fmt, *args):
2566 self._messages.append(_InfoMessage(project, fmt % args))
2567
2568 def fail(self, project, err=None):
2569 self._failures.append(_Failure(project, err))
2570 self.clean = False
2571
2572 def later1(self, project, what):
2573 self._later_queue1.append(_Later(project, what))
2574
2575 def later2(self, project, what):
2576 self._later_queue2.append(_Later(project, what))
2577
2578 def Finish(self):
2579 self._PrintMessages()
2580 self._RunLater()
2581 self._PrintMessages()
2582 return self.clean
2583
2584 def _RunLater(self):
2585 for q in ['_later_queue1', '_later_queue2']:
2586 if not self._RunQueue(q):
2587 return
2588
2589 def _RunQueue(self, queue):
2590 for m in getattr(self, queue):
2591 if not m.Run(self):
2592 self.clean = False
2593 return False
2594 setattr(self, queue, [])
2595 return True
2596
2597 def _PrintMessages(self):
2598 for m in self._messages:
2599 m.Print(self)
2600 for m in self._failures:
2601 m.Print(self)
2602
2603 self._messages = []
2604 self._failures = []
2605
2606
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002607class MetaProject(Project):
2608 """A special project housed under .repo.
2609 """
2610 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002611 Project.__init__(self,
2612 manifest = manifest,
2613 name = name,
2614 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002615 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002616 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002617 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002618 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002619 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002620 revisionId = None,
2621 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002622
2623 def PreSync(self):
2624 if self.Exists:
2625 cb = self.CurrentBranch
2626 if cb:
2627 base = self.GetBranch(cb).merge
2628 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002629 self.revisionExpr = base
2630 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002631
Florian Vallee5d016502012-06-07 17:19:26 +02002632 def MetaBranchSwitch(self, target):
2633 """ Prepare MetaProject for manifest branch switch
2634 """
2635
2636 # detach and delete manifest branch, allowing a new
2637 # branch to take over
2638 syncbuf = SyncBuffer(self.config, detach_head = True)
2639 self.Sync_LocalHalf(syncbuf)
2640 syncbuf.Finish()
2641
2642 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002643 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002644 capture_stdout = True,
2645 capture_stderr = True).Wait() == 0
2646
2647
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002648 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002649 def LastFetch(self):
2650 try:
2651 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2652 return os.path.getmtime(fh)
2653 except OSError:
2654 return 0
2655
2656 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002657 def HasChanges(self):
2658 """Has the remote received new commits not yet checked out?
2659 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002660 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002661 return False
2662
David Pursehouse8a68ff92012-09-24 12:15:13 +09002663 all_refs = self.bare_ref.all
2664 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002665 head = self.work_git.GetHead()
2666 if head.startswith(R_HEADS):
2667 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002668 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002669 except KeyError:
2670 head = None
2671
2672 if revid == head:
2673 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002674 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002675 return True
2676 return False