blob: 127176e54b6c5f38c3ebc258947a493c4de3df8a [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".
Conley Owens971de8e2012-04-16 10:36:08 -0700712
713 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"
Colin Cross5acde752012-03-28 20:15:45 -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)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800985
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800986 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)
1003
1004 if GitCommand(self, cmd, bare = True).Wait() != 0:
1005 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001006
1007 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1008 self.bare_git.UpdateRef(R_PUB + branch.name,
1009 R_HEADS + branch.name,
1010 message = msg)
1011
1012
1013## Sync ##
1014
Julien Campergue335f5ef2013-10-16 11:02:35 +02001015 def _ExtractArchive(self, tarpath, path=None):
1016 """Extract the given tar on its current location
1017
1018 Args:
1019 - tarpath: The path to the actual tar file
1020
1021 """
1022 try:
1023 with tarfile.open(tarpath, 'r') as tar:
1024 tar.extractall(path=path)
1025 return True
1026 except (IOError, tarfile.TarError) as e:
1027 print("error: Cannot extract archive %s: "
1028 "%s" % (tarpath, str(e)), file=sys.stderr)
1029 return False
1030
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001031 def Sync_NetworkHalf(self,
1032 quiet=False,
1033 is_new=None,
1034 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001035 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001036 no_tags=False,
1037 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038 """Perform only the network IO portion of the sync process.
1039 Local working directory/branch state is not affected.
1040 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001041 if archive and not isinstance(self, MetaProject):
1042 if self.remote.url.startswith(('http://', 'https://')):
1043 print("error: %s: Cannot fetch archives from http/https "
1044 "remotes." % self.name, file=sys.stderr)
1045 return False
1046
1047 name = self.relpath.replace('\\', '/')
1048 name = name.replace('/', '_')
1049 tarpath = '%s.tar' % name
1050 topdir = self.manifest.topdir
1051
1052 try:
1053 self._FetchArchive(tarpath, cwd=topdir)
1054 except GitError as e:
1055 print('error: %s' % str(e), file=sys.stderr)
1056 return False
1057
1058 # From now on, we only need absolute tarpath
1059 tarpath = os.path.join(topdir, tarpath)
1060
1061 if not self._ExtractArchive(tarpath, path=topdir):
1062 return False
1063 try:
1064 os.remove(tarpath)
1065 except OSError as e:
1066 print("warn: Cannot remove archive %s: "
1067 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001068 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001069 return True
1070
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001071 if is_new is None:
1072 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001073 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001075 else:
1076 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001078
1079 if is_new:
1080 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1081 try:
1082 fd = open(alt, 'rb')
1083 try:
1084 alt_dir = fd.readline().rstrip()
1085 finally:
1086 fd.close()
1087 except IOError:
1088 alt_dir = None
1089 else:
1090 alt_dir = None
1091
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001092 if clone_bundle \
1093 and alt_dir is None \
1094 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001095 is_new = False
1096
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001097 if not current_branch_only:
1098 if self.sync_c:
1099 current_branch_only = True
1100 elif not self.manifest._loaded:
1101 # Manifest cannot check defaults until it syncs.
1102 current_branch_only = False
1103 elif self.manifest.default.sync_c:
1104 current_branch_only = True
1105
Conley Owens666d5342014-05-01 13:09:57 -07001106 has_sha1 = ID_RE.match(self.revisionExpr) and self._CheckForSha1()
1107 if (not has_sha1 #Need to fetch since we don't already have this revision
1108 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1109 current_branch_only=current_branch_only,
1110 no_tags=no_tags)):
1111 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001112
1113 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001114 self._InitMRef()
1115 else:
1116 self._InitMirrorHead()
1117 try:
1118 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1119 except OSError:
1120 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001122
1123 def PostRepoUpgrade(self):
1124 self._InitHooks()
1125
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001126 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001127 for copyfile in self.copyfiles:
1128 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001129 for linkfile in self.linkfiles:
1130 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001131
Julien Camperguedd654222014-01-09 16:21:37 +01001132 def GetCommitRevisionId(self):
1133 """Get revisionId of a commit.
1134
1135 Use this method instead of GetRevisionId to get the id of the commit rather
1136 than the id of the current git object (for example, a tag)
1137
1138 """
1139 if not self.revisionExpr.startswith(R_TAGS):
1140 return self.GetRevisionId(self._allrefs)
1141
1142 try:
1143 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1144 except GitError:
1145 raise ManifestInvalidRevisionError(
1146 'revision %s in %s not found' % (self.revisionExpr,
1147 self.name))
1148
David Pursehouse8a68ff92012-09-24 12:15:13 +09001149 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001150 if self.revisionId:
1151 return self.revisionId
1152
1153 rem = self.GetRemote(self.remote.name)
1154 rev = rem.ToLocal(self.revisionExpr)
1155
David Pursehouse8a68ff92012-09-24 12:15:13 +09001156 if all_refs is not None and rev in all_refs:
1157 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001158
1159 try:
1160 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1161 except GitError:
1162 raise ManifestInvalidRevisionError(
1163 'revision %s in %s not found' % (self.revisionExpr,
1164 self.name))
1165
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001166 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167 """Perform only the local IO portion of the sync process.
1168 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169 """
David James8d201162013-10-11 17:03:19 -07001170 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001171 all_refs = self.bare_ref.all
1172 self.CleanPublishedCache(all_refs)
1173 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001174
David Pursehouse1d947b32012-10-25 12:23:11 +09001175 def _doff():
1176 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001177 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001178
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001179 head = self.work_git.GetHead()
1180 if head.startswith(R_HEADS):
1181 branch = head[len(R_HEADS):]
1182 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001183 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001184 except KeyError:
1185 head = None
1186 else:
1187 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001188
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001189 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190 # Currently on a detached HEAD. The user is assumed to
1191 # not have any local modifications worth worrying about.
1192 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001193 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001194 syncbuf.fail(self, _PriorSyncFailedError())
1195 return
1196
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001197 if head == revid:
1198 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001199 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001200 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001201 if not syncbuf.detach_head:
1202 return
1203 else:
1204 lost = self._revlist(not_rev(revid), HEAD)
1205 if lost:
1206 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001207
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001208 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001209 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001210 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001211 syncbuf.fail(self, e)
1212 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001213 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001214 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001215
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001216 if head == revid:
1217 # No changes; don't do anything further.
1218 #
1219 return
1220
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001221 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001222
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001223 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001225 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001226 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001227 syncbuf.info(self,
1228 "leaving %s; does not track upstream",
1229 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001230 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001231 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001232 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001233 syncbuf.fail(self, e)
1234 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001235 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001236 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001237
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001238 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001239 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001240 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001241 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242 if not_merged:
1243 if upstream_gain:
1244 # The user has published this branch and some of those
1245 # commits are not yet merged upstream. We do not want
1246 # to rewrite the published commits so we punt.
1247 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001248 syncbuf.fail(self,
1249 "branch %s is published (but not merged) and is now %d commits behind"
1250 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001251 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001252 elif pub == head:
1253 # All published commits are merged, and thus we are a
1254 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001255 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001256 syncbuf.later1(self, _doff)
1257 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001259 # Examine the local commits not in the remote. Find the
1260 # last one attributed to this user, if any.
1261 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001262 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001263 last_mine = None
1264 cnt_mine = 0
1265 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301266 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001267 if committer_email == self.UserEmail:
1268 last_mine = commit_id
1269 cnt_mine += 1
1270
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001271 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001272 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001273
1274 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001275 syncbuf.fail(self, _DirtyError())
1276 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001277
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001278 # If the upstream switched on us, warn the user.
1279 #
1280 if branch.merge != self.revisionExpr:
1281 if branch.merge and self.revisionExpr:
1282 syncbuf.info(self,
1283 'manifest switched %s...%s',
1284 branch.merge,
1285 self.revisionExpr)
1286 elif branch.merge:
1287 syncbuf.info(self,
1288 'manifest no longer tracks %s',
1289 branch.merge)
1290
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001291 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001293 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001295 syncbuf.info(self,
1296 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001297 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001299 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001300 if not ID_RE.match(self.revisionExpr):
1301 # in case of manifest sync the revisionExpr might be a SHA1
1302 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303 branch.Save()
1304
Mike Pontillod3153822012-02-28 11:53:24 -08001305 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001306 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001307 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001308 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001309 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001310 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001312 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001313 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001314 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001315 syncbuf.fail(self, e)
1316 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001318 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001320 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 # dest should already be an absolute path, but src is project relative
1322 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001323 abssrc = os.path.join(self.worktree, src)
1324 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001325
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001326 def AddLinkFile(self, src, dest, absdest):
1327 # dest should already be an absolute path, but src is project relative
1328 # make src an absolute path
1329 abssrc = os.path.join(self.worktree, src)
1330 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1331
James W. Mills24c13082012-04-12 15:04:13 -05001332 def AddAnnotation(self, name, value, keep):
1333 self.annotations.append(_Annotation(name, value, keep))
1334
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001335 def DownloadPatchSet(self, change_id, patch_id):
1336 """Download a single patch set of a single change to FETCH_HEAD.
1337 """
1338 remote = self.GetRemote(self.remote.name)
1339
1340 cmd = ['fetch', remote.name]
1341 cmd.append('refs/changes/%2.2d/%d/%d' \
1342 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001343 if GitCommand(self, cmd, bare=True).Wait() != 0:
1344 return None
1345 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001346 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001347 change_id,
1348 patch_id,
1349 self.bare_git.rev_parse('FETCH_HEAD'))
1350
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001351
1352## Branch Management ##
1353
1354 def StartBranch(self, name):
1355 """Create a new branch off the manifest's revision.
1356 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001357 head = self.work_git.GetHead()
1358 if head == (R_HEADS + name):
1359 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001360
David Pursehouse8a68ff92012-09-24 12:15:13 +09001361 all_refs = self.bare_ref.all
1362 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001363 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001364 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001365 capture_stdout = True,
1366 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001367
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001368 branch = self.GetBranch(name)
1369 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001370 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001371 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001372
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001373 if head.startswith(R_HEADS):
1374 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001375 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001376 except KeyError:
1377 head = None
1378
1379 if revid and head and revid == head:
1380 ref = os.path.join(self.gitdir, R_HEADS + name)
1381 try:
1382 os.makedirs(os.path.dirname(ref))
1383 except OSError:
1384 pass
1385 _lwrite(ref, '%s\n' % revid)
1386 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1387 'ref: %s%s\n' % (R_HEADS, name))
1388 branch.Save()
1389 return True
1390
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001391 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001392 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001393 capture_stdout = True,
1394 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001395 branch.Save()
1396 return True
1397 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398
Wink Saville02d79452009-04-10 13:01:24 -07001399 def CheckoutBranch(self, name):
1400 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001401
1402 Args:
1403 name: The name of the branch to checkout.
1404
1405 Returns:
1406 True if the checkout succeeded; False if it didn't; None if the branch
1407 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001408 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001409 rev = R_HEADS + name
1410 head = self.work_git.GetHead()
1411 if head == rev:
1412 # Already on the branch
1413 #
1414 return True
Wink Saville02d79452009-04-10 13:01:24 -07001415
David Pursehouse8a68ff92012-09-24 12:15:13 +09001416 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001417 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001418 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001419 except KeyError:
1420 # Branch does not exist in this project
1421 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001422 return None
Wink Saville02d79452009-04-10 13:01:24 -07001423
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001424 if head.startswith(R_HEADS):
1425 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001426 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001427 except KeyError:
1428 head = None
1429
1430 if head == revid:
1431 # Same revision; just update HEAD to point to the new
1432 # target branch, but otherwise take no other action.
1433 #
1434 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1435 'ref: %s%s\n' % (R_HEADS, name))
1436 return True
1437
1438 return GitCommand(self,
1439 ['checkout', name, '--'],
1440 capture_stdout = True,
1441 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001442
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001443 def AbandonBranch(self, name):
1444 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001445
1446 Args:
1447 name: The name of the branch to abandon.
1448
1449 Returns:
1450 True if the abandon succeeded; False if it didn't; None if the branch
1451 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001452 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001453 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001454 all_refs = self.bare_ref.all
1455 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001456 # Doesn't exist
1457 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001458
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001459 head = self.work_git.GetHead()
1460 if head == rev:
1461 # We can't destroy the branch while we are sitting
1462 # on it. Switch to a detached HEAD.
1463 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001464 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001465
David Pursehouse8a68ff92012-09-24 12:15:13 +09001466 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001467 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001468 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1469 '%s\n' % revid)
1470 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001471 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001472
1473 return GitCommand(self,
1474 ['branch', '-D', name],
1475 capture_stdout = True,
1476 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001477
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001478 def PruneHeads(self):
1479 """Prune any topic branches already merged into upstream.
1480 """
1481 cb = self.CurrentBranch
1482 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001483 left = self._allrefs
1484 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001485 if name.startswith(R_HEADS):
1486 name = name[len(R_HEADS):]
1487 if cb is None or name != cb:
1488 kill.append(name)
1489
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001490 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 if cb is not None \
1492 and not self._revlist(HEAD + '...' + rev) \
1493 and not self.IsDirty(consider_untracked = False):
1494 self.work_git.DetachHead(HEAD)
1495 kill.append(cb)
1496
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001497 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001498 old = self.bare_git.GetHead()
1499 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001500 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1501
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001502 try:
1503 self.bare_git.DetachHead(rev)
1504
1505 b = ['branch', '-d']
1506 b.extend(kill)
1507 b = GitCommand(self, b, bare=True,
1508 capture_stdout=True,
1509 capture_stderr=True)
1510 b.Wait()
1511 finally:
1512 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001513 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001514
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001515 for branch in kill:
1516 if (R_HEADS + branch) not in left:
1517 self.CleanPublishedCache()
1518 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001519
1520 if cb and cb not in kill:
1521 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001522 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001523
1524 kept = []
1525 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001526 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001527 branch = self.GetBranch(branch)
1528 base = branch.LocalMerge
1529 if not base:
1530 base = rev
1531 kept.append(ReviewableBranch(self, branch, base))
1532 return kept
1533
1534
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001535## Submodule Management ##
1536
1537 def GetRegisteredSubprojects(self):
1538 result = []
1539 def rec(subprojects):
1540 if not subprojects:
1541 return
1542 result.extend(subprojects)
1543 for p in subprojects:
1544 rec(p.subprojects)
1545 rec(self.subprojects)
1546 return result
1547
1548 def _GetSubmodules(self):
1549 # Unfortunately we cannot call `git submodule status --recursive` here
1550 # because the working tree might not exist yet, and it cannot be used
1551 # without a working tree in its current implementation.
1552
1553 def get_submodules(gitdir, rev):
1554 # Parse .gitmodules for submodule sub_paths and sub_urls
1555 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1556 if not sub_paths:
1557 return []
1558 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1559 # revision of submodule repository
1560 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1561 submodules = []
1562 for sub_path, sub_url in zip(sub_paths, sub_urls):
1563 try:
1564 sub_rev = sub_revs[sub_path]
1565 except KeyError:
1566 # Ignore non-exist submodules
1567 continue
1568 submodules.append((sub_rev, sub_path, sub_url))
1569 return submodules
1570
1571 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1572 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1573 def parse_gitmodules(gitdir, rev):
1574 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1575 try:
1576 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1577 bare = True, gitdir = gitdir)
1578 except GitError:
1579 return [], []
1580 if p.Wait() != 0:
1581 return [], []
1582
1583 gitmodules_lines = []
1584 fd, temp_gitmodules_path = tempfile.mkstemp()
1585 try:
1586 os.write(fd, p.stdout)
1587 os.close(fd)
1588 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1589 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1590 bare = True, gitdir = gitdir)
1591 if p.Wait() != 0:
1592 return [], []
1593 gitmodules_lines = p.stdout.split('\n')
1594 except GitError:
1595 return [], []
1596 finally:
1597 os.remove(temp_gitmodules_path)
1598
1599 names = set()
1600 paths = {}
1601 urls = {}
1602 for line in gitmodules_lines:
1603 if not line:
1604 continue
1605 m = re_path.match(line)
1606 if m:
1607 names.add(m.group(1))
1608 paths[m.group(1)] = m.group(2)
1609 continue
1610 m = re_url.match(line)
1611 if m:
1612 names.add(m.group(1))
1613 urls[m.group(1)] = m.group(2)
1614 continue
1615 names = sorted(names)
1616 return ([paths.get(name, '') for name in names],
1617 [urls.get(name, '') for name in names])
1618
1619 def git_ls_tree(gitdir, rev, paths):
1620 cmd = ['ls-tree', rev, '--']
1621 cmd.extend(paths)
1622 try:
1623 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1624 bare = True, gitdir = gitdir)
1625 except GitError:
1626 return []
1627 if p.Wait() != 0:
1628 return []
1629 objects = {}
1630 for line in p.stdout.split('\n'):
1631 if not line.strip():
1632 continue
1633 object_rev, object_path = line.split()[2:4]
1634 objects[object_path] = object_rev
1635 return objects
1636
1637 try:
1638 rev = self.GetRevisionId()
1639 except GitError:
1640 return []
1641 return get_submodules(self.gitdir, rev)
1642
1643 def GetDerivedSubprojects(self):
1644 result = []
1645 if not self.Exists:
1646 # If git repo does not exist yet, querying its submodules will
1647 # mess up its states; so return here.
1648 return result
1649 for rev, path, url in self._GetSubmodules():
1650 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001651 relpath, worktree, gitdir, objdir = \
1652 self.manifest.GetSubprojectPaths(self, name, path)
1653 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001654 if project:
1655 result.extend(project.GetDerivedSubprojects())
1656 continue
David James8d201162013-10-11 17:03:19 -07001657
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001658 remote = RemoteSpec(self.remote.name,
1659 url = url,
1660 review = self.remote.review)
1661 subproject = Project(manifest = self.manifest,
1662 name = name,
1663 remote = remote,
1664 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001665 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001666 worktree = worktree,
1667 relpath = relpath,
1668 revisionExpr = self.revisionExpr,
1669 revisionId = rev,
1670 rebase = self.rebase,
1671 groups = self.groups,
1672 sync_c = self.sync_c,
1673 sync_s = self.sync_s,
1674 parent = self,
1675 is_derived = True)
1676 result.append(subproject)
1677 result.extend(subproject.GetDerivedSubprojects())
1678 return result
1679
1680
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001681## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001682 def _CheckForSha1(self):
1683 try:
1684 # if revision (sha or tag) is not present then following function
1685 # throws an error.
1686 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1687 return True
1688 except GitError:
1689 # There is no such persistent revision. We have to fetch it.
1690 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691
Julien Campergue335f5ef2013-10-16 11:02:35 +02001692 def _FetchArchive(self, tarpath, cwd=None):
1693 cmd = ['archive', '-v', '-o', tarpath]
1694 cmd.append('--remote=%s' % self.remote.url)
1695 cmd.append('--prefix=%s/' % self.relpath)
1696 cmd.append(self.revisionExpr)
1697
1698 command = GitCommand(self, cmd, cwd=cwd,
1699 capture_stdout=True,
1700 capture_stderr=True)
1701
1702 if command.Wait() != 0:
1703 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1704
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001705 def _RemoteFetch(self, name=None,
1706 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001707 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001708 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001709 alt_dir=None,
1710 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001711
1712 is_sha1 = False
1713 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001714 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001715
David Pursehouse9bc422f2014-04-15 10:28:56 +09001716 # The depth should not be used when fetching to a mirror because
1717 # it will result in a shallow repository that cannot be cloned or
1718 # fetched from.
1719 if not self.manifest.IsMirror:
1720 if self.clone_depth:
1721 depth = self.clone_depth
1722 else:
1723 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1724
Shawn Pearce69e04d82014-01-29 12:48:54 -08001725 if depth:
1726 current_branch_only = True
1727
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001728 if current_branch_only:
1729 if ID_RE.match(self.revisionExpr) is not None:
1730 is_sha1 = True
1731 elif self.revisionExpr.startswith(R_TAGS):
1732 # this is a tag and its sha1 value should never change
1733 tag_name = self.revisionExpr[len(R_TAGS):]
1734
1735 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001736 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001737 return True
Brian Harring14a66742012-09-28 20:21:57 -07001738 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1739 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001740
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001741 if not name:
1742 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001743
1744 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001745 remote = self.GetRemote(name)
1746 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001747 ssh_proxy = True
1748
Shawn O. Pearce88443382010-10-08 10:02:09 +02001749 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001750 if alt_dir and 'objects' == os.path.basename(alt_dir):
1751 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001752 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1753 remote = self.GetRemote(name)
1754
David Pursehouse8a68ff92012-09-24 12:15:13 +09001755 all_refs = self.bare_ref.all
1756 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001757 tmp = set()
1758
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301759 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001760 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001761 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001762 all_refs[r] = ref_id
1763 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001764 continue
1765
David Pursehouse8a68ff92012-09-24 12:15:13 +09001766 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001767 continue
1768
David Pursehouse8a68ff92012-09-24 12:15:13 +09001769 r = 'refs/_alt/%s' % ref_id
1770 all_refs[r] = ref_id
1771 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001772 tmp.add(r)
1773
Shawn O. Pearce88443382010-10-08 10:02:09 +02001774 tmp_packed = ''
1775 old_packed = ''
1776
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301777 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001778 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001779 tmp_packed += line
1780 if r not in tmp:
1781 old_packed += line
1782
1783 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001784 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001785 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001786
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001787 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001788
1789 # The --depth option only affects the initial fetch; after that we'll do
1790 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001791 if depth and initial:
1792 cmd.append('--depth=%s' % depth)
1793
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001794 if quiet:
1795 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001796 if not self.worktree:
1797 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001798 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001799
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001800 # If using depth then we should not get all the tags since they may
1801 # be outside of the depth.
1802 if no_tags or depth:
1803 cmd.append('--no-tags')
1804 else:
1805 cmd.append('--tags')
1806
Brian Harring14a66742012-09-28 20:21:57 -07001807 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001808 # Fetch whole repo
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301809 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001810 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001811 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001812 cmd.append(tag_name)
1813 else:
1814 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001815 if is_sha1:
1816 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001817 if branch.startswith(R_HEADS):
1818 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301819 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001820
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001821 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001822 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001823 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1824 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001825 ok = True
1826 break
Brian Harring14a66742012-09-28 20:21:57 -07001827 elif current_branch_only and is_sha1 and ret == 128:
1828 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1829 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1830 # abort the optimization attempt and do a full sync.
1831 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001832 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001833
1834 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001835 # Ensure that some refs exist. Otherwise, we probably aren't looking
1836 # at a real git repository and may have a bad url.
1837 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001838 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001839
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001840 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001841 if old_packed != '':
1842 _lwrite(packed_refs, old_packed)
1843 else:
1844 os.remove(packed_refs)
1845 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001846
1847 if is_sha1 and current_branch_only and self.upstream:
1848 # We just synced the upstream given branch; verify we
1849 # got what we wanted, else trigger a second run of all
1850 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001851 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001852 return self._RemoteFetch(name=name, current_branch_only=False,
1853 initial=False, quiet=quiet, alt_dir=alt_dir)
1854
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001855 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001856
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001857 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001858 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001859 return False
1860
1861 remote = self.GetRemote(self.remote.name)
1862 bundle_url = remote.url + '/clone.bundle'
1863 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001864 if GetSchemeFromUrl(bundle_url) not in (
1865 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001866 return False
1867
1868 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1869 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1870
1871 exist_dst = os.path.exists(bundle_dst)
1872 exist_tmp = os.path.exists(bundle_tmp)
1873
1874 if not initial and not exist_dst and not exist_tmp:
1875 return False
1876
1877 if not exist_dst:
1878 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1879 if not exist_dst:
1880 return False
1881
1882 cmd = ['fetch']
1883 if quiet:
1884 cmd.append('--quiet')
1885 if not self.worktree:
1886 cmd.append('--update-head-ok')
1887 cmd.append(bundle_dst)
1888 for f in remote.fetch:
1889 cmd.append(str(f))
1890 cmd.append('refs/tags/*:refs/tags/*')
1891
1892 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001893 if os.path.exists(bundle_dst):
1894 os.remove(bundle_dst)
1895 if os.path.exists(bundle_tmp):
1896 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001897 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001898
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001899 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001900 if os.path.exists(dstPath):
1901 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001902
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001903 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001904 if quiet:
1905 cmd += ['--silent']
1906 if os.path.exists(tmpPath):
1907 size = os.stat(tmpPath).st_size
1908 if size >= 1024:
1909 cmd += ['--continue-at', '%d' % (size,)]
1910 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001911 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001912 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1913 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001914 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001915 if cookiefile:
1916 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001917 if srcUrl.startswith('persistent-'):
1918 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001919 cmd += [srcUrl]
1920
1921 if IsTrace():
1922 Trace('%s', ' '.join(cmd))
1923 try:
1924 proc = subprocess.Popen(cmd)
1925 except OSError:
1926 return False
1927
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001928 curlret = proc.wait()
1929
1930 if curlret == 22:
1931 # From curl man page:
1932 # 22: HTTP page not retrieved. The requested url was not found or
1933 # returned another error with the HTTP error code being 400 or above.
1934 # This return code only appears if -f, --fail is used.
1935 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001936 print("Server does not provide clone.bundle; ignoring.",
1937 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001938 return False
1939
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001940 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001941 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001942 os.rename(tmpPath, dstPath)
1943 return True
1944 else:
1945 os.remove(tmpPath)
1946 return False
1947 else:
1948 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001949
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001950 def _IsValidBundle(self, path):
1951 try:
1952 with open(path) as f:
1953 if f.read(16) == '# v2 git bundle\n':
1954 return True
1955 else:
1956 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1957 return False
1958 except OSError:
1959 return False
1960
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001961 def _GetBundleCookieFile(self, url):
1962 if url.startswith('persistent-'):
1963 try:
1964 p = subprocess.Popen(
1965 ['git-remote-persistent-https', '-print_config', url],
1966 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1967 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001968 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001969 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001970 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001971 for line in p.stdout:
1972 line = line.strip()
1973 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001974 cookiefile = line[len(prefix):]
1975 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001976 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001977 err_msg = p.stderr.read()
1978 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001979 pass # Persistent proxy doesn't support -print_config.
1980 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001981 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001982 if cookiefile:
1983 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001984 except OSError as e:
1985 if e.errno == errno.ENOENT:
1986 pass # No persistent proxy.
1987 raise
1988 return GitConfig.ForUser().GetString('http.cookiefile')
1989
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001990 def _Checkout(self, rev, quiet=False):
1991 cmd = ['checkout']
1992 if quiet:
1993 cmd.append('-q')
1994 cmd.append(rev)
1995 cmd.append('--')
1996 if GitCommand(self, cmd).Wait() != 0:
1997 if self._allrefs:
1998 raise GitError('%s checkout %s ' % (self.name, rev))
1999
Pierre Tardye5a21222011-03-24 16:28:18 +01002000 def _CherryPick(self, rev, quiet=False):
2001 cmd = ['cherry-pick']
2002 cmd.append(rev)
2003 cmd.append('--')
2004 if GitCommand(self, cmd).Wait() != 0:
2005 if self._allrefs:
2006 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2007
Erwan Mahea94f1622011-08-19 13:56:09 +02002008 def _Revert(self, rev, quiet=False):
2009 cmd = ['revert']
2010 cmd.append('--no-edit')
2011 cmd.append(rev)
2012 cmd.append('--')
2013 if GitCommand(self, cmd).Wait() != 0:
2014 if self._allrefs:
2015 raise GitError('%s revert %s ' % (self.name, rev))
2016
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002017 def _ResetHard(self, rev, quiet=True):
2018 cmd = ['reset', '--hard']
2019 if quiet:
2020 cmd.append('-q')
2021 cmd.append(rev)
2022 if GitCommand(self, cmd).Wait() != 0:
2023 raise GitError('%s reset --hard %s ' % (self.name, rev))
2024
2025 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002026 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002027 if onto is not None:
2028 cmd.extend(['--onto', onto])
2029 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002030 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002031 raise GitError('%s rebase %s ' % (self.name, upstream))
2032
Pierre Tardy3d125942012-05-04 12:18:12 +02002033 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002034 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002035 if ffonly:
2036 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002037 if GitCommand(self, cmd).Wait() != 0:
2038 raise GitError('%s merge %s ' % (self.name, head))
2039
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002040 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002041 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002042
2043 # Initialize the bare repository, which contains all of the objects.
2044 if not os.path.exists(self.objdir):
2045 os.makedirs(self.objdir)
2046 self.bare_objdir.init()
2047
2048 # If we have a separate directory to hold refs, initialize it as well.
2049 if self.objdir != self.gitdir:
2050 os.makedirs(self.gitdir)
2051 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2052 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002053
Shawn O. Pearce88443382010-10-08 10:02:09 +02002054 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002055 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002056
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002057 if ref_dir or mirror_git:
2058 if not mirror_git:
2059 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002060 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2061 self.relpath + '.git')
2062
2063 if os.path.exists(mirror_git):
2064 ref_dir = mirror_git
2065
2066 elif os.path.exists(repo_git):
2067 ref_dir = repo_git
2068
2069 else:
2070 ref_dir = None
2071
2072 if ref_dir:
2073 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2074 os.path.join(ref_dir, 'objects') + '\n')
2075
Jimmie Westera0444582012-10-24 13:44:42 +02002076 self._UpdateHooks()
2077
2078 m = self.manifest.manifestProject.config
2079 for key in ['user.name', 'user.email']:
2080 if m.Has(key, include_defaults = False):
2081 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002082 if self.manifest.IsMirror:
2083 self.config.SetString('core.bare', 'true')
2084 else:
2085 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002086
Jimmie Westera0444582012-10-24 13:44:42 +02002087 def _UpdateHooks(self):
2088 if os.path.exists(self.gitdir):
2089 # Always recreate hooks since they can have been changed
2090 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002091 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002092 try:
2093 to_rm = os.listdir(hooks)
2094 except OSError:
2095 to_rm = []
2096 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002097 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002098 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002099
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002100 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002101 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002102 if not os.path.exists(hooks):
2103 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002104 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002105 name = os.path.basename(stock_hook)
2106
Victor Boivie65e0f352011-04-18 11:23:29 +02002107 if name in ('commit-msg',) and not self.remote.review \
2108 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002109 # Don't install a Gerrit Code Review hook if this
2110 # project does not appear to use it for reviews.
2111 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002112 # Since the manifest project is one of those, but also
2113 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002114 continue
2115
2116 dst = os.path.join(hooks, name)
2117 if os.path.islink(dst):
2118 continue
2119 if os.path.exists(dst):
2120 if filecmp.cmp(stock_hook, dst, shallow=False):
2121 os.remove(dst)
2122 else:
2123 _error("%s: Not replacing %s hook", self.relpath, name)
2124 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002125 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002126 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002127 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002128 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002129 raise GitError('filesystem must support symlinks')
2130 else:
2131 raise
2132
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002133 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002134 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002135 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002136 remote.url = self.remote.url
2137 remote.review = self.remote.review
2138 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002139
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002140 if self.worktree:
2141 remote.ResetFetch(mirror=False)
2142 else:
2143 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002144 remote.Save()
2145
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002146 def _InitMRef(self):
2147 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002148 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002149
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002150 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002151 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002152
2153 def _InitAnyMRef(self, ref):
2154 cur = self.bare_ref.symref(ref)
2155
2156 if self.revisionId:
2157 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2158 msg = 'manifest set to %s' % self.revisionId
2159 dst = self.revisionId + '^0'
2160 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2161 else:
2162 remote = self.GetRemote(self.remote.name)
2163 dst = remote.ToLocal(self.revisionExpr)
2164 if cur != dst:
2165 msg = 'manifest set to %s' % self.revisionExpr
2166 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002167
David James8d201162013-10-11 17:03:19 -07002168 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2169 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2170
2171 Args:
2172 gitdir: The bare git repository. Must already be initialized.
2173 dotgit: The repository you would like to initialize.
2174 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2175 Only one work tree can store refs under a given |gitdir|.
2176 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2177 This saves you the effort of initializing |dotgit| yourself.
2178 """
2179 # These objects can be shared between several working trees.
2180 symlink_files = ['description', 'info']
2181 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2182 if share_refs:
2183 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002184 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002185 symlink_dirs += ['logs', 'refs']
2186 to_symlink = symlink_files + symlink_dirs
2187
2188 to_copy = []
2189 if copy_all:
2190 to_copy = os.listdir(gitdir)
2191
2192 for name in set(to_copy).union(to_symlink):
2193 try:
2194 src = os.path.realpath(os.path.join(gitdir, name))
2195 dst = os.path.realpath(os.path.join(dotgit, name))
2196
2197 if os.path.lexists(dst) and not os.path.islink(dst):
2198 raise GitError('cannot overwrite a local work tree')
2199
2200 # If the source dir doesn't exist, create an empty dir.
2201 if name in symlink_dirs and not os.path.lexists(src):
2202 os.makedirs(src)
2203
2204 if name in to_symlink:
2205 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2206 elif copy_all and not os.path.islink(dst):
2207 if os.path.isdir(src):
2208 shutil.copytree(src, dst)
2209 elif os.path.isfile(src):
2210 shutil.copy(src, dst)
2211 except OSError as e:
2212 if e.errno == errno.EPERM:
2213 raise GitError('filesystem must support symlinks')
2214 else:
2215 raise
2216
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002217 def _InitWorkTree(self):
2218 dotgit = os.path.join(self.worktree, '.git')
2219 if not os.path.exists(dotgit):
2220 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002221 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2222 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002223
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002224 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002225
2226 cmd = ['read-tree', '--reset', '-u']
2227 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002228 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002229 if GitCommand(self, cmd).Wait() != 0:
2230 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002231
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002232 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002233
2234 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002235 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002236
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002237 def _revlist(self, *args, **kw):
2238 a = []
2239 a.extend(args)
2240 a.append('--')
2241 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002242
2243 @property
2244 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002245 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002246
Julien Camperguedd654222014-01-09 16:21:37 +01002247 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2248 """Get logs between two revisions of this project."""
2249 comp = '..'
2250 if rev1:
2251 revs = [rev1]
2252 if rev2:
2253 revs.extend([comp, rev2])
2254 cmd = ['log', ''.join(revs)]
2255 out = DiffColoring(self.config)
2256 if out.is_on and color:
2257 cmd.append('--color')
2258 if oneline:
2259 cmd.append('--oneline')
2260
2261 try:
2262 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2263 if log.Wait() == 0:
2264 return log.stdout
2265 except GitError:
2266 # worktree may not exist if groups changed for example. In that case,
2267 # try in gitdir instead.
2268 if not os.path.exists(self.worktree):
2269 return self.bare_git.log(*cmd[1:])
2270 else:
2271 raise
2272 return None
2273
2274 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2275 """Get the list of logs from this revision to given revisionId"""
2276 logs = {}
2277 selfId = self.GetRevisionId(self._allrefs)
2278 toId = toProject.GetRevisionId(toProject._allrefs)
2279
2280 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2281 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2282 return logs
2283
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002284 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002285 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002286 self._project = project
2287 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002288 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002289
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002290 def LsOthers(self):
2291 p = GitCommand(self._project,
2292 ['ls-files',
2293 '-z',
2294 '--others',
2295 '--exclude-standard'],
2296 bare = False,
David James8d201162013-10-11 17:03:19 -07002297 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002298 capture_stdout = True,
2299 capture_stderr = True)
2300 if p.Wait() == 0:
2301 out = p.stdout
2302 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002303 return out[:-1].split('\0') # pylint: disable=W1401
2304 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002305 return []
2306
2307 def DiffZ(self, name, *args):
2308 cmd = [name]
2309 cmd.append('-z')
2310 cmd.extend(args)
2311 p = GitCommand(self._project,
2312 cmd,
David James8d201162013-10-11 17:03:19 -07002313 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002314 bare = False,
2315 capture_stdout = True,
2316 capture_stderr = True)
2317 try:
2318 out = p.process.stdout.read()
2319 r = {}
2320 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002321 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002322 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002323 try:
2324 info = out.next()
2325 path = out.next()
2326 except StopIteration:
2327 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002328
2329 class _Info(object):
2330 def __init__(self, path, omode, nmode, oid, nid, state):
2331 self.path = path
2332 self.src_path = None
2333 self.old_mode = omode
2334 self.new_mode = nmode
2335 self.old_id = oid
2336 self.new_id = nid
2337
2338 if len(state) == 1:
2339 self.status = state
2340 self.level = None
2341 else:
2342 self.status = state[:1]
2343 self.level = state[1:]
2344 while self.level.startswith('0'):
2345 self.level = self.level[1:]
2346
2347 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002348 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002349 if info.status in ('R', 'C'):
2350 info.src_path = info.path
2351 info.path = out.next()
2352 r[info.path] = info
2353 return r
2354 finally:
2355 p.Wait()
2356
2357 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002358 if self._bare:
2359 path = os.path.join(self._project.gitdir, HEAD)
2360 else:
2361 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002362 try:
2363 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002364 except IOError as e:
2365 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002366 try:
2367 line = fd.read()
2368 finally:
2369 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302370 try:
2371 line = line.decode()
2372 except AttributeError:
2373 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002374 if line.startswith('ref: '):
2375 return line[5:-1]
2376 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002377
2378 def SetHead(self, ref, message=None):
2379 cmdv = []
2380 if message is not None:
2381 cmdv.extend(['-m', message])
2382 cmdv.append(HEAD)
2383 cmdv.append(ref)
2384 self.symbolic_ref(*cmdv)
2385
2386 def DetachHead(self, new, message=None):
2387 cmdv = ['--no-deref']
2388 if message is not None:
2389 cmdv.extend(['-m', message])
2390 cmdv.append(HEAD)
2391 cmdv.append(new)
2392 self.update_ref(*cmdv)
2393
2394 def UpdateRef(self, name, new, old=None,
2395 message=None,
2396 detach=False):
2397 cmdv = []
2398 if message is not None:
2399 cmdv.extend(['-m', message])
2400 if detach:
2401 cmdv.append('--no-deref')
2402 cmdv.append(name)
2403 cmdv.append(new)
2404 if old is not None:
2405 cmdv.append(old)
2406 self.update_ref(*cmdv)
2407
2408 def DeleteRef(self, name, old=None):
2409 if not old:
2410 old = self.rev_parse(name)
2411 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002412 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002413
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002414 def rev_list(self, *args, **kw):
2415 if 'format' in kw:
2416 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2417 else:
2418 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002419 cmdv.extend(args)
2420 p = GitCommand(self._project,
2421 cmdv,
2422 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002423 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002424 capture_stdout = True,
2425 capture_stderr = True)
2426 r = []
2427 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002428 if line[-1] == '\n':
2429 line = line[:-1]
2430 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002431 if p.Wait() != 0:
2432 raise GitError('%s rev-list %s: %s' % (
2433 self._project.name,
2434 str(args),
2435 p.stderr))
2436 return r
2437
2438 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002439 """Allow arbitrary git commands using pythonic syntax.
2440
2441 This allows you to do things like:
2442 git_obj.rev_parse('HEAD')
2443
2444 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2445 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002446 Any other positional arguments will be passed to the git command, and the
2447 following keyword arguments are supported:
2448 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002449
2450 Args:
2451 name: The name of the git command to call. Any '_' characters will
2452 be replaced with '-'.
2453
2454 Returns:
2455 A callable object that will try to call git with the named command.
2456 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002457 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002458 def runner(*args, **kwargs):
2459 cmdv = []
2460 config = kwargs.pop('config', None)
2461 for k in kwargs:
2462 raise TypeError('%s() got an unexpected keyword argument %r'
2463 % (name, k))
2464 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002465 if not git_require((1, 7, 2)):
2466 raise ValueError('cannot set config on command line for %s()'
2467 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302468 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002469 cmdv.append('-c')
2470 cmdv.append('%s=%s' % (k, v))
2471 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002472 cmdv.extend(args)
2473 p = GitCommand(self._project,
2474 cmdv,
2475 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002476 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002477 capture_stdout = True,
2478 capture_stderr = True)
2479 if p.Wait() != 0:
2480 raise GitError('%s %s: %s' % (
2481 self._project.name,
2482 name,
2483 p.stderr))
2484 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302485 try:
Conley Owensedd01512013-09-26 12:59:58 -07002486 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302487 except AttributeError:
2488 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002489 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2490 return r[:-1]
2491 return r
2492 return runner
2493
2494
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002495class _PriorSyncFailedError(Exception):
2496 def __str__(self):
2497 return 'prior sync failed; rebase still in progress'
2498
2499class _DirtyError(Exception):
2500 def __str__(self):
2501 return 'contains uncommitted changes'
2502
2503class _InfoMessage(object):
2504 def __init__(self, project, text):
2505 self.project = project
2506 self.text = text
2507
2508 def Print(self, syncbuf):
2509 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2510 syncbuf.out.nl()
2511
2512class _Failure(object):
2513 def __init__(self, project, why):
2514 self.project = project
2515 self.why = why
2516
2517 def Print(self, syncbuf):
2518 syncbuf.out.fail('error: %s/: %s',
2519 self.project.relpath,
2520 str(self.why))
2521 syncbuf.out.nl()
2522
2523class _Later(object):
2524 def __init__(self, project, action):
2525 self.project = project
2526 self.action = action
2527
2528 def Run(self, syncbuf):
2529 out = syncbuf.out
2530 out.project('project %s/', self.project.relpath)
2531 out.nl()
2532 try:
2533 self.action()
2534 out.nl()
2535 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002536 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002537 out.nl()
2538 return False
2539
2540class _SyncColoring(Coloring):
2541 def __init__(self, config):
2542 Coloring.__init__(self, config, 'reposync')
2543 self.project = self.printer('header', attr = 'bold')
2544 self.info = self.printer('info')
2545 self.fail = self.printer('fail', fg='red')
2546
2547class SyncBuffer(object):
2548 def __init__(self, config, detach_head=False):
2549 self._messages = []
2550 self._failures = []
2551 self._later_queue1 = []
2552 self._later_queue2 = []
2553
2554 self.out = _SyncColoring(config)
2555 self.out.redirect(sys.stderr)
2556
2557 self.detach_head = detach_head
2558 self.clean = True
2559
2560 def info(self, project, fmt, *args):
2561 self._messages.append(_InfoMessage(project, fmt % args))
2562
2563 def fail(self, project, err=None):
2564 self._failures.append(_Failure(project, err))
2565 self.clean = False
2566
2567 def later1(self, project, what):
2568 self._later_queue1.append(_Later(project, what))
2569
2570 def later2(self, project, what):
2571 self._later_queue2.append(_Later(project, what))
2572
2573 def Finish(self):
2574 self._PrintMessages()
2575 self._RunLater()
2576 self._PrintMessages()
2577 return self.clean
2578
2579 def _RunLater(self):
2580 for q in ['_later_queue1', '_later_queue2']:
2581 if not self._RunQueue(q):
2582 return
2583
2584 def _RunQueue(self, queue):
2585 for m in getattr(self, queue):
2586 if not m.Run(self):
2587 self.clean = False
2588 return False
2589 setattr(self, queue, [])
2590 return True
2591
2592 def _PrintMessages(self):
2593 for m in self._messages:
2594 m.Print(self)
2595 for m in self._failures:
2596 m.Print(self)
2597
2598 self._messages = []
2599 self._failures = []
2600
2601
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002602class MetaProject(Project):
2603 """A special project housed under .repo.
2604 """
2605 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002606 Project.__init__(self,
2607 manifest = manifest,
2608 name = name,
2609 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002610 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002611 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002612 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002613 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002614 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002615 revisionId = None,
2616 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002617
2618 def PreSync(self):
2619 if self.Exists:
2620 cb = self.CurrentBranch
2621 if cb:
2622 base = self.GetBranch(cb).merge
2623 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002624 self.revisionExpr = base
2625 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002626
Florian Vallee5d016502012-06-07 17:19:26 +02002627 def MetaBranchSwitch(self, target):
2628 """ Prepare MetaProject for manifest branch switch
2629 """
2630
2631 # detach and delete manifest branch, allowing a new
2632 # branch to take over
2633 syncbuf = SyncBuffer(self.config, detach_head = True)
2634 self.Sync_LocalHalf(syncbuf)
2635 syncbuf.Finish()
2636
2637 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002638 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002639 capture_stdout = True,
2640 capture_stderr = True).Wait() == 0
2641
2642
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002643 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002644 def LastFetch(self):
2645 try:
2646 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2647 return os.path.getmtime(fh)
2648 except OSError:
2649 return 0
2650
2651 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002652 def HasChanges(self):
2653 """Has the remote received new commits not yet checked out?
2654 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002655 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002656 return False
2657
David Pursehouse8a68ff92012-09-24 12:15:13 +09002658 all_refs = self.bare_ref.all
2659 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002660 head = self.work_git.GetHead()
2661 if head.startswith(R_HEADS):
2662 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002663 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002664 except KeyError:
2665 head = None
2666
2667 if revid == head:
2668 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002669 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002670 return True
2671 return False