blob: 55c188db1de0b58e3a5cc87a6f0730921c87da09 [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:
Anthony King70f68902014-05-05 21:15:34 +0100441 exec(compile(open(self._script_fullpath).read(),
442 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800443 except Exception:
444 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
445 traceback.format_exc(), self._hook_type))
446
447 # Running the script should have defined a main() function.
448 if 'main' not in context:
449 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
450
451
452 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
453 # We don't actually want hooks to define their main with this argument--
454 # it's there to remind them that their hook should always take **kwargs.
455 # For instance, a pre-upload hook should be defined like:
456 # def main(project_list, **kwargs):
457 #
458 # This allows us to later expand the API without breaking old hooks.
459 kwargs = kwargs.copy()
460 kwargs['hook_should_take_kwargs'] = True
461
462 # Call the main function in the hook. If the hook should cause the
463 # build to fail, it will raise an Exception. We'll catch that convert
464 # to a HookError w/ just the failing traceback.
465 try:
466 context['main'](**kwargs)
467 except Exception:
468 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
469 'above.' % (
470 traceback.format_exc(), self._hook_type))
471 finally:
472 # Restore sys.path and CWD.
473 sys.path = orig_syspath
474 os.chdir(orig_path)
475
476 def Run(self, user_allows_all_hooks, **kwargs):
477 """Run the hook.
478
479 If the hook doesn't exist (because there is no hooks project or because
480 this particular hook is not enabled), this is a no-op.
481
482 Args:
483 user_allows_all_hooks: If True, we will never prompt about running the
484 hook--we'll just assume it's OK to run it.
485 kwargs: Keyword arguments to pass to the hook. These are often specific
486 to the hook type. For instance, pre-upload hooks will contain
487 a project_list.
488
489 Raises:
490 HookError: If there was a problem finding the hook or the user declined
491 to run a required hook (from _CheckForHookApproval).
492 """
493 # No-op if there is no hooks project or if hook is disabled.
494 if ((not self._hooks_project) or
495 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
496 return
497
498 # Bail with a nice error if we can't find the hook.
499 if not os.path.isfile(self._script_fullpath):
500 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
501
502 # Make sure the user is OK with running the hook.
503 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
504 return
505
506 # Run the hook with the same version of python we're using.
507 self._ExecuteHook(**kwargs)
508
509
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700510class Project(object):
511 def __init__(self,
512 manifest,
513 name,
514 remote,
515 gitdir,
David James8d201162013-10-11 17:03:19 -0700516 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700517 worktree,
518 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700519 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800520 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700521 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700522 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700523 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800524 sync_s = False,
David Pursehouseede7f122012-11-27 22:25:30 +0900525 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800526 upstream = None,
527 parent = None,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400528 is_derived = False,
529 dest_branch = None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800530 """Init a Project object.
531
532 Args:
533 manifest: The XmlManifest object.
534 name: The `name` attribute of manifest.xml's project element.
535 remote: RemoteSpec object specifying its remote's properties.
536 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700537 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800538 worktree: Absolute path of git working tree.
539 relpath: Relative path of git working tree to repo's top directory.
540 revisionExpr: The `revision` attribute of manifest.xml's project element.
541 revisionId: git commit id for checking out.
542 rebase: The `rebase` attribute of manifest.xml's project element.
543 groups: The `groups` attribute of manifest.xml's project element.
544 sync_c: The `sync-c` attribute of manifest.xml's project element.
545 sync_s: The `sync-s` attribute of manifest.xml's project element.
546 upstream: The `upstream` attribute of manifest.xml's project element.
547 parent: The parent Project object.
548 is_derived: False if the project was explicitly defined in the manifest;
549 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400550 dest_branch: The branch to which to push changes for review by default.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800551 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552 self.manifest = manifest
553 self.name = name
554 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800555 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700556 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800557 if worktree:
558 self.worktree = worktree.replace('\\', '/')
559 else:
560 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700561 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700562 self.revisionExpr = revisionExpr
563
564 if revisionId is None \
565 and revisionExpr \
566 and IsId(revisionExpr):
567 self.revisionId = revisionExpr
568 else:
569 self.revisionId = revisionId
570
Mike Pontillod3153822012-02-28 11:53:24 -0800571 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700572 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700573 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800574 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900575 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700576 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800577 self.parent = parent
578 self.is_derived = is_derived
579 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800580
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700581 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700582 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500583 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500584 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700585 self.config = GitConfig.ForRepository(
586 gitdir = self.gitdir,
587 defaults = self.manifest.globalConfig)
588
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800589 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700590 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800591 else:
592 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700593 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700594 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700595 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400596 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700597
Doug Anderson37282b42011-03-04 11:54:18 -0800598 # This will be filled in if a project is later identified to be the
599 # project containing repo hooks.
600 self.enabled_repo_hooks = []
601
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700602 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800603 def Derived(self):
604 return self.is_derived
605
606 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700607 def Exists(self):
608 return os.path.isdir(self.gitdir)
609
610 @property
611 def CurrentBranch(self):
612 """Obtain the name of the currently checked out branch.
613 The branch name omits the 'refs/heads/' prefix.
614 None is returned if the project is on a detached HEAD.
615 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700616 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700617 if b.startswith(R_HEADS):
618 return b[len(R_HEADS):]
619 return None
620
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700621 def IsRebaseInProgress(self):
622 w = self.worktree
623 g = os.path.join(w, '.git')
624 return os.path.exists(os.path.join(g, 'rebase-apply')) \
625 or os.path.exists(os.path.join(g, 'rebase-merge')) \
626 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200627
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700628 def IsDirty(self, consider_untracked=True):
629 """Is the working directory modified in some way?
630 """
631 self.work_git.update_index('-q',
632 '--unmerged',
633 '--ignore-missing',
634 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900635 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700636 return True
637 if self.work_git.DiffZ('diff-files'):
638 return True
639 if consider_untracked and self.work_git.LsOthers():
640 return True
641 return False
642
643 _userident_name = None
644 _userident_email = None
645
646 @property
647 def UserName(self):
648 """Obtain the user's personal name.
649 """
650 if self._userident_name is None:
651 self._LoadUserIdentity()
652 return self._userident_name
653
654 @property
655 def UserEmail(self):
656 """Obtain the user's email address. This is very likely
657 to be their Gerrit login.
658 """
659 if self._userident_email is None:
660 self._LoadUserIdentity()
661 return self._userident_email
662
663 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900664 u = self.bare_git.var('GIT_COMMITTER_IDENT')
665 m = re.compile("^(.*) <([^>]*)> ").match(u)
666 if m:
667 self._userident_name = m.group(1)
668 self._userident_email = m.group(2)
669 else:
670 self._userident_name = ''
671 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700672
673 def GetRemote(self, name):
674 """Get the configuration for a single remote.
675 """
676 return self.config.GetRemote(name)
677
678 def GetBranch(self, name):
679 """Get the configuration for a single branch.
680 """
681 return self.config.GetBranch(name)
682
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700683 def GetBranches(self):
684 """Get all existing local branches.
685 """
686 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900687 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700688 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700689
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530690 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700691 if name.startswith(R_HEADS):
692 name = name[len(R_HEADS):]
693 b = self.GetBranch(name)
694 b.current = name == current
695 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900696 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700697 heads[name] = b
698
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530699 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700700 if name.startswith(R_PUB):
701 name = name[len(R_PUB):]
702 b = heads.get(name)
703 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900704 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700705
706 return heads
707
Colin Cross5acde752012-03-28 20:15:45 -0700708 def MatchesGroups(self, manifest_groups):
709 """Returns true if the manifest groups specified at init should cause
710 this project to be synced.
711 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700712 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700713
714 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700715 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700716 manifest_groups: "-group1,group2"
717 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500718
719 The special manifest group "default" will match any project that
720 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700721 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500722 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700723 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500724 if not 'notdefault' in expanded_project_groups:
725 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700726
Conley Owens971de8e2012-04-16 10:36:08 -0700727 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700728 for group in expanded_manifest_groups:
729 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700730 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700731 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700732 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700733
Conley Owens971de8e2012-04-16 10:36:08 -0700734 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735
736## Status Display ##
737
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500738 def HasChanges(self):
739 """Returns true if there are uncommitted changes.
740 """
741 self.work_git.update_index('-q',
742 '--unmerged',
743 '--ignore-missing',
744 '--refresh')
745 if self.IsRebaseInProgress():
746 return True
747
748 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
749 return True
750
751 if self.work_git.DiffZ('diff-files'):
752 return True
753
754 if self.work_git.LsOthers():
755 return True
756
757 return False
758
Terence Haddock4655e812011-03-31 12:33:34 +0200759 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700760 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200761
762 Args:
763 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700764 """
765 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200766 if output_redir == None:
767 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700768 print(file=output_redir)
769 print('project %s/' % self.relpath, file=output_redir)
770 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700771 return
772
773 self.work_git.update_index('-q',
774 '--unmerged',
775 '--ignore-missing',
776 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700777 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700778 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
779 df = self.work_git.DiffZ('diff-files')
780 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100781 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700782 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700783
784 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200785 if not output_redir == None:
786 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700787 out.project('project %-40s', self.relpath + '/')
788
789 branch = self.CurrentBranch
790 if branch is None:
791 out.nobranch('(*** NO BRANCH ***)')
792 else:
793 out.branch('branch %s', branch)
794 out.nl()
795
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700796 if rb:
797 out.important('prior sync failed; rebase still in progress')
798 out.nl()
799
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700800 paths = list()
801 paths.extend(di.keys())
802 paths.extend(df.keys())
803 paths.extend(do)
804
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530805 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900806 try:
807 i = di[p]
808 except KeyError:
809 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900811 try:
812 f = df[p]
813 except KeyError:
814 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200815
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900816 if i:
817 i_status = i.status.upper()
818 else:
819 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700820
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900821 if f:
822 f_status = f.status.lower()
823 else:
824 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700825
826 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800827 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828 i.src_path, p, i.level)
829 else:
830 line = ' %s%s\t%s' % (i_status, f_status, p)
831
832 if i and not f:
833 out.added('%s', line)
834 elif (i and f) or (not i and f):
835 out.changed('%s', line)
836 elif not i and not f:
837 out.untracked('%s', line)
838 else:
839 out.write('%s', line)
840 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200841
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700842 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843
pelyad67872d2012-03-28 14:49:58 +0300844 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845 """Prints the status of the repository to stdout.
846 """
847 out = DiffColoring(self.config)
848 cmd = ['diff']
849 if out.is_on:
850 cmd.append('--color')
851 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300852 if absolute_paths:
853 cmd.append('--src-prefix=a/%s/' % self.relpath)
854 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700855 cmd.append('--')
856 p = GitCommand(self,
857 cmd,
858 capture_stdout = True,
859 capture_stderr = True)
860 has_diff = False
861 for line in p.process.stdout:
862 if not has_diff:
863 out.nl()
864 out.project('project %s/' % self.relpath)
865 out.nl()
866 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700867 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868 p.Wait()
869
870
871## Publish / Upload ##
872
David Pursehouse8a68ff92012-09-24 12:15:13 +0900873 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874 """Was the branch published (uploaded) for code review?
875 If so, returns the SHA-1 hash of the last published
876 state for the branch.
877 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700878 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900879 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700880 try:
881 return self.bare_git.rev_parse(key)
882 except GitError:
883 return None
884 else:
885 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900886 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700887 except KeyError:
888 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889
David Pursehouse8a68ff92012-09-24 12:15:13 +0900890 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700891 """Prunes any stale published refs.
892 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900893 if all_refs is None:
894 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700895 heads = set()
896 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530897 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898 if name.startswith(R_HEADS):
899 heads.add(name)
900 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900901 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530903 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904 n = name[len(R_PUB):]
905 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900906 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700907
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700908 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700909 """List any branches which can be uploaded for review.
910 """
911 heads = {}
912 pubed = {}
913
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530914 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700915 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900916 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900918 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919
920 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530921 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900922 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700924 if selected_branch and branch != selected_branch:
925 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800927 rb = self.GetUploadableBranch(branch)
928 if rb:
929 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 return ready
931
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800932 def GetUploadableBranch(self, branch_name):
933 """Get a single uploadable branch, or None.
934 """
935 branch = self.GetBranch(branch_name)
936 base = branch.LocalMerge
937 if branch.LocalMerge:
938 rb = ReviewableBranch(self, branch, base)
939 if rb.commits:
940 return rb
941 return None
942
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700943 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700944 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700945 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400946 draft=False,
947 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 """Uploads the named branch for code review.
949 """
950 if branch is None:
951 branch = self.CurrentBranch
952 if branch is None:
953 raise GitError('not currently on a branch')
954
955 branch = self.GetBranch(branch)
956 if not branch.LocalMerge:
957 raise GitError('branch %s does not track a remote' % branch.name)
958 if not branch.remote.review:
959 raise GitError('remote %s has no review url' % branch.remote.name)
960
Bryan Jacobsf609f912013-05-06 13:36:24 -0400961 if dest_branch is None:
962 dest_branch = self.dest_branch
963 if dest_branch is None:
964 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700965 if not dest_branch.startswith(R_HEADS):
966 dest_branch = R_HEADS + dest_branch
967
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800968 if not branch.remote.projectname:
969 branch.remote.projectname = self.name
970 branch.remote.Save()
971
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800972 url = branch.remote.ReviewUrl(self.UserEmail)
973 if url is None:
974 raise UploadError('review not configured')
975 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800976
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800977 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800978 rp = ['gerrit receive-pack']
979 for e in people[0]:
980 rp.append('--reviewer=%s' % sq(e))
981 for e in people[1]:
982 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800983 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700984
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800985 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800986
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800987 if dest_branch.startswith(R_HEADS):
988 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700989
990 upload_type = 'for'
991 if draft:
992 upload_type = 'drafts'
993
994 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
995 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800996 if auto_topic:
997 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800998 if not url.startswith('ssh://'):
999 rp = ['r=%s' % p for p in people[0]] + \
1000 ['cc=%s' % p for p in people[1]]
1001 if rp:
1002 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001003 cmd.append(ref_spec)
1004
1005 if GitCommand(self, cmd, bare = True).Wait() != 0:
1006 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001007
1008 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1009 self.bare_git.UpdateRef(R_PUB + branch.name,
1010 R_HEADS + branch.name,
1011 message = msg)
1012
1013
1014## Sync ##
1015
Julien Campergue335f5ef2013-10-16 11:02:35 +02001016 def _ExtractArchive(self, tarpath, path=None):
1017 """Extract the given tar on its current location
1018
1019 Args:
1020 - tarpath: The path to the actual tar file
1021
1022 """
1023 try:
1024 with tarfile.open(tarpath, 'r') as tar:
1025 tar.extractall(path=path)
1026 return True
1027 except (IOError, tarfile.TarError) as e:
1028 print("error: Cannot extract archive %s: "
1029 "%s" % (tarpath, str(e)), file=sys.stderr)
1030 return False
1031
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001032 def Sync_NetworkHalf(self,
1033 quiet=False,
1034 is_new=None,
1035 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001036 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001037 no_tags=False,
1038 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039 """Perform only the network IO portion of the sync process.
1040 Local working directory/branch state is not affected.
1041 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001042 if archive and not isinstance(self, MetaProject):
1043 if self.remote.url.startswith(('http://', 'https://')):
1044 print("error: %s: Cannot fetch archives from http/https "
1045 "remotes." % self.name, file=sys.stderr)
1046 return False
1047
1048 name = self.relpath.replace('\\', '/')
1049 name = name.replace('/', '_')
1050 tarpath = '%s.tar' % name
1051 topdir = self.manifest.topdir
1052
1053 try:
1054 self._FetchArchive(tarpath, cwd=topdir)
1055 except GitError as e:
1056 print('error: %s' % str(e), file=sys.stderr)
1057 return False
1058
1059 # From now on, we only need absolute tarpath
1060 tarpath = os.path.join(topdir, tarpath)
1061
1062 if not self._ExtractArchive(tarpath, path=topdir):
1063 return False
1064 try:
1065 os.remove(tarpath)
1066 except OSError as e:
1067 print("warn: Cannot remove archive %s: "
1068 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001069 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001070 return True
1071
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001072 if is_new is None:
1073 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001074 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001076 else:
1077 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001079
1080 if is_new:
1081 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1082 try:
1083 fd = open(alt, 'rb')
1084 try:
1085 alt_dir = fd.readline().rstrip()
1086 finally:
1087 fd.close()
1088 except IOError:
1089 alt_dir = None
1090 else:
1091 alt_dir = None
1092
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001093 if clone_bundle \
1094 and alt_dir is None \
1095 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001096 is_new = False
1097
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001098 if not current_branch_only:
1099 if self.sync_c:
1100 current_branch_only = True
1101 elif not self.manifest._loaded:
1102 # Manifest cannot check defaults until it syncs.
1103 current_branch_only = False
1104 elif self.manifest.default.sync_c:
1105 current_branch_only = True
1106
Conley Owens666d5342014-05-01 13:09:57 -07001107 has_sha1 = ID_RE.match(self.revisionExpr) and self._CheckForSha1()
1108 if (not has_sha1 #Need to fetch since we don't already have this revision
1109 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1110 current_branch_only=current_branch_only,
1111 no_tags=no_tags)):
1112 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001113
1114 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001115 self._InitMRef()
1116 else:
1117 self._InitMirrorHead()
1118 try:
1119 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1120 except OSError:
1121 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001122 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001123
1124 def PostRepoUpgrade(self):
1125 self._InitHooks()
1126
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001127 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001128 for copyfile in self.copyfiles:
1129 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001130 for linkfile in self.linkfiles:
1131 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001132
Julien Camperguedd654222014-01-09 16:21:37 +01001133 def GetCommitRevisionId(self):
1134 """Get revisionId of a commit.
1135
1136 Use this method instead of GetRevisionId to get the id of the commit rather
1137 than the id of the current git object (for example, a tag)
1138
1139 """
1140 if not self.revisionExpr.startswith(R_TAGS):
1141 return self.GetRevisionId(self._allrefs)
1142
1143 try:
1144 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1145 except GitError:
1146 raise ManifestInvalidRevisionError(
1147 'revision %s in %s not found' % (self.revisionExpr,
1148 self.name))
1149
David Pursehouse8a68ff92012-09-24 12:15:13 +09001150 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001151 if self.revisionId:
1152 return self.revisionId
1153
1154 rem = self.GetRemote(self.remote.name)
1155 rev = rem.ToLocal(self.revisionExpr)
1156
David Pursehouse8a68ff92012-09-24 12:15:13 +09001157 if all_refs is not None and rev in all_refs:
1158 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001159
1160 try:
1161 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1162 except GitError:
1163 raise ManifestInvalidRevisionError(
1164 'revision %s in %s not found' % (self.revisionExpr,
1165 self.name))
1166
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001167 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001168 """Perform only the local IO portion of the sync process.
1169 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170 """
David James8d201162013-10-11 17:03:19 -07001171 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001172 all_refs = self.bare_ref.all
1173 self.CleanPublishedCache(all_refs)
1174 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001175
David Pursehouse1d947b32012-10-25 12:23:11 +09001176 def _doff():
1177 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001178 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001179
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001180 head = self.work_git.GetHead()
1181 if head.startswith(R_HEADS):
1182 branch = head[len(R_HEADS):]
1183 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001184 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001185 except KeyError:
1186 head = None
1187 else:
1188 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001189
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001190 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001191 # Currently on a detached HEAD. The user is assumed to
1192 # not have any local modifications worth worrying about.
1193 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001194 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001195 syncbuf.fail(self, _PriorSyncFailedError())
1196 return
1197
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001198 if head == revid:
1199 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001200 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001201 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001202 if not syncbuf.detach_head:
1203 return
1204 else:
1205 lost = self._revlist(not_rev(revid), HEAD)
1206 if lost:
1207 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001208
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001209 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001210 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001211 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001212 syncbuf.fail(self, e)
1213 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001214 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001215 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001216
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001217 if head == revid:
1218 # No changes; don't do anything further.
1219 #
1220 return
1221
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001222 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001223
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001224 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001225 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001226 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001228 syncbuf.info(self,
1229 "leaving %s; does not track upstream",
1230 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001231 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001232 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001233 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001234 syncbuf.fail(self, e)
1235 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001236 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001237 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001238
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001239 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001240 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001241 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001242 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001243 if not_merged:
1244 if upstream_gain:
1245 # The user has published this branch and some of those
1246 # commits are not yet merged upstream. We do not want
1247 # to rewrite the published commits so we punt.
1248 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001249 syncbuf.fail(self,
1250 "branch %s is published (but not merged) and is now %d commits behind"
1251 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001252 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001253 elif pub == head:
1254 # All published commits are merged, and thus we are a
1255 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001256 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001257 syncbuf.later1(self, _doff)
1258 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001259
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001260 # Examine the local commits not in the remote. Find the
1261 # last one attributed to this user, if any.
1262 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001263 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001264 last_mine = None
1265 cnt_mine = 0
1266 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301267 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001268 if committer_email == self.UserEmail:
1269 last_mine = commit_id
1270 cnt_mine += 1
1271
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001272 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001273 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001274
1275 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001276 syncbuf.fail(self, _DirtyError())
1277 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001278
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001279 # If the upstream switched on us, warn the user.
1280 #
1281 if branch.merge != self.revisionExpr:
1282 if branch.merge and self.revisionExpr:
1283 syncbuf.info(self,
1284 'manifest switched %s...%s',
1285 branch.merge,
1286 self.revisionExpr)
1287 elif branch.merge:
1288 syncbuf.info(self,
1289 'manifest no longer tracks %s',
1290 branch.merge)
1291
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001292 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001294 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001295 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001296 syncbuf.info(self,
1297 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001298 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001300 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001301 if not ID_RE.match(self.revisionExpr):
1302 # in case of manifest sync the revisionExpr might be a SHA1
1303 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001304 branch.Save()
1305
Mike Pontillod3153822012-02-28 11:53:24 -08001306 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001307 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001308 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001309 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001310 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001311 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001312 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001313 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001314 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001315 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001316 syncbuf.fail(self, e)
1317 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001319 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001321 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 # dest should already be an absolute path, but src is project relative
1323 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001324 abssrc = os.path.join(self.worktree, src)
1325 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001327 def AddLinkFile(self, src, dest, absdest):
1328 # dest should already be an absolute path, but src is project relative
1329 # make src an absolute path
1330 abssrc = os.path.join(self.worktree, src)
1331 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1332
James W. Mills24c13082012-04-12 15:04:13 -05001333 def AddAnnotation(self, name, value, keep):
1334 self.annotations.append(_Annotation(name, value, keep))
1335
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001336 def DownloadPatchSet(self, change_id, patch_id):
1337 """Download a single patch set of a single change to FETCH_HEAD.
1338 """
1339 remote = self.GetRemote(self.remote.name)
1340
1341 cmd = ['fetch', remote.name]
1342 cmd.append('refs/changes/%2.2d/%d/%d' \
1343 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001344 if GitCommand(self, cmd, bare=True).Wait() != 0:
1345 return None
1346 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001347 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001348 change_id,
1349 patch_id,
1350 self.bare_git.rev_parse('FETCH_HEAD'))
1351
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001352
1353## Branch Management ##
1354
1355 def StartBranch(self, name):
1356 """Create a new branch off the manifest's revision.
1357 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001358 head = self.work_git.GetHead()
1359 if head == (R_HEADS + name):
1360 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001361
David Pursehouse8a68ff92012-09-24 12:15:13 +09001362 all_refs = self.bare_ref.all
1363 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001364 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001365 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001366 capture_stdout = True,
1367 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001368
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001369 branch = self.GetBranch(name)
1370 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001371 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001372 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001373
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001374 if head.startswith(R_HEADS):
1375 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001376 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001377 except KeyError:
1378 head = None
1379
1380 if revid and head and revid == head:
1381 ref = os.path.join(self.gitdir, R_HEADS + name)
1382 try:
1383 os.makedirs(os.path.dirname(ref))
1384 except OSError:
1385 pass
1386 _lwrite(ref, '%s\n' % revid)
1387 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1388 'ref: %s%s\n' % (R_HEADS, name))
1389 branch.Save()
1390 return True
1391
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001392 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001393 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001394 capture_stdout = True,
1395 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001396 branch.Save()
1397 return True
1398 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399
Wink Saville02d79452009-04-10 13:01:24 -07001400 def CheckoutBranch(self, name):
1401 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001402
1403 Args:
1404 name: The name of the branch to checkout.
1405
1406 Returns:
1407 True if the checkout succeeded; False if it didn't; None if the branch
1408 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001409 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001410 rev = R_HEADS + name
1411 head = self.work_git.GetHead()
1412 if head == rev:
1413 # Already on the branch
1414 #
1415 return True
Wink Saville02d79452009-04-10 13:01:24 -07001416
David Pursehouse8a68ff92012-09-24 12:15:13 +09001417 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001418 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001419 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001420 except KeyError:
1421 # Branch does not exist in this project
1422 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001423 return None
Wink Saville02d79452009-04-10 13:01:24 -07001424
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001425 if head.startswith(R_HEADS):
1426 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001427 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001428 except KeyError:
1429 head = None
1430
1431 if head == revid:
1432 # Same revision; just update HEAD to point to the new
1433 # target branch, but otherwise take no other action.
1434 #
1435 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1436 'ref: %s%s\n' % (R_HEADS, name))
1437 return True
1438
1439 return GitCommand(self,
1440 ['checkout', name, '--'],
1441 capture_stdout = True,
1442 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001443
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001444 def AbandonBranch(self, name):
1445 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001446
1447 Args:
1448 name: The name of the branch to abandon.
1449
1450 Returns:
1451 True if the abandon succeeded; False if it didn't; None if the branch
1452 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001453 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001454 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001455 all_refs = self.bare_ref.all
1456 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001457 # Doesn't exist
1458 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001459
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001460 head = self.work_git.GetHead()
1461 if head == rev:
1462 # We can't destroy the branch while we are sitting
1463 # on it. Switch to a detached HEAD.
1464 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001465 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001466
David Pursehouse8a68ff92012-09-24 12:15:13 +09001467 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001468 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001469 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1470 '%s\n' % revid)
1471 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001472 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001473
1474 return GitCommand(self,
1475 ['branch', '-D', name],
1476 capture_stdout = True,
1477 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001478
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001479 def PruneHeads(self):
1480 """Prune any topic branches already merged into upstream.
1481 """
1482 cb = self.CurrentBranch
1483 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001484 left = self._allrefs
1485 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001486 if name.startswith(R_HEADS):
1487 name = name[len(R_HEADS):]
1488 if cb is None or name != cb:
1489 kill.append(name)
1490
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001491 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001492 if cb is not None \
1493 and not self._revlist(HEAD + '...' + rev) \
1494 and not self.IsDirty(consider_untracked = False):
1495 self.work_git.DetachHead(HEAD)
1496 kill.append(cb)
1497
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001498 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001499 old = self.bare_git.GetHead()
1500 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001501 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1502
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001503 try:
1504 self.bare_git.DetachHead(rev)
1505
1506 b = ['branch', '-d']
1507 b.extend(kill)
1508 b = GitCommand(self, b, bare=True,
1509 capture_stdout=True,
1510 capture_stderr=True)
1511 b.Wait()
1512 finally:
1513 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001514 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001515
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001516 for branch in kill:
1517 if (R_HEADS + branch) not in left:
1518 self.CleanPublishedCache()
1519 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001520
1521 if cb and cb not in kill:
1522 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001523 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001524
1525 kept = []
1526 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001527 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001528 branch = self.GetBranch(branch)
1529 base = branch.LocalMerge
1530 if not base:
1531 base = rev
1532 kept.append(ReviewableBranch(self, branch, base))
1533 return kept
1534
1535
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001536## Submodule Management ##
1537
1538 def GetRegisteredSubprojects(self):
1539 result = []
1540 def rec(subprojects):
1541 if not subprojects:
1542 return
1543 result.extend(subprojects)
1544 for p in subprojects:
1545 rec(p.subprojects)
1546 rec(self.subprojects)
1547 return result
1548
1549 def _GetSubmodules(self):
1550 # Unfortunately we cannot call `git submodule status --recursive` here
1551 # because the working tree might not exist yet, and it cannot be used
1552 # without a working tree in its current implementation.
1553
1554 def get_submodules(gitdir, rev):
1555 # Parse .gitmodules for submodule sub_paths and sub_urls
1556 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1557 if not sub_paths:
1558 return []
1559 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1560 # revision of submodule repository
1561 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1562 submodules = []
1563 for sub_path, sub_url in zip(sub_paths, sub_urls):
1564 try:
1565 sub_rev = sub_revs[sub_path]
1566 except KeyError:
1567 # Ignore non-exist submodules
1568 continue
1569 submodules.append((sub_rev, sub_path, sub_url))
1570 return submodules
1571
1572 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1573 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1574 def parse_gitmodules(gitdir, rev):
1575 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1576 try:
1577 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1578 bare = True, gitdir = gitdir)
1579 except GitError:
1580 return [], []
1581 if p.Wait() != 0:
1582 return [], []
1583
1584 gitmodules_lines = []
1585 fd, temp_gitmodules_path = tempfile.mkstemp()
1586 try:
1587 os.write(fd, p.stdout)
1588 os.close(fd)
1589 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1590 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1591 bare = True, gitdir = gitdir)
1592 if p.Wait() != 0:
1593 return [], []
1594 gitmodules_lines = p.stdout.split('\n')
1595 except GitError:
1596 return [], []
1597 finally:
1598 os.remove(temp_gitmodules_path)
1599
1600 names = set()
1601 paths = {}
1602 urls = {}
1603 for line in gitmodules_lines:
1604 if not line:
1605 continue
1606 m = re_path.match(line)
1607 if m:
1608 names.add(m.group(1))
1609 paths[m.group(1)] = m.group(2)
1610 continue
1611 m = re_url.match(line)
1612 if m:
1613 names.add(m.group(1))
1614 urls[m.group(1)] = m.group(2)
1615 continue
1616 names = sorted(names)
1617 return ([paths.get(name, '') for name in names],
1618 [urls.get(name, '') for name in names])
1619
1620 def git_ls_tree(gitdir, rev, paths):
1621 cmd = ['ls-tree', rev, '--']
1622 cmd.extend(paths)
1623 try:
1624 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1625 bare = True, gitdir = gitdir)
1626 except GitError:
1627 return []
1628 if p.Wait() != 0:
1629 return []
1630 objects = {}
1631 for line in p.stdout.split('\n'):
1632 if not line.strip():
1633 continue
1634 object_rev, object_path = line.split()[2:4]
1635 objects[object_path] = object_rev
1636 return objects
1637
1638 try:
1639 rev = self.GetRevisionId()
1640 except GitError:
1641 return []
1642 return get_submodules(self.gitdir, rev)
1643
1644 def GetDerivedSubprojects(self):
1645 result = []
1646 if not self.Exists:
1647 # If git repo does not exist yet, querying its submodules will
1648 # mess up its states; so return here.
1649 return result
1650 for rev, path, url in self._GetSubmodules():
1651 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001652 relpath, worktree, gitdir, objdir = \
1653 self.manifest.GetSubprojectPaths(self, name, path)
1654 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001655 if project:
1656 result.extend(project.GetDerivedSubprojects())
1657 continue
David James8d201162013-10-11 17:03:19 -07001658
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001659 remote = RemoteSpec(self.remote.name,
1660 url = url,
1661 review = self.remote.review)
1662 subproject = Project(manifest = self.manifest,
1663 name = name,
1664 remote = remote,
1665 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001666 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001667 worktree = worktree,
1668 relpath = relpath,
1669 revisionExpr = self.revisionExpr,
1670 revisionId = rev,
1671 rebase = self.rebase,
1672 groups = self.groups,
1673 sync_c = self.sync_c,
1674 sync_s = self.sync_s,
1675 parent = self,
1676 is_derived = True)
1677 result.append(subproject)
1678 result.extend(subproject.GetDerivedSubprojects())
1679 return result
1680
1681
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001682## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001683 def _CheckForSha1(self):
1684 try:
1685 # if revision (sha or tag) is not present then following function
1686 # throws an error.
1687 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1688 return True
1689 except GitError:
1690 # There is no such persistent revision. We have to fetch it.
1691 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001692
Julien Campergue335f5ef2013-10-16 11:02:35 +02001693 def _FetchArchive(self, tarpath, cwd=None):
1694 cmd = ['archive', '-v', '-o', tarpath]
1695 cmd.append('--remote=%s' % self.remote.url)
1696 cmd.append('--prefix=%s/' % self.relpath)
1697 cmd.append(self.revisionExpr)
1698
1699 command = GitCommand(self, cmd, cwd=cwd,
1700 capture_stdout=True,
1701 capture_stderr=True)
1702
1703 if command.Wait() != 0:
1704 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1705
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001706 def _RemoteFetch(self, name=None,
1707 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001708 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001709 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001710 alt_dir=None,
1711 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001712
1713 is_sha1 = False
1714 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001715 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001716
David Pursehouse9bc422f2014-04-15 10:28:56 +09001717 # The depth should not be used when fetching to a mirror because
1718 # it will result in a shallow repository that cannot be cloned or
1719 # fetched from.
1720 if not self.manifest.IsMirror:
1721 if self.clone_depth:
1722 depth = self.clone_depth
1723 else:
1724 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1725
Shawn Pearce69e04d82014-01-29 12:48:54 -08001726 if depth:
1727 current_branch_only = True
1728
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001729 if current_branch_only:
1730 if ID_RE.match(self.revisionExpr) is not None:
1731 is_sha1 = True
1732 elif self.revisionExpr.startswith(R_TAGS):
1733 # this is a tag and its sha1 value should never change
1734 tag_name = self.revisionExpr[len(R_TAGS):]
1735
1736 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001737 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001738 return True
Brian Harring14a66742012-09-28 20:21:57 -07001739 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1740 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001741
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001742 if not name:
1743 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001744
1745 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001746 remote = self.GetRemote(name)
1747 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001748 ssh_proxy = True
1749
Shawn O. Pearce88443382010-10-08 10:02:09 +02001750 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001751 if alt_dir and 'objects' == os.path.basename(alt_dir):
1752 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001753 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1754 remote = self.GetRemote(name)
1755
David Pursehouse8a68ff92012-09-24 12:15:13 +09001756 all_refs = self.bare_ref.all
1757 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001758 tmp = set()
1759
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301760 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001761 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001762 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001763 all_refs[r] = ref_id
1764 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001765 continue
1766
David Pursehouse8a68ff92012-09-24 12:15:13 +09001767 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001768 continue
1769
David Pursehouse8a68ff92012-09-24 12:15:13 +09001770 r = 'refs/_alt/%s' % ref_id
1771 all_refs[r] = ref_id
1772 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001773 tmp.add(r)
1774
Shawn O. Pearce88443382010-10-08 10:02:09 +02001775 tmp_packed = ''
1776 old_packed = ''
1777
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301778 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001779 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001780 tmp_packed += line
1781 if r not in tmp:
1782 old_packed += line
1783
1784 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001785 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001786 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001787
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001788 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001789
1790 # The --depth option only affects the initial fetch; after that we'll do
1791 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001792 if depth and initial:
1793 cmd.append('--depth=%s' % depth)
1794
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001795 if quiet:
1796 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001797 if not self.worktree:
1798 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001799 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001800
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001801 # If using depth then we should not get all the tags since they may
1802 # be outside of the depth.
1803 if no_tags or depth:
1804 cmd.append('--no-tags')
1805 else:
1806 cmd.append('--tags')
1807
Brian Harring14a66742012-09-28 20:21:57 -07001808 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001809 # Fetch whole repo
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301810 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001811 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001812 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001813 cmd.append(tag_name)
1814 else:
1815 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001816 if is_sha1:
1817 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001818 if branch.startswith(R_HEADS):
1819 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301820 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001821
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001822 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001823 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001824 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1825 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001826 ok = True
1827 break
Brian Harring14a66742012-09-28 20:21:57 -07001828 elif current_branch_only and is_sha1 and ret == 128:
1829 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1830 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1831 # abort the optimization attempt and do a full sync.
1832 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001833 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001834
1835 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001836 # Ensure that some refs exist. Otherwise, we probably aren't looking
1837 # at a real git repository and may have a bad url.
1838 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001839 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001840
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001841 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001842 if old_packed != '':
1843 _lwrite(packed_refs, old_packed)
1844 else:
1845 os.remove(packed_refs)
1846 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001847
1848 if is_sha1 and current_branch_only and self.upstream:
1849 # We just synced the upstream given branch; verify we
1850 # got what we wanted, else trigger a second run of all
1851 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001852 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001853 return self._RemoteFetch(name=name, current_branch_only=False,
1854 initial=False, quiet=quiet, alt_dir=alt_dir)
1855
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001856 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001857
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001858 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001859 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001860 return False
1861
1862 remote = self.GetRemote(self.remote.name)
1863 bundle_url = remote.url + '/clone.bundle'
1864 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001865 if GetSchemeFromUrl(bundle_url) not in (
1866 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001867 return False
1868
1869 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1870 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1871
1872 exist_dst = os.path.exists(bundle_dst)
1873 exist_tmp = os.path.exists(bundle_tmp)
1874
1875 if not initial and not exist_dst and not exist_tmp:
1876 return False
1877
1878 if not exist_dst:
1879 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1880 if not exist_dst:
1881 return False
1882
1883 cmd = ['fetch']
1884 if quiet:
1885 cmd.append('--quiet')
1886 if not self.worktree:
1887 cmd.append('--update-head-ok')
1888 cmd.append(bundle_dst)
1889 for f in remote.fetch:
1890 cmd.append(str(f))
1891 cmd.append('refs/tags/*:refs/tags/*')
1892
1893 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001894 if os.path.exists(bundle_dst):
1895 os.remove(bundle_dst)
1896 if os.path.exists(bundle_tmp):
1897 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001898 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001899
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001900 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001901 if os.path.exists(dstPath):
1902 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001903
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001904 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001905 if quiet:
1906 cmd += ['--silent']
1907 if os.path.exists(tmpPath):
1908 size = os.stat(tmpPath).st_size
1909 if size >= 1024:
1910 cmd += ['--continue-at', '%d' % (size,)]
1911 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001912 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001913 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1914 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001915 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001916 if cookiefile:
1917 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001918 if srcUrl.startswith('persistent-'):
1919 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001920 cmd += [srcUrl]
1921
1922 if IsTrace():
1923 Trace('%s', ' '.join(cmd))
1924 try:
1925 proc = subprocess.Popen(cmd)
1926 except OSError:
1927 return False
1928
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001929 curlret = proc.wait()
1930
1931 if curlret == 22:
1932 # From curl man page:
1933 # 22: HTTP page not retrieved. The requested url was not found or
1934 # returned another error with the HTTP error code being 400 or above.
1935 # This return code only appears if -f, --fail is used.
1936 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001937 print("Server does not provide clone.bundle; ignoring.",
1938 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001939 return False
1940
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001941 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001942 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001943 os.rename(tmpPath, dstPath)
1944 return True
1945 else:
1946 os.remove(tmpPath)
1947 return False
1948 else:
1949 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001950
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001951 def _IsValidBundle(self, path):
1952 try:
1953 with open(path) as f:
1954 if f.read(16) == '# v2 git bundle\n':
1955 return True
1956 else:
1957 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1958 return False
1959 except OSError:
1960 return False
1961
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001962 def _GetBundleCookieFile(self, url):
1963 if url.startswith('persistent-'):
1964 try:
1965 p = subprocess.Popen(
1966 ['git-remote-persistent-https', '-print_config', url],
1967 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1968 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001969 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001970 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001971 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001972 for line in p.stdout:
1973 line = line.strip()
1974 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001975 cookiefile = line[len(prefix):]
1976 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001977 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001978 err_msg = p.stderr.read()
1979 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001980 pass # Persistent proxy doesn't support -print_config.
1981 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001982 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001983 if cookiefile:
1984 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001985 except OSError as e:
1986 if e.errno == errno.ENOENT:
1987 pass # No persistent proxy.
1988 raise
1989 return GitConfig.ForUser().GetString('http.cookiefile')
1990
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001991 def _Checkout(self, rev, quiet=False):
1992 cmd = ['checkout']
1993 if quiet:
1994 cmd.append('-q')
1995 cmd.append(rev)
1996 cmd.append('--')
1997 if GitCommand(self, cmd).Wait() != 0:
1998 if self._allrefs:
1999 raise GitError('%s checkout %s ' % (self.name, rev))
2000
Pierre Tardye5a21222011-03-24 16:28:18 +01002001 def _CherryPick(self, rev, quiet=False):
2002 cmd = ['cherry-pick']
2003 cmd.append(rev)
2004 cmd.append('--')
2005 if GitCommand(self, cmd).Wait() != 0:
2006 if self._allrefs:
2007 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2008
Erwan Mahea94f1622011-08-19 13:56:09 +02002009 def _Revert(self, rev, quiet=False):
2010 cmd = ['revert']
2011 cmd.append('--no-edit')
2012 cmd.append(rev)
2013 cmd.append('--')
2014 if GitCommand(self, cmd).Wait() != 0:
2015 if self._allrefs:
2016 raise GitError('%s revert %s ' % (self.name, rev))
2017
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002018 def _ResetHard(self, rev, quiet=True):
2019 cmd = ['reset', '--hard']
2020 if quiet:
2021 cmd.append('-q')
2022 cmd.append(rev)
2023 if GitCommand(self, cmd).Wait() != 0:
2024 raise GitError('%s reset --hard %s ' % (self.name, rev))
2025
2026 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002027 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002028 if onto is not None:
2029 cmd.extend(['--onto', onto])
2030 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002031 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002032 raise GitError('%s rebase %s ' % (self.name, upstream))
2033
Pierre Tardy3d125942012-05-04 12:18:12 +02002034 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002035 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002036 if ffonly:
2037 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002038 if GitCommand(self, cmd).Wait() != 0:
2039 raise GitError('%s merge %s ' % (self.name, head))
2040
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002041 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002042 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002043
2044 # Initialize the bare repository, which contains all of the objects.
2045 if not os.path.exists(self.objdir):
2046 os.makedirs(self.objdir)
2047 self.bare_objdir.init()
2048
2049 # If we have a separate directory to hold refs, initialize it as well.
2050 if self.objdir != self.gitdir:
2051 os.makedirs(self.gitdir)
2052 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2053 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002054
Shawn O. Pearce88443382010-10-08 10:02:09 +02002055 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002056 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002057
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002058 if ref_dir or mirror_git:
2059 if not mirror_git:
2060 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002061 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2062 self.relpath + '.git')
2063
2064 if os.path.exists(mirror_git):
2065 ref_dir = mirror_git
2066
2067 elif os.path.exists(repo_git):
2068 ref_dir = repo_git
2069
2070 else:
2071 ref_dir = None
2072
2073 if ref_dir:
2074 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2075 os.path.join(ref_dir, 'objects') + '\n')
2076
Jimmie Westera0444582012-10-24 13:44:42 +02002077 self._UpdateHooks()
2078
2079 m = self.manifest.manifestProject.config
2080 for key in ['user.name', 'user.email']:
2081 if m.Has(key, include_defaults = False):
2082 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002083 if self.manifest.IsMirror:
2084 self.config.SetString('core.bare', 'true')
2085 else:
2086 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002087
Jimmie Westera0444582012-10-24 13:44:42 +02002088 def _UpdateHooks(self):
2089 if os.path.exists(self.gitdir):
2090 # Always recreate hooks since they can have been changed
2091 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002092 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002093 try:
2094 to_rm = os.listdir(hooks)
2095 except OSError:
2096 to_rm = []
2097 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002098 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002099 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002100
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002101 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002102 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002103 if not os.path.exists(hooks):
2104 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002105 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002106 name = os.path.basename(stock_hook)
2107
Victor Boivie65e0f352011-04-18 11:23:29 +02002108 if name in ('commit-msg',) and not self.remote.review \
2109 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002110 # Don't install a Gerrit Code Review hook if this
2111 # project does not appear to use it for reviews.
2112 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002113 # Since the manifest project is one of those, but also
2114 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002115 continue
2116
2117 dst = os.path.join(hooks, name)
2118 if os.path.islink(dst):
2119 continue
2120 if os.path.exists(dst):
2121 if filecmp.cmp(stock_hook, dst, shallow=False):
2122 os.remove(dst)
2123 else:
2124 _error("%s: Not replacing %s hook", self.relpath, name)
2125 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002126 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002127 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002128 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002129 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002130 raise GitError('filesystem must support symlinks')
2131 else:
2132 raise
2133
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002135 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002136 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002137 remote.url = self.remote.url
2138 remote.review = self.remote.review
2139 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002140
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002141 if self.worktree:
2142 remote.ResetFetch(mirror=False)
2143 else:
2144 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002145 remote.Save()
2146
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002147 def _InitMRef(self):
2148 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002149 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002150
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002151 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002152 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002153
2154 def _InitAnyMRef(self, ref):
2155 cur = self.bare_ref.symref(ref)
2156
2157 if self.revisionId:
2158 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2159 msg = 'manifest set to %s' % self.revisionId
2160 dst = self.revisionId + '^0'
2161 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2162 else:
2163 remote = self.GetRemote(self.remote.name)
2164 dst = remote.ToLocal(self.revisionExpr)
2165 if cur != dst:
2166 msg = 'manifest set to %s' % self.revisionExpr
2167 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002168
David James8d201162013-10-11 17:03:19 -07002169 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2170 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2171
2172 Args:
2173 gitdir: The bare git repository. Must already be initialized.
2174 dotgit: The repository you would like to initialize.
2175 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2176 Only one work tree can store refs under a given |gitdir|.
2177 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2178 This saves you the effort of initializing |dotgit| yourself.
2179 """
2180 # These objects can be shared between several working trees.
2181 symlink_files = ['description', 'info']
2182 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2183 if share_refs:
2184 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002185 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002186 symlink_dirs += ['logs', 'refs']
2187 to_symlink = symlink_files + symlink_dirs
2188
2189 to_copy = []
2190 if copy_all:
2191 to_copy = os.listdir(gitdir)
2192
2193 for name in set(to_copy).union(to_symlink):
2194 try:
2195 src = os.path.realpath(os.path.join(gitdir, name))
2196 dst = os.path.realpath(os.path.join(dotgit, name))
2197
2198 if os.path.lexists(dst) and not os.path.islink(dst):
2199 raise GitError('cannot overwrite a local work tree')
2200
2201 # If the source dir doesn't exist, create an empty dir.
2202 if name in symlink_dirs and not os.path.lexists(src):
2203 os.makedirs(src)
2204
2205 if name in to_symlink:
2206 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2207 elif copy_all and not os.path.islink(dst):
2208 if os.path.isdir(src):
2209 shutil.copytree(src, dst)
2210 elif os.path.isfile(src):
2211 shutil.copy(src, dst)
2212 except OSError as e:
2213 if e.errno == errno.EPERM:
2214 raise GitError('filesystem must support symlinks')
2215 else:
2216 raise
2217
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002218 def _InitWorkTree(self):
2219 dotgit = os.path.join(self.worktree, '.git')
2220 if not os.path.exists(dotgit):
2221 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002222 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2223 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002224
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002225 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002226
2227 cmd = ['read-tree', '--reset', '-u']
2228 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002229 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002230 if GitCommand(self, cmd).Wait() != 0:
2231 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002232
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002233 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002234
2235 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002236 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002237
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002238 def _revlist(self, *args, **kw):
2239 a = []
2240 a.extend(args)
2241 a.append('--')
2242 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002243
2244 @property
2245 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002246 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002247
Julien Camperguedd654222014-01-09 16:21:37 +01002248 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2249 """Get logs between two revisions of this project."""
2250 comp = '..'
2251 if rev1:
2252 revs = [rev1]
2253 if rev2:
2254 revs.extend([comp, rev2])
2255 cmd = ['log', ''.join(revs)]
2256 out = DiffColoring(self.config)
2257 if out.is_on and color:
2258 cmd.append('--color')
2259 if oneline:
2260 cmd.append('--oneline')
2261
2262 try:
2263 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2264 if log.Wait() == 0:
2265 return log.stdout
2266 except GitError:
2267 # worktree may not exist if groups changed for example. In that case,
2268 # try in gitdir instead.
2269 if not os.path.exists(self.worktree):
2270 return self.bare_git.log(*cmd[1:])
2271 else:
2272 raise
2273 return None
2274
2275 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2276 """Get the list of logs from this revision to given revisionId"""
2277 logs = {}
2278 selfId = self.GetRevisionId(self._allrefs)
2279 toId = toProject.GetRevisionId(toProject._allrefs)
2280
2281 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2282 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2283 return logs
2284
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002285 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002286 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002287 self._project = project
2288 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002289 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002290
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002291 def LsOthers(self):
2292 p = GitCommand(self._project,
2293 ['ls-files',
2294 '-z',
2295 '--others',
2296 '--exclude-standard'],
2297 bare = False,
David James8d201162013-10-11 17:03:19 -07002298 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002299 capture_stdout = True,
2300 capture_stderr = True)
2301 if p.Wait() == 0:
2302 out = p.stdout
2303 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002304 return out[:-1].split('\0') # pylint: disable=W1401
2305 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002306 return []
2307
2308 def DiffZ(self, name, *args):
2309 cmd = [name]
2310 cmd.append('-z')
2311 cmd.extend(args)
2312 p = GitCommand(self._project,
2313 cmd,
David James8d201162013-10-11 17:03:19 -07002314 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002315 bare = False,
2316 capture_stdout = True,
2317 capture_stderr = True)
2318 try:
2319 out = p.process.stdout.read()
2320 r = {}
2321 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002322 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002323 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002324 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002325 info = next(out)
2326 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002327 except StopIteration:
2328 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002329
2330 class _Info(object):
2331 def __init__(self, path, omode, nmode, oid, nid, state):
2332 self.path = path
2333 self.src_path = None
2334 self.old_mode = omode
2335 self.new_mode = nmode
2336 self.old_id = oid
2337 self.new_id = nid
2338
2339 if len(state) == 1:
2340 self.status = state
2341 self.level = None
2342 else:
2343 self.status = state[:1]
2344 self.level = state[1:]
2345 while self.level.startswith('0'):
2346 self.level = self.level[1:]
2347
2348 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002349 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002350 if info.status in ('R', 'C'):
2351 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002352 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002353 r[info.path] = info
2354 return r
2355 finally:
2356 p.Wait()
2357
2358 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002359 if self._bare:
2360 path = os.path.join(self._project.gitdir, HEAD)
2361 else:
2362 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002363 try:
2364 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002365 except IOError as e:
2366 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002367 try:
2368 line = fd.read()
2369 finally:
2370 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302371 try:
2372 line = line.decode()
2373 except AttributeError:
2374 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002375 if line.startswith('ref: '):
2376 return line[5:-1]
2377 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002378
2379 def SetHead(self, ref, message=None):
2380 cmdv = []
2381 if message is not None:
2382 cmdv.extend(['-m', message])
2383 cmdv.append(HEAD)
2384 cmdv.append(ref)
2385 self.symbolic_ref(*cmdv)
2386
2387 def DetachHead(self, new, message=None):
2388 cmdv = ['--no-deref']
2389 if message is not None:
2390 cmdv.extend(['-m', message])
2391 cmdv.append(HEAD)
2392 cmdv.append(new)
2393 self.update_ref(*cmdv)
2394
2395 def UpdateRef(self, name, new, old=None,
2396 message=None,
2397 detach=False):
2398 cmdv = []
2399 if message is not None:
2400 cmdv.extend(['-m', message])
2401 if detach:
2402 cmdv.append('--no-deref')
2403 cmdv.append(name)
2404 cmdv.append(new)
2405 if old is not None:
2406 cmdv.append(old)
2407 self.update_ref(*cmdv)
2408
2409 def DeleteRef(self, name, old=None):
2410 if not old:
2411 old = self.rev_parse(name)
2412 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002413 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002414
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002415 def rev_list(self, *args, **kw):
2416 if 'format' in kw:
2417 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2418 else:
2419 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002420 cmdv.extend(args)
2421 p = GitCommand(self._project,
2422 cmdv,
2423 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002424 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002425 capture_stdout = True,
2426 capture_stderr = True)
2427 r = []
2428 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002429 if line[-1] == '\n':
2430 line = line[:-1]
2431 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002432 if p.Wait() != 0:
2433 raise GitError('%s rev-list %s: %s' % (
2434 self._project.name,
2435 str(args),
2436 p.stderr))
2437 return r
2438
2439 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002440 """Allow arbitrary git commands using pythonic syntax.
2441
2442 This allows you to do things like:
2443 git_obj.rev_parse('HEAD')
2444
2445 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2446 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002447 Any other positional arguments will be passed to the git command, and the
2448 following keyword arguments are supported:
2449 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002450
2451 Args:
2452 name: The name of the git command to call. Any '_' characters will
2453 be replaced with '-'.
2454
2455 Returns:
2456 A callable object that will try to call git with the named command.
2457 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002458 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002459 def runner(*args, **kwargs):
2460 cmdv = []
2461 config = kwargs.pop('config', None)
2462 for k in kwargs:
2463 raise TypeError('%s() got an unexpected keyword argument %r'
2464 % (name, k))
2465 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002466 if not git_require((1, 7, 2)):
2467 raise ValueError('cannot set config on command line for %s()'
2468 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302469 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002470 cmdv.append('-c')
2471 cmdv.append('%s=%s' % (k, v))
2472 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002473 cmdv.extend(args)
2474 p = GitCommand(self._project,
2475 cmdv,
2476 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002477 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002478 capture_stdout = True,
2479 capture_stderr = True)
2480 if p.Wait() != 0:
2481 raise GitError('%s %s: %s' % (
2482 self._project.name,
2483 name,
2484 p.stderr))
2485 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302486 try:
Conley Owensedd01512013-09-26 12:59:58 -07002487 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302488 except AttributeError:
2489 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002490 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2491 return r[:-1]
2492 return r
2493 return runner
2494
2495
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002496class _PriorSyncFailedError(Exception):
2497 def __str__(self):
2498 return 'prior sync failed; rebase still in progress'
2499
2500class _DirtyError(Exception):
2501 def __str__(self):
2502 return 'contains uncommitted changes'
2503
2504class _InfoMessage(object):
2505 def __init__(self, project, text):
2506 self.project = project
2507 self.text = text
2508
2509 def Print(self, syncbuf):
2510 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2511 syncbuf.out.nl()
2512
2513class _Failure(object):
2514 def __init__(self, project, why):
2515 self.project = project
2516 self.why = why
2517
2518 def Print(self, syncbuf):
2519 syncbuf.out.fail('error: %s/: %s',
2520 self.project.relpath,
2521 str(self.why))
2522 syncbuf.out.nl()
2523
2524class _Later(object):
2525 def __init__(self, project, action):
2526 self.project = project
2527 self.action = action
2528
2529 def Run(self, syncbuf):
2530 out = syncbuf.out
2531 out.project('project %s/', self.project.relpath)
2532 out.nl()
2533 try:
2534 self.action()
2535 out.nl()
2536 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002537 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002538 out.nl()
2539 return False
2540
2541class _SyncColoring(Coloring):
2542 def __init__(self, config):
2543 Coloring.__init__(self, config, 'reposync')
2544 self.project = self.printer('header', attr = 'bold')
2545 self.info = self.printer('info')
2546 self.fail = self.printer('fail', fg='red')
2547
2548class SyncBuffer(object):
2549 def __init__(self, config, detach_head=False):
2550 self._messages = []
2551 self._failures = []
2552 self._later_queue1 = []
2553 self._later_queue2 = []
2554
2555 self.out = _SyncColoring(config)
2556 self.out.redirect(sys.stderr)
2557
2558 self.detach_head = detach_head
2559 self.clean = True
2560
2561 def info(self, project, fmt, *args):
2562 self._messages.append(_InfoMessage(project, fmt % args))
2563
2564 def fail(self, project, err=None):
2565 self._failures.append(_Failure(project, err))
2566 self.clean = False
2567
2568 def later1(self, project, what):
2569 self._later_queue1.append(_Later(project, what))
2570
2571 def later2(self, project, what):
2572 self._later_queue2.append(_Later(project, what))
2573
2574 def Finish(self):
2575 self._PrintMessages()
2576 self._RunLater()
2577 self._PrintMessages()
2578 return self.clean
2579
2580 def _RunLater(self):
2581 for q in ['_later_queue1', '_later_queue2']:
2582 if not self._RunQueue(q):
2583 return
2584
2585 def _RunQueue(self, queue):
2586 for m in getattr(self, queue):
2587 if not m.Run(self):
2588 self.clean = False
2589 return False
2590 setattr(self, queue, [])
2591 return True
2592
2593 def _PrintMessages(self):
2594 for m in self._messages:
2595 m.Print(self)
2596 for m in self._failures:
2597 m.Print(self)
2598
2599 self._messages = []
2600 self._failures = []
2601
2602
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002603class MetaProject(Project):
2604 """A special project housed under .repo.
2605 """
2606 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002607 Project.__init__(self,
2608 manifest = manifest,
2609 name = name,
2610 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002611 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002612 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002613 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002614 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002615 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002616 revisionId = None,
2617 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002618
2619 def PreSync(self):
2620 if self.Exists:
2621 cb = self.CurrentBranch
2622 if cb:
2623 base = self.GetBranch(cb).merge
2624 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002625 self.revisionExpr = base
2626 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002627
Florian Vallee5d016502012-06-07 17:19:26 +02002628 def MetaBranchSwitch(self, target):
2629 """ Prepare MetaProject for manifest branch switch
2630 """
2631
2632 # detach and delete manifest branch, allowing a new
2633 # branch to take over
2634 syncbuf = SyncBuffer(self.config, detach_head = True)
2635 self.Sync_LocalHalf(syncbuf)
2636 syncbuf.Finish()
2637
2638 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002639 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002640 capture_stdout = True,
2641 capture_stderr = True).Wait() == 0
2642
2643
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002644 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002645 def LastFetch(self):
2646 try:
2647 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2648 return os.path.getmtime(fh)
2649 except OSError:
2650 return 0
2651
2652 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002653 def HasChanges(self):
2654 """Has the remote received new commits not yet checked out?
2655 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002656 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002657 return False
2658
David Pursehouse8a68ff92012-09-24 12:15:13 +09002659 all_refs = self.bare_ref.all
2660 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002661 head = self.work_git.GetHead()
2662 if head.startswith(R_HEADS):
2663 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002664 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002665 except KeyError:
2666 head = None
2667
2668 if revid == head:
2669 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002670 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002671 return True
2672 return False