blob: e4ed7d03999d6aefe0bcf086addc94403614810d [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Doug Anderson37282b42011-03-04 11:54:18 -080016import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080017import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import filecmp
19import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070029
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070031from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070032from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090033from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080034from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080035from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070036from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037
Shawn O. Pearced237b692009-04-17 18:49:50 -070038from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
David Pursehouse59bbb582013-05-17 10:49:33 +090040from pyversion import is_python3
41if not is_python3():
42 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053043 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090044 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053045
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070046def _lwrite(path, content):
47 lock = '%s.lock' % path
48
49 fd = open(lock, 'wb')
50 try:
51 fd.write(content)
52 finally:
53 fd.close()
54
55 try:
56 os.rename(lock, path)
57 except OSError:
58 os.remove(lock)
59 raise
60
Shawn O. Pearce48244782009-04-16 08:25:57 -070061def _error(fmt, *args):
62 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070063 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070064
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070065def not_rev(r):
66 return '^' + r
67
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080068def sq(r):
69 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080070
Doug Anderson8ced8642011-01-10 14:16:30 -080071_project_hook_list = None
72def _ProjectHooks():
73 """List the hooks present in the 'hooks' directory.
74
75 These hooks are project hooks and are copied to the '.git/hooks' directory
76 of all subprojects.
77
78 This function caches the list of hooks (based on the contents of the
79 'repo/hooks' directory) on the first call.
80
81 Returns:
82 A list of absolute paths to all of the files in the hooks directory.
83 """
84 global _project_hook_list
85 if _project_hook_list is None:
Jesse Hall672cc492013-11-27 11:17:13 -080086 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087 d = os.path.join(d , 'hooks')
Chirayu Desai217ea7d2013-03-01 19:14:38 +053088 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
Doug Anderson8ced8642011-01-10 14:16:30 -080089 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080090
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080091
Shawn O. Pearce632768b2008-10-23 11:58:52 -070092class DownloadedChange(object):
93 _commit_cache = None
94
95 def __init__(self, project, base, change_id, ps_id, commit):
96 self.project = project
97 self.base = base
98 self.change_id = change_id
99 self.ps_id = ps_id
100 self.commit = commit
101
102 @property
103 def commits(self):
104 if self._commit_cache is None:
105 self._commit_cache = self.project.bare_git.rev_list(
106 '--abbrev=8',
107 '--abbrev-commit',
108 '--pretty=oneline',
109 '--reverse',
110 '--date-order',
111 not_rev(self.base),
112 self.commit,
113 '--')
114 return self._commit_cache
115
116
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700117class ReviewableBranch(object):
118 _commit_cache = None
119
120 def __init__(self, project, branch, base):
121 self.project = project
122 self.branch = branch
123 self.base = base
124
125 @property
126 def name(self):
127 return self.branch.name
128
129 @property
130 def commits(self):
131 if self._commit_cache is None:
132 self._commit_cache = self.project.bare_git.rev_list(
133 '--abbrev=8',
134 '--abbrev-commit',
135 '--pretty=oneline',
136 '--reverse',
137 '--date-order',
138 not_rev(self.base),
139 R_HEADS + self.name,
140 '--')
141 return self._commit_cache
142
143 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800144 def unabbrev_commits(self):
145 r = dict()
146 for commit in self.project.bare_git.rev_list(
147 not_rev(self.base),
148 R_HEADS + self.name,
149 '--'):
150 r[commit[0:8]] = commit
151 return r
152
153 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154 def date(self):
155 return self.project.bare_git.log(
156 '--pretty=format:%cd',
157 '-n', '1',
158 R_HEADS + self.name,
159 '--')
160
Bryan Jacobsf609f912013-05-06 13:36:24 -0400161 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700163 people,
Brian Harring435370c2012-07-28 15:37:04 -0700164 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400165 draft=draft,
166 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700168 def GetPublishedRefs(self):
169 refs = {}
170 output = self.project.bare_git.ls_remote(
171 self.branch.remote.SshReviewUrl(self.project.UserEmail),
172 'refs/changes/*')
173 for line in output.split('\n'):
174 try:
175 (sha, ref) = line.split()
176 refs[sha] = ref
177 except ValueError:
178 pass
179
180 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
182class StatusColoring(Coloring):
183 def __init__(self, config):
184 Coloring.__init__(self, config, 'status')
185 self.project = self.printer('header', attr = 'bold')
186 self.branch = self.printer('header', attr = 'bold')
187 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700188 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700189
190 self.added = self.printer('added', fg = 'green')
191 self.changed = self.printer('changed', fg = 'red')
192 self.untracked = self.printer('untracked', fg = 'red')
193
194
195class DiffColoring(Coloring):
196 def __init__(self, config):
197 Coloring.__init__(self, config, 'diff')
198 self.project = self.printer('header', attr = 'bold')
199
James W. Mills24c13082012-04-12 15:04:13 -0500200class _Annotation:
201 def __init__(self, name, value, keep):
202 self.name = name
203 self.value = value
204 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205
206class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800207 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 self.src = src
209 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800210 self.abs_src = abssrc
211 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700212
213 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800214 src = self.abs_src
215 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 # copy file if it does not exist or is out of date
217 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
218 try:
219 # remove existing file first, since it might be read-only
220 if os.path.exists(dest):
221 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400222 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200223 dest_dir = os.path.dirname(dest)
224 if not os.path.isdir(dest_dir):
225 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226 shutil.copy(src, dest)
227 # make the file read-only
228 mode = os.stat(dest)[stat.ST_MODE]
229 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
230 os.chmod(dest, mode)
231 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700232 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500234class _LinkFile:
235 def __init__(self, src, dest, abssrc, absdest):
236 self.src = src
237 self.dest = dest
238 self.abs_src = abssrc
239 self.abs_dest = absdest
240
241 def _Link(self):
242 src = self.abs_src
243 dest = self.abs_dest
244 # link file if it does not exist or is out of date
245 if not os.path.islink(dest) or os.readlink(dest) != src:
246 try:
247 # remove existing file first, since it might be read-only
248 if os.path.exists(dest):
249 os.remove(dest)
250 else:
251 dest_dir = os.path.dirname(dest)
252 if not os.path.isdir(dest_dir):
253 os.makedirs(dest_dir)
254 os.symlink(src, dest)
255 except IOError:
256 _error('Cannot link file %s to %s', src, dest)
257
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700258class RemoteSpec(object):
259 def __init__(self,
260 name,
261 url = None,
262 review = None):
263 self.name = name
264 self.url = url
265 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700266
Doug Anderson37282b42011-03-04 11:54:18 -0800267class RepoHook(object):
268 """A RepoHook contains information about a script to run as a hook.
269
270 Hooks are used to run a python script before running an upload (for instance,
271 to run presubmit checks). Eventually, we may have hooks for other actions.
272
273 This shouldn't be confused with files in the 'repo/hooks' directory. Those
274 files are copied into each '.git/hooks' folder for each project. Repo-level
275 hooks are associated instead with repo actions.
276
277 Hooks are always python. When a hook is run, we will load the hook into the
278 interpreter and execute its main() function.
279 """
280 def __init__(self,
281 hook_type,
282 hooks_project,
283 topdir,
284 abort_if_user_denies=False):
285 """RepoHook constructor.
286
287 Params:
288 hook_type: A string representing the type of hook. This is also used
289 to figure out the name of the file containing the hook. For
290 example: 'pre-upload'.
291 hooks_project: The project containing the repo hooks. If you have a
292 manifest, this is manifest.repo_hooks_project. OK if this is None,
293 which will make the hook a no-op.
294 topdir: Repo's top directory (the one containing the .repo directory).
295 Scripts will run with CWD as this directory. If you have a manifest,
296 this is manifest.topdir
297 abort_if_user_denies: If True, we'll throw a HookError() if the user
298 doesn't allow us to run the hook.
299 """
300 self._hook_type = hook_type
301 self._hooks_project = hooks_project
302 self._topdir = topdir
303 self._abort_if_user_denies = abort_if_user_denies
304
305 # Store the full path to the script for convenience.
306 if self._hooks_project:
307 self._script_fullpath = os.path.join(self._hooks_project.worktree,
308 self._hook_type + '.py')
309 else:
310 self._script_fullpath = None
311
312 def _GetHash(self):
313 """Return a hash of the contents of the hooks directory.
314
315 We'll just use git to do this. This hash has the property that if anything
316 changes in the directory we will return a different has.
317
318 SECURITY CONSIDERATION:
319 This hash only represents the contents of files in the hook directory, not
320 any other files imported or called by hooks. Changes to imported files
321 can change the script behavior without affecting the hash.
322
323 Returns:
324 A string representing the hash. This will always be ASCII so that it can
325 be printed to the user easily.
326 """
327 assert self._hooks_project, "Must have hooks to calculate their hash."
328
329 # We will use the work_git object rather than just calling GetRevisionId().
330 # That gives us a hash of the latest checked in version of the files that
331 # the user will actually be executing. Specifically, GetRevisionId()
332 # doesn't appear to change even if a user checks out a different version
333 # of the hooks repo (via git checkout) nor if a user commits their own revs.
334 #
335 # NOTE: Local (non-committed) changes will not be factored into this hash.
336 # I think this is OK, since we're really only worried about warning the user
337 # about upstream changes.
338 return self._hooks_project.work_git.rev_parse('HEAD')
339
340 def _GetMustVerb(self):
341 """Return 'must' if the hook is required; 'should' if not."""
342 if self._abort_if_user_denies:
343 return 'must'
344 else:
345 return 'should'
346
347 def _CheckForHookApproval(self):
348 """Check to see whether this hook has been approved.
349
350 We'll look at the hash of all of the hooks. If this matches the hash that
351 the user last approved, we're done. If it doesn't, we'll ask the user
352 about approval.
353
354 Note that we ask permission for each individual hook even though we use
355 the hash of all hooks when detecting changes. We'd like the user to be
356 able to approve / deny each hook individually. We only use the hash of all
357 hooks because there is no other easy way to detect changes to local imports.
358
359 Returns:
360 True if this hook is approved to run; False otherwise.
361
362 Raises:
363 HookError: Raised if the user doesn't approve and abort_if_user_denies
364 was passed to the consturctor.
365 """
Doug Anderson37282b42011-03-04 11:54:18 -0800366 hooks_config = self._hooks_project.config
367 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
368
369 # Get the last hash that the user approved for this hook; may be None.
370 old_hash = hooks_config.GetString(git_approval_key)
371
372 # Get the current hash so we can tell if scripts changed since approval.
373 new_hash = self._GetHash()
374
375 if old_hash is not None:
376 # User previously approved hook and asked not to be prompted again.
377 if new_hash == old_hash:
378 # Approval matched. We're done.
379 return True
380 else:
381 # Give the user a reason why we're prompting, since they last told
382 # us to "never ask again".
383 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
384 self._hook_type)
385 else:
386 prompt = ''
387
388 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
389 if sys.stdout.isatty():
390 prompt += ('Repo %s run the script:\n'
391 ' %s\n'
392 '\n'
393 'Do you want to allow this script to run '
394 '(yes/yes-never-ask-again/NO)? ') % (
395 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530396 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900397 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800398
399 # User is doing a one-time approval.
400 if response in ('y', 'yes'):
401 return True
402 elif response == 'yes-never-ask-again':
403 hooks_config.SetString(git_approval_key, new_hash)
404 return True
405
406 # For anything else, we'll assume no approval.
407 if self._abort_if_user_denies:
408 raise HookError('You must allow the %s hook or use --no-verify.' %
409 self._hook_type)
410
411 return False
412
413 def _ExecuteHook(self, **kwargs):
414 """Actually execute the given hook.
415
416 This will run the hook's 'main' function in our python interpreter.
417
418 Args:
419 kwargs: Keyword arguments to pass to the hook. These are often specific
420 to the hook type. For instance, pre-upload hooks will contain
421 a project_list.
422 """
423 # Keep sys.path and CWD stashed away so that we can always restore them
424 # upon function exit.
425 orig_path = os.getcwd()
426 orig_syspath = sys.path
427
428 try:
429 # Always run hooks with CWD as topdir.
430 os.chdir(self._topdir)
431
432 # Put the hook dir as the first item of sys.path so hooks can do
433 # relative imports. We want to replace the repo dir as [0] so
434 # hooks can't import repo files.
435 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
436
437 # Exec, storing global context in the context dict. We catch exceptions
438 # and convert to a HookError w/ just the failing traceback.
439 context = {}
440 try:
441 execfile(self._script_fullpath, context)
442 except Exception:
443 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
444 traceback.format_exc(), self._hook_type))
445
446 # Running the script should have defined a main() function.
447 if 'main' not in context:
448 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
449
450
451 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
452 # We don't actually want hooks to define their main with this argument--
453 # it's there to remind them that their hook should always take **kwargs.
454 # For instance, a pre-upload hook should be defined like:
455 # def main(project_list, **kwargs):
456 #
457 # This allows us to later expand the API without breaking old hooks.
458 kwargs = kwargs.copy()
459 kwargs['hook_should_take_kwargs'] = True
460
461 # Call the main function in the hook. If the hook should cause the
462 # build to fail, it will raise an Exception. We'll catch that convert
463 # to a HookError w/ just the failing traceback.
464 try:
465 context['main'](**kwargs)
466 except Exception:
467 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
468 'above.' % (
469 traceback.format_exc(), self._hook_type))
470 finally:
471 # Restore sys.path and CWD.
472 sys.path = orig_syspath
473 os.chdir(orig_path)
474
475 def Run(self, user_allows_all_hooks, **kwargs):
476 """Run the hook.
477
478 If the hook doesn't exist (because there is no hooks project or because
479 this particular hook is not enabled), this is a no-op.
480
481 Args:
482 user_allows_all_hooks: If True, we will never prompt about running the
483 hook--we'll just assume it's OK to run it.
484 kwargs: Keyword arguments to pass to the hook. These are often specific
485 to the hook type. For instance, pre-upload hooks will contain
486 a project_list.
487
488 Raises:
489 HookError: If there was a problem finding the hook or the user declined
490 to run a required hook (from _CheckForHookApproval).
491 """
492 # No-op if there is no hooks project or if hook is disabled.
493 if ((not self._hooks_project) or
494 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
495 return
496
497 # Bail with a nice error if we can't find the hook.
498 if not os.path.isfile(self._script_fullpath):
499 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
500
501 # Make sure the user is OK with running the hook.
502 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
503 return
504
505 # Run the hook with the same version of python we're using.
506 self._ExecuteHook(**kwargs)
507
508
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700509class Project(object):
510 def __init__(self,
511 manifest,
512 name,
513 remote,
514 gitdir,
David James8d201162013-10-11 17:03:19 -0700515 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700516 worktree,
517 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700518 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800519 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700520 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700521 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700522 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800523 sync_s = False,
David Pursehouseede7f122012-11-27 22:25:30 +0900524 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800525 upstream = None,
526 parent = None,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400527 is_derived = False,
natalie.chenb7852052015-03-10 16:58:49 +0800528 dest_branch = None,
529 localcache = 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 = []
natalie.chenb7852052015-03-10 16:58:49 +0800601 self.localcache = None
602 if localcache:
603 self.localcache = localcache.replace('\\', '/')
604 self.bare_cache = self._GitGetByExec(self, bare=True, gitdir=localcache)
Doug Anderson37282b42011-03-04 11:54:18 -0800605
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700606 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800607 def Derived(self):
608 return self.is_derived
609
610 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700611 def Exists(self):
612 return os.path.isdir(self.gitdir)
613
614 @property
615 def CurrentBranch(self):
616 """Obtain the name of the currently checked out branch.
617 The branch name omits the 'refs/heads/' prefix.
618 None is returned if the project is on a detached HEAD.
619 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700620 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700621 if b.startswith(R_HEADS):
622 return b[len(R_HEADS):]
623 return None
624
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700625 def IsRebaseInProgress(self):
626 w = self.worktree
627 g = os.path.join(w, '.git')
628 return os.path.exists(os.path.join(g, 'rebase-apply')) \
629 or os.path.exists(os.path.join(g, 'rebase-merge')) \
630 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200631
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700632 def IsDirty(self, consider_untracked=True):
633 """Is the working directory modified in some way?
634 """
635 self.work_git.update_index('-q',
636 '--unmerged',
637 '--ignore-missing',
638 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900639 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700640 return True
641 if self.work_git.DiffZ('diff-files'):
642 return True
643 if consider_untracked and self.work_git.LsOthers():
644 return True
645 return False
646
647 _userident_name = None
648 _userident_email = None
649
650 @property
651 def UserName(self):
652 """Obtain the user's personal name.
653 """
654 if self._userident_name is None:
655 self._LoadUserIdentity()
656 return self._userident_name
657
658 @property
659 def UserEmail(self):
660 """Obtain the user's email address. This is very likely
661 to be their Gerrit login.
662 """
663 if self._userident_email is None:
664 self._LoadUserIdentity()
665 return self._userident_email
666
667 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900668 u = self.bare_git.var('GIT_COMMITTER_IDENT')
669 m = re.compile("^(.*) <([^>]*)> ").match(u)
670 if m:
671 self._userident_name = m.group(1)
672 self._userident_email = m.group(2)
673 else:
674 self._userident_name = ''
675 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700676
677 def GetRemote(self, name):
678 """Get the configuration for a single remote.
679 """
680 return self.config.GetRemote(name)
681
682 def GetBranch(self, name):
683 """Get the configuration for a single branch.
684 """
685 return self.config.GetBranch(name)
686
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700687 def GetBranches(self):
688 """Get all existing local branches.
689 """
690 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900691 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700692 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700693
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530694 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700695 if name.startswith(R_HEADS):
696 name = name[len(R_HEADS):]
697 b = self.GetBranch(name)
698 b.current = name == current
699 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900700 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700701 heads[name] = b
702
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530703 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700704 if name.startswith(R_PUB):
705 name = name[len(R_PUB):]
706 b = heads.get(name)
707 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900708 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700709
710 return heads
711
Colin Cross5acde752012-03-28 20:15:45 -0700712 def MatchesGroups(self, manifest_groups):
713 """Returns true if the manifest groups specified at init should cause
714 this project to be synced.
715 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700716 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700717
Conley Owens971de8e2012-04-16 10:36:08 -0700718 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700719 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700720 manifest_groups: "-group1,group2"
721 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500722
723 The special manifest group "default" will match any project that
724 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700725 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500726 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700727 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500728 if not 'notdefault' in expanded_project_groups:
729 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700730
Conley Owens971de8e2012-04-16 10:36:08 -0700731 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700732 for group in expanded_manifest_groups:
733 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700734 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700735 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700736 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700737
Conley Owens971de8e2012-04-16 10:36:08 -0700738 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700739
740## Status Display ##
741
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500742 def HasChanges(self):
743 """Returns true if there are uncommitted changes.
744 """
745 self.work_git.update_index('-q',
746 '--unmerged',
747 '--ignore-missing',
748 '--refresh')
749 if self.IsRebaseInProgress():
750 return True
751
752 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
753 return True
754
755 if self.work_git.DiffZ('diff-files'):
756 return True
757
758 if self.work_git.LsOthers():
759 return True
760
761 return False
762
Terence Haddock4655e812011-03-31 12:33:34 +0200763 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700764 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200765
766 Args:
767 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700768 """
769 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200770 if output_redir == None:
771 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700772 print(file=output_redir)
773 print('project %s/' % self.relpath, file=output_redir)
774 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775 return
776
777 self.work_git.update_index('-q',
778 '--unmerged',
779 '--ignore-missing',
780 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700781 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
783 df = self.work_git.DiffZ('diff-files')
784 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100785 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700786 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700787
788 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200789 if not output_redir == None:
790 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791 out.project('project %-40s', self.relpath + '/')
792
793 branch = self.CurrentBranch
794 if branch is None:
795 out.nobranch('(*** NO BRANCH ***)')
796 else:
797 out.branch('branch %s', branch)
798 out.nl()
799
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700800 if rb:
801 out.important('prior sync failed; rebase still in progress')
802 out.nl()
803
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700804 paths = list()
805 paths.extend(di.keys())
806 paths.extend(df.keys())
807 paths.extend(do)
808
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530809 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900810 try:
811 i = di[p]
812 except KeyError:
813 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900815 try:
816 f = df[p]
817 except KeyError:
818 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200819
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900820 if i:
821 i_status = i.status.upper()
822 else:
823 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900825 if f:
826 f_status = f.status.lower()
827 else:
828 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829
830 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800831 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 i.src_path, p, i.level)
833 else:
834 line = ' %s%s\t%s' % (i_status, f_status, p)
835
836 if i and not f:
837 out.added('%s', line)
838 elif (i and f) or (not i and f):
839 out.changed('%s', line)
840 elif not i and not f:
841 out.untracked('%s', line)
842 else:
843 out.write('%s', line)
844 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200845
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700846 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700847
pelyad67872d2012-03-28 14:49:58 +0300848 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849 """Prints the status of the repository to stdout.
850 """
851 out = DiffColoring(self.config)
852 cmd = ['diff']
853 if out.is_on:
854 cmd.append('--color')
855 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300856 if absolute_paths:
857 cmd.append('--src-prefix=a/%s/' % self.relpath)
858 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700859 cmd.append('--')
860 p = GitCommand(self,
861 cmd,
862 capture_stdout = True,
863 capture_stderr = True)
864 has_diff = False
865 for line in p.process.stdout:
866 if not has_diff:
867 out.nl()
868 out.project('project %s/' % self.relpath)
869 out.nl()
870 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700871 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872 p.Wait()
873
874
875## Publish / Upload ##
876
David Pursehouse8a68ff92012-09-24 12:15:13 +0900877 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878 """Was the branch published (uploaded) for code review?
879 If so, returns the SHA-1 hash of the last published
880 state for the branch.
881 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700882 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900883 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700884 try:
885 return self.bare_git.rev_parse(key)
886 except GitError:
887 return None
888 else:
889 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900890 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700891 except KeyError:
892 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893
David Pursehouse8a68ff92012-09-24 12:15:13 +0900894 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700895 """Prunes any stale published refs.
896 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900897 if all_refs is None:
898 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899 heads = set()
900 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530901 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902 if name.startswith(R_HEADS):
903 heads.add(name)
904 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900905 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530907 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700908 n = name[len(R_PUB):]
909 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900910 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700912 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913 """List any branches which can be uploaded for review.
914 """
915 heads = {}
916 pubed = {}
917
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530918 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900920 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900922 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923
924 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530925 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900926 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700928 if selected_branch and branch != selected_branch:
929 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800931 rb = self.GetUploadableBranch(branch)
932 if rb:
933 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934 return ready
935
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800936 def GetUploadableBranch(self, branch_name):
937 """Get a single uploadable branch, or None.
938 """
939 branch = self.GetBranch(branch_name)
940 base = branch.LocalMerge
941 if branch.LocalMerge:
942 rb = ReviewableBranch(self, branch, base)
943 if rb.commits:
944 return rb
945 return None
946
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700947 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700948 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700949 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400950 draft=False,
951 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 """Uploads the named branch for code review.
953 """
954 if branch is None:
955 branch = self.CurrentBranch
956 if branch is None:
957 raise GitError('not currently on a branch')
958
959 branch = self.GetBranch(branch)
960 if not branch.LocalMerge:
961 raise GitError('branch %s does not track a remote' % branch.name)
962 if not branch.remote.review:
963 raise GitError('remote %s has no review url' % branch.remote.name)
964
Bryan Jacobsf609f912013-05-06 13:36:24 -0400965 if dest_branch is None:
966 dest_branch = self.dest_branch
967 if dest_branch is None:
968 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 if not dest_branch.startswith(R_HEADS):
970 dest_branch = R_HEADS + dest_branch
971
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800972 if not branch.remote.projectname:
973 branch.remote.projectname = self.name
974 branch.remote.Save()
975
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800976 url = branch.remote.ReviewUrl(self.UserEmail)
977 if url is None:
978 raise UploadError('review not configured')
979 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800980
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800981 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800982 rp = ['gerrit receive-pack']
983 for e in people[0]:
984 rp.append('--reviewer=%s' % sq(e))
985 for e in people[1]:
986 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800987 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700988
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800989 cmd.append(url)
990
991 if dest_branch.startswith(R_HEADS):
992 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700993
994 upload_type = 'for'
995 if draft:
996 upload_type = 'drafts'
997
998 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
999 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001000 if auto_topic:
1001 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001002 if not url.startswith('ssh://'):
1003 rp = ['r=%s' % p for p in people[0]] + \
1004 ['cc=%s' % p for p in people[1]]
1005 if rp:
1006 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001007 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001008
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001009 if GitCommand(self, cmd, bare = True).Wait() != 0:
1010 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011
1012 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1013 self.bare_git.UpdateRef(R_PUB + branch.name,
1014 R_HEADS + branch.name,
1015 message = msg)
1016
1017
1018## Sync ##
1019
Julien Campergue335f5ef2013-10-16 11:02:35 +02001020 def _ExtractArchive(self, tarpath, path=None):
1021 """Extract the given tar on its current location
1022
1023 Args:
1024 - tarpath: The path to the actual tar file
1025
1026 """
1027 try:
1028 with tarfile.open(tarpath, 'r') as tar:
1029 tar.extractall(path=path)
1030 return True
1031 except (IOError, tarfile.TarError) as e:
1032 print("error: Cannot extract archive %s: "
1033 "%s" % (tarpath, str(e)), file=sys.stderr)
1034 return False
1035
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001036 def Sync_NetworkHalf(self,
1037 quiet=False,
1038 is_new=None,
1039 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001040 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001041 no_tags=False,
1042 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001043 """Perform only the network IO portion of the sync process.
1044 Local working directory/branch state is not affected.
1045 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001046 if archive and not isinstance(self, MetaProject):
1047 if self.remote.url.startswith(('http://', 'https://')):
1048 print("error: %s: Cannot fetch archives from http/https "
1049 "remotes." % self.name, file=sys.stderr)
1050 return False
1051
1052 name = self.relpath.replace('\\', '/')
1053 name = name.replace('/', '_')
1054 tarpath = '%s.tar' % name
1055 topdir = self.manifest.topdir
1056
1057 try:
1058 self._FetchArchive(tarpath, cwd=topdir)
1059 except GitError as e:
1060 print('error: %s' % str(e), file=sys.stderr)
1061 return False
1062
1063 # From now on, we only need absolute tarpath
1064 tarpath = os.path.join(topdir, tarpath)
1065
1066 if not self._ExtractArchive(tarpath, path=topdir):
1067 return False
1068 try:
1069 os.remove(tarpath)
1070 except OSError as e:
1071 print("warn: Cannot remove archive %s: "
1072 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001073 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001074 return True
1075
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001076 if is_new is None:
1077 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001078 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001080 else:
1081 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001083
1084 if is_new:
1085 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1086 try:
1087 fd = open(alt, 'rb')
1088 try:
1089 alt_dir = fd.readline().rstrip()
1090 finally:
1091 fd.close()
1092 except IOError:
1093 alt_dir = None
1094 else:
1095 alt_dir = None
1096
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001097 if clone_bundle \
1098 and alt_dir is None \
1099 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001100 is_new = False
1101
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001102 if not current_branch_only:
1103 if self.sync_c:
1104 current_branch_only = True
1105 elif not self.manifest._loaded:
1106 # Manifest cannot check defaults until it syncs.
1107 current_branch_only = False
1108 elif self.manifest.default.sync_c:
1109 current_branch_only = True
1110
Conley Owens666d5342014-05-01 13:09:57 -07001111 has_sha1 = ID_RE.match(self.revisionExpr) and self._CheckForSha1()
1112 if (not has_sha1 #Need to fetch since we don't already have this revision
1113 and not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1114 current_branch_only=current_branch_only,
1115 no_tags=no_tags)):
1116 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001117
1118 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001119 self._InitMRef()
1120 else:
1121 self._InitMirrorHead()
1122 try:
1123 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1124 except OSError:
1125 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001127
1128 def PostRepoUpgrade(self):
1129 self._InitHooks()
1130
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001131 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001132 for copyfile in self.copyfiles:
1133 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001134 for linkfile in self.linkfiles:
1135 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136
Julien Camperguedd654222014-01-09 16:21:37 +01001137 def GetCommitRevisionId(self):
1138 """Get revisionId of a commit.
1139
1140 Use this method instead of GetRevisionId to get the id of the commit rather
1141 than the id of the current git object (for example, a tag)
1142
1143 """
1144 if not self.revisionExpr.startswith(R_TAGS):
1145 return self.GetRevisionId(self._allrefs)
1146
1147 try:
1148 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1149 except GitError:
1150 raise ManifestInvalidRevisionError(
1151 'revision %s in %s not found' % (self.revisionExpr,
1152 self.name))
1153
David Pursehouse8a68ff92012-09-24 12:15:13 +09001154 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001155 if self.revisionId:
1156 return self.revisionId
1157
1158 rem = self.GetRemote(self.remote.name)
1159 rev = rem.ToLocal(self.revisionExpr)
1160
David Pursehouse8a68ff92012-09-24 12:15:13 +09001161 if all_refs is not None and rev in all_refs:
1162 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001163
1164 try:
1165 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1166 except GitError:
1167 raise ManifestInvalidRevisionError(
1168 'revision %s in %s not found' % (self.revisionExpr,
1169 self.name))
1170
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001171 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172 """Perform only the local IO portion of the sync process.
1173 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001174 """
David James8d201162013-10-11 17:03:19 -07001175 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001176 all_refs = self.bare_ref.all
1177 self.CleanPublishedCache(all_refs)
1178 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001179
David Pursehouse1d947b32012-10-25 12:23:11 +09001180 def _doff():
1181 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001182 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001183
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001184 head = self.work_git.GetHead()
1185 if head.startswith(R_HEADS):
1186 branch = head[len(R_HEADS):]
1187 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001188 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001189 except KeyError:
1190 head = None
1191 else:
1192 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001194 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195 # Currently on a detached HEAD. The user is assumed to
1196 # not have any local modifications worth worrying about.
1197 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001198 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001199 syncbuf.fail(self, _PriorSyncFailedError())
1200 return
1201
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001202 if head == revid:
1203 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001204 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001205 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001206 if not syncbuf.detach_head:
1207 return
1208 else:
1209 lost = self._revlist(not_rev(revid), HEAD)
1210 if lost:
1211 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001212
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001214 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001215 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001216 syncbuf.fail(self, e)
1217 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001218 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001219 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001220
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001221 if head == revid:
1222 # No changes; don't do anything further.
1223 #
1224 return
1225
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001226 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001228 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001229 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001230 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001231 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001232 syncbuf.info(self,
1233 "leaving %s; does not track upstream",
1234 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001235 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001236 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001237 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001238 syncbuf.fail(self, e)
1239 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001240 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001241 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001243 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001244 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001245 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001246 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247 if not_merged:
1248 if upstream_gain:
1249 # The user has published this branch and some of those
1250 # commits are not yet merged upstream. We do not want
1251 # to rewrite the published commits so we punt.
1252 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001253 syncbuf.fail(self,
1254 "branch %s is published (but not merged) and is now %d commits behind"
1255 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001256 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001257 elif pub == head:
1258 # All published commits are merged, and thus we are a
1259 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001260 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001261 syncbuf.later1(self, _doff)
1262 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001264 # Examine the local commits not in the remote. Find the
1265 # last one attributed to this user, if any.
1266 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001267 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001268 last_mine = None
1269 cnt_mine = 0
1270 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301271 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001272 if committer_email == self.UserEmail:
1273 last_mine = commit_id
1274 cnt_mine += 1
1275
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001276 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001277 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001278
1279 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001280 syncbuf.fail(self, _DirtyError())
1281 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001282
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001283 # If the upstream switched on us, warn the user.
1284 #
1285 if branch.merge != self.revisionExpr:
1286 if branch.merge and self.revisionExpr:
1287 syncbuf.info(self,
1288 'manifest switched %s...%s',
1289 branch.merge,
1290 self.revisionExpr)
1291 elif branch.merge:
1292 syncbuf.info(self,
1293 'manifest no longer tracks %s',
1294 branch.merge)
1295
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001296 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001298 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001300 syncbuf.info(self,
1301 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001302 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001304 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001305 if not ID_RE.match(self.revisionExpr):
1306 # in case of manifest sync the revisionExpr might be a SHA1
1307 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308 branch.Save()
1309
Mike Pontillod3153822012-02-28 11:53:24 -08001310 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001311 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001312 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001313 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001314 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001315 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001317 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001318 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001319 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001320 syncbuf.fail(self, e)
1321 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001323 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001325 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326 # dest should already be an absolute path, but src is project relative
1327 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001328 abssrc = os.path.join(self.worktree, src)
1329 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001331 def AddLinkFile(self, src, dest, absdest):
1332 # dest should already be an absolute path, but src is project relative
1333 # make src an absolute path
1334 abssrc = os.path.join(self.worktree, src)
1335 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1336
James W. Mills24c13082012-04-12 15:04:13 -05001337 def AddAnnotation(self, name, value, keep):
1338 self.annotations.append(_Annotation(name, value, keep))
1339
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001340 def DownloadPatchSet(self, change_id, patch_id):
1341 """Download a single patch set of a single change to FETCH_HEAD.
1342 """
1343 remote = self.GetRemote(self.remote.name)
1344
1345 cmd = ['fetch', remote.name]
1346 cmd.append('refs/changes/%2.2d/%d/%d' \
1347 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001348 if GitCommand(self, cmd, bare=True).Wait() != 0:
1349 return None
1350 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001351 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001352 change_id,
1353 patch_id,
1354 self.bare_git.rev_parse('FETCH_HEAD'))
1355
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001356
1357## Branch Management ##
natalie.chenb7852052015-03-10 16:58:49 +08001358 def CleanCache(self, name):
1359 refs = GitRefs(self.localcache).all
1360
1361 try:
1362 self.bare_cache.DeleteRef(name)
1363 except:
1364 raise
1365 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001366
1367 def StartBranch(self, name):
1368 """Create a new branch off the manifest's revision.
1369 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001370 head = self.work_git.GetHead()
1371 if head == (R_HEADS + name):
1372 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373
David Pursehouse8a68ff92012-09-24 12:15:13 +09001374 all_refs = self.bare_ref.all
1375 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001376 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001377 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001378 capture_stdout = True,
1379 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001380
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001381 branch = self.GetBranch(name)
1382 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001383 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001384 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001385
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001386 if head.startswith(R_HEADS):
1387 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001388 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001389 except KeyError:
1390 head = None
1391
1392 if revid and head and revid == head:
1393 ref = os.path.join(self.gitdir, R_HEADS + name)
1394 try:
1395 os.makedirs(os.path.dirname(ref))
1396 except OSError:
1397 pass
1398 _lwrite(ref, '%s\n' % revid)
1399 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1400 'ref: %s%s\n' % (R_HEADS, name))
1401 branch.Save()
1402 return True
1403
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001404 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001405 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001406 capture_stdout = True,
1407 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001408 branch.Save()
1409 return True
1410 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001411
Wink Saville02d79452009-04-10 13:01:24 -07001412 def CheckoutBranch(self, name):
1413 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001414
1415 Args:
1416 name: The name of the branch to checkout.
1417
1418 Returns:
1419 True if the checkout succeeded; False if it didn't; None if the branch
1420 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001421 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001422 rev = R_HEADS + name
1423 head = self.work_git.GetHead()
1424 if head == rev:
1425 # Already on the branch
1426 #
1427 return True
Wink Saville02d79452009-04-10 13:01:24 -07001428
David Pursehouse8a68ff92012-09-24 12:15:13 +09001429 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001430 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001431 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001432 except KeyError:
1433 # Branch does not exist in this project
1434 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001435 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001436
1437 if head.startswith(R_HEADS):
1438 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001439 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001440 except KeyError:
1441 head = None
1442
1443 if head == revid:
1444 # Same revision; just update HEAD to point to the new
1445 # target branch, but otherwise take no other action.
1446 #
1447 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1448 'ref: %s%s\n' % (R_HEADS, name))
1449 return True
Wink Saville02d79452009-04-10 13:01:24 -07001450
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001451 return GitCommand(self,
1452 ['checkout', name, '--'],
1453 capture_stdout = True,
1454 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001455
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001456 def AbandonBranch(self, name):
1457 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001458
1459 Args:
1460 name: The name of the branch to abandon.
1461
1462 Returns:
1463 True if the abandon succeeded; False if it didn't; None if the branch
1464 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001465 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001466 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001467 all_refs = self.bare_ref.all
1468 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001469 # Doesn't exist
1470 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001471
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001472 head = self.work_git.GetHead()
1473 if head == rev:
1474 # We can't destroy the branch while we are sitting
1475 # on it. Switch to a detached HEAD.
1476 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001477 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001478
David Pursehouse8a68ff92012-09-24 12:15:13 +09001479 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001480 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001481 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1482 '%s\n' % revid)
1483 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001484 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001485
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001486 return GitCommand(self,
1487 ['branch', '-D', name],
1488 capture_stdout = True,
1489 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001490
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 def PruneHeads(self):
1492 """Prune any topic branches already merged into upstream.
1493 """
1494 cb = self.CurrentBranch
1495 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001496 left = self._allrefs
1497 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001498 if name.startswith(R_HEADS):
1499 name = name[len(R_HEADS):]
1500 if cb is None or name != cb:
1501 kill.append(name)
1502
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001503 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001504 if cb is not None \
1505 and not self._revlist(HEAD + '...' + rev) \
1506 and not self.IsDirty(consider_untracked = False):
1507 self.work_git.DetachHead(HEAD)
1508 kill.append(cb)
1509
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001510 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001511 old = self.bare_git.GetHead()
1512 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001513 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1514
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001515 try:
1516 self.bare_git.DetachHead(rev)
1517
1518 b = ['branch', '-d']
1519 b.extend(kill)
1520 b = GitCommand(self, b, bare=True,
1521 capture_stdout=True,
1522 capture_stderr=True)
1523 b.Wait()
1524 finally:
1525 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001526 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001527
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001528 for branch in kill:
1529 if (R_HEADS + branch) not in left:
1530 self.CleanPublishedCache()
1531 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001532
1533 if cb and cb not in kill:
1534 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001535 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001536
1537 kept = []
1538 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001539 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001540 branch = self.GetBranch(branch)
1541 base = branch.LocalMerge
1542 if not base:
1543 base = rev
1544 kept.append(ReviewableBranch(self, branch, base))
1545 return kept
1546
1547
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001548## Submodule Management ##
1549
1550 def GetRegisteredSubprojects(self):
1551 result = []
1552 def rec(subprojects):
1553 if not subprojects:
1554 return
1555 result.extend(subprojects)
1556 for p in subprojects:
1557 rec(p.subprojects)
1558 rec(self.subprojects)
1559 return result
1560
1561 def _GetSubmodules(self):
1562 # Unfortunately we cannot call `git submodule status --recursive` here
1563 # because the working tree might not exist yet, and it cannot be used
1564 # without a working tree in its current implementation.
1565
1566 def get_submodules(gitdir, rev):
1567 # Parse .gitmodules for submodule sub_paths and sub_urls
1568 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1569 if not sub_paths:
1570 return []
1571 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1572 # revision of submodule repository
1573 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1574 submodules = []
1575 for sub_path, sub_url in zip(sub_paths, sub_urls):
1576 try:
1577 sub_rev = sub_revs[sub_path]
1578 except KeyError:
1579 # Ignore non-exist submodules
1580 continue
1581 submodules.append((sub_rev, sub_path, sub_url))
1582 return submodules
1583
1584 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1585 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1586 def parse_gitmodules(gitdir, rev):
1587 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1588 try:
1589 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1590 bare = True, gitdir = gitdir)
1591 except GitError:
1592 return [], []
1593 if p.Wait() != 0:
1594 return [], []
1595
1596 gitmodules_lines = []
1597 fd, temp_gitmodules_path = tempfile.mkstemp()
1598 try:
1599 os.write(fd, p.stdout)
1600 os.close(fd)
1601 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1602 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1603 bare = True, gitdir = gitdir)
1604 if p.Wait() != 0:
1605 return [], []
1606 gitmodules_lines = p.stdout.split('\n')
1607 except GitError:
1608 return [], []
1609 finally:
1610 os.remove(temp_gitmodules_path)
1611
1612 names = set()
1613 paths = {}
1614 urls = {}
1615 for line in gitmodules_lines:
1616 if not line:
1617 continue
1618 m = re_path.match(line)
1619 if m:
1620 names.add(m.group(1))
1621 paths[m.group(1)] = m.group(2)
1622 continue
1623 m = re_url.match(line)
1624 if m:
1625 names.add(m.group(1))
1626 urls[m.group(1)] = m.group(2)
1627 continue
1628 names = sorted(names)
1629 return ([paths.get(name, '') for name in names],
1630 [urls.get(name, '') for name in names])
1631
1632 def git_ls_tree(gitdir, rev, paths):
1633 cmd = ['ls-tree', rev, '--']
1634 cmd.extend(paths)
1635 try:
1636 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1637 bare = True, gitdir = gitdir)
1638 except GitError:
1639 return []
1640 if p.Wait() != 0:
1641 return []
1642 objects = {}
1643 for line in p.stdout.split('\n'):
1644 if not line.strip():
1645 continue
1646 object_rev, object_path = line.split()[2:4]
1647 objects[object_path] = object_rev
1648 return objects
1649
1650 try:
1651 rev = self.GetRevisionId()
1652 except GitError:
1653 return []
1654 return get_submodules(self.gitdir, rev)
1655
1656 def GetDerivedSubprojects(self):
1657 result = []
1658 if not self.Exists:
1659 # If git repo does not exist yet, querying its submodules will
1660 # mess up its states; so return here.
1661 return result
1662 for rev, path, url in self._GetSubmodules():
1663 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001664 relpath, worktree, gitdir, objdir = \
1665 self.manifest.GetSubprojectPaths(self, name, path)
1666 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001667 if project:
1668 result.extend(project.GetDerivedSubprojects())
1669 continue
David James8d201162013-10-11 17:03:19 -07001670
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001671 remote = RemoteSpec(self.remote.name,
1672 url = url,
1673 review = self.remote.review)
1674 subproject = Project(manifest = self.manifest,
1675 name = name,
1676 remote = remote,
1677 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001678 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001679 worktree = worktree,
1680 relpath = relpath,
1681 revisionExpr = self.revisionExpr,
1682 revisionId = rev,
1683 rebase = self.rebase,
1684 groups = self.groups,
1685 sync_c = self.sync_c,
1686 sync_s = self.sync_s,
1687 parent = self,
1688 is_derived = True)
1689 result.append(subproject)
1690 result.extend(subproject.GetDerivedSubprojects())
1691 return result
1692
1693
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001694## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001695 def _CheckForSha1(self):
1696 try:
1697 # if revision (sha or tag) is not present then following function
1698 # throws an error.
1699 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1700 return True
1701 except GitError:
1702 # There is no such persistent revision. We have to fetch it.
1703 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001704
Julien Campergue335f5ef2013-10-16 11:02:35 +02001705 def _FetchArchive(self, tarpath, cwd=None):
1706 cmd = ['archive', '-v', '-o', tarpath]
1707 cmd.append('--remote=%s' % self.remote.url)
1708 cmd.append('--prefix=%s/' % self.relpath)
1709 cmd.append(self.revisionExpr)
1710
1711 command = GitCommand(self, cmd, cwd=cwd,
1712 capture_stdout=True,
1713 capture_stderr=True)
1714
1715 if command.Wait() != 0:
1716 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1717
natalie.chenb7852052015-03-10 16:58:49 +08001718 def _UpdateCache(self, name, ssh_proxy, tag_name, is_sha1):
1719 if not name:
1720 name = self.remote.name
1721
1722 remote = self.GetRemote(name)
1723 cmd = ['fetch']
1724 depth = None
1725 if not self.manifest.IsMirror:
1726 if self.clone_depth:
1727 depth = self.clone_depth
1728 else:
1729 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1730 if depth:
1731 cmd.append('--depth=%s' % depth)
1732 cmd.append('--no-tags')
1733 cmd.append('--quiet')
1734 cmd.append('--update-head-ok')
1735 cmd.append(remote.url)
1736
1737 if tag_name is not None:
1738 cmd.append('tag')
1739 cmd.append(tag_name)
1740 else:
1741 branch = self.revisionExpr
1742 if is_sha1:
1743 branch = self.upstream
1744 if branch.startswith(R_HEADS):
1745 branch = branch[len(R_HEADS):]
1746 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
1747 else:
1748 cmd.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
1749
1750 for _i in range(2):
1751 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy, gitdir=self.localcache).Wait()
1752 if ret == 0:
1753 ok = True
1754 break
1755 elif ret == 128:
1756 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1757 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1758 # abort the optimization attempt and do a full sync.
1759 break
1760 time.sleep(random.randint(30, 45))
1761 if ok and depth:
1762 os.remove('%s/shallow' % self.localcache)
1763
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001764 def _RemoteFetch(self, name=None,
1765 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001766 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001767 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001768 alt_dir=None,
1769 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001770
1771 is_sha1 = False
1772 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001773 depth = None
1774
1775 # The depth should not be used when fetching to a mirror because
1776 # it will result in a shallow repository that cannot be cloned or
1777 # fetched from.
1778 if not self.manifest.IsMirror:
1779 if self.clone_depth:
1780 depth = self.clone_depth
1781 else:
1782 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001783
Shawn Pearce69e04d82014-01-29 12:48:54 -08001784 if depth:
1785 current_branch_only = True
1786
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001787 if current_branch_only:
1788 if ID_RE.match(self.revisionExpr) is not None:
1789 is_sha1 = True
1790 elif self.revisionExpr.startswith(R_TAGS):
1791 # this is a tag and its sha1 value should never change
1792 tag_name = self.revisionExpr[len(R_TAGS):]
1793
1794 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001795 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001796 return True
Brian Harring14a66742012-09-28 20:21:57 -07001797 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1798 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001799
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001800 if not name:
1801 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001802
1803 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001804 remote = self.GetRemote(name)
1805 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001806 ssh_proxy = True
1807
natalie.chenb7852052015-03-10 16:58:49 +08001808 try:
1809 if self.localcache and not depth:
1810 self._UpdateCache(name, ssh_proxy, tag_name, is_sha1)
1811 except Exception as e:
1812 print("update cache failed. %s" % traceback.format_exc(), file=sys.stderr)
1813
Shawn O. Pearce88443382010-10-08 10:02:09 +02001814 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001815 if alt_dir and 'objects' == os.path.basename(alt_dir):
1816 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001817 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1818 remote = self.GetRemote(name)
1819
David Pursehouse8a68ff92012-09-24 12:15:13 +09001820 all_refs = self.bare_ref.all
1821 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001822 tmp = set()
1823
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301824 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001825 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001826 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001827 all_refs[r] = ref_id
1828 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001829 continue
1830
David Pursehouse8a68ff92012-09-24 12:15:13 +09001831 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001832 continue
1833
David Pursehouse8a68ff92012-09-24 12:15:13 +09001834 r = 'refs/_alt/%s' % ref_id
1835 all_refs[r] = ref_id
1836 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001837 tmp.add(r)
1838
Shawn O. Pearce88443382010-10-08 10:02:09 +02001839 tmp_packed = ''
1840 old_packed = ''
1841
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301842 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001843 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001844 tmp_packed += line
1845 if r not in tmp:
1846 old_packed += line
1847
1848 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001849 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001850 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001851
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001852 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001853
1854 # The --depth option only affects the initial fetch; after that we'll do
1855 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001856 if depth and initial:
1857 cmd.append('--depth=%s' % depth)
1858
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001859 if quiet:
1860 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001861 if not self.worktree:
1862 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001863 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001864
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001865 # If using depth then we should not get all the tags since they may
1866 # be outside of the depth.
1867 if no_tags or depth:
1868 cmd.append('--no-tags')
1869 else:
1870 cmd.append('--tags')
1871
Brian Harring14a66742012-09-28 20:21:57 -07001872 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001873 # Fetch whole repo
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301874 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
natalie.chen59964f02014-12-19 20:06:47 +08001875 if self.upstream and self.upstream.startswith('refs/builds'):
1876 cmd.append((u'+%s:' % self.upstream) + remote.ToLocal(self.upstream))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001877 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001878 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001879 cmd.append(tag_name)
1880 else:
1881 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001882 if is_sha1:
1883 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001884 if branch.startswith(R_HEADS):
1885 branch = branch[len(R_HEADS):]
natalie.chen59964f02014-12-19 20:06:47 +08001886 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
1887 else:
1888 cmd.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001889
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001890 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001891 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001892 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1893 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001894 ok = True
1895 break
Brian Harring14a66742012-09-28 20:21:57 -07001896 elif current_branch_only and is_sha1 and ret == 128:
1897 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1898 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1899 # abort the optimization attempt and do a full sync.
1900 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001901 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001902
1903 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001904 # Ensure that some refs exist. Otherwise, we probably aren't looking
1905 # at a real git repository and may have a bad url.
1906 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001907 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001908
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001909 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001910 if old_packed != '':
1911 _lwrite(packed_refs, old_packed)
1912 else:
1913 os.remove(packed_refs)
1914 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001915
1916 if is_sha1 and current_branch_only and self.upstream:
1917 # We just synced the upstream given branch; verify we
1918 # got what we wanted, else trigger a second run of all
1919 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001920 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001921 return self._RemoteFetch(name=name, current_branch_only=False,
1922 initial=False, quiet=quiet, alt_dir=alt_dir)
1923
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001924 return ok
1925
1926 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001927 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001928 return False
1929
1930 remote = self.GetRemote(self.remote.name)
1931 bundle_url = remote.url + '/clone.bundle'
1932 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001933 if GetSchemeFromUrl(bundle_url) not in (
1934 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001935 return False
1936
1937 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1938 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001939
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001940 exist_dst = os.path.exists(bundle_dst)
1941 exist_tmp = os.path.exists(bundle_tmp)
1942
1943 if not initial and not exist_dst and not exist_tmp:
1944 return False
1945
1946 if not exist_dst:
1947 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1948 if not exist_dst:
1949 return False
1950
1951 cmd = ['fetch']
1952 if quiet:
1953 cmd.append('--quiet')
1954 if not self.worktree:
1955 cmd.append('--update-head-ok')
1956 cmd.append(bundle_dst)
1957 for f in remote.fetch:
1958 cmd.append(str(f))
1959 cmd.append('refs/tags/*:refs/tags/*')
1960
1961 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001962 if os.path.exists(bundle_dst):
1963 os.remove(bundle_dst)
1964 if os.path.exists(bundle_tmp):
1965 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001966 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001967
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001968 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001969 if os.path.exists(dstPath):
1970 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001971
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001972 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001973 if quiet:
1974 cmd += ['--silent']
1975 if os.path.exists(tmpPath):
1976 size = os.stat(tmpPath).st_size
1977 if size >= 1024:
1978 cmd += ['--continue-at', '%d' % (size,)]
1979 else:
1980 os.remove(tmpPath)
1981 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1982 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001983 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001984 if cookiefile:
1985 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001986 if srcUrl.startswith('persistent-'):
1987 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001988 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001989
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001990 if IsTrace():
1991 Trace('%s', ' '.join(cmd))
1992 try:
1993 proc = subprocess.Popen(cmd)
1994 except OSError:
1995 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001996
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001997 curlret = proc.wait()
1998
1999 if curlret == 22:
2000 # From curl man page:
2001 # 22: HTTP page not retrieved. The requested url was not found or
2002 # returned another error with the HTTP error code being 400 or above.
2003 # This return code only appears if -f, --fail is used.
2004 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07002005 print("Server does not provide clone.bundle; ignoring.",
2006 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002007 return False
2008
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002009 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002010 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002011 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002012 return True
2013 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002014 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002015 return False
2016 else:
2017 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002018
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002019 def _IsValidBundle(self, path):
2020 try:
2021 with open(path) as f:
2022 if f.read(16) == '# v2 git bundle\n':
2023 return True
2024 else:
2025 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
2026 return False
2027 except OSError:
2028 return False
2029
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002030 def _GetBundleCookieFile(self, url):
2031 if url.startswith('persistent-'):
2032 try:
2033 p = subprocess.Popen(
2034 ['git-remote-persistent-https', '-print_config', url],
2035 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2036 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07002037 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002038 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07002039 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002040 for line in p.stdout:
2041 line = line.strip()
2042 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07002043 cookiefile = line[len(prefix):]
2044 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002045 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08002046 err_msg = p.stderr.read()
2047 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002048 pass # Persistent proxy doesn't support -print_config.
2049 else:
Conley Owenscbc07982013-11-21 10:38:03 -08002050 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07002051 if cookiefile:
2052 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002053 except OSError as e:
2054 if e.errno == errno.ENOENT:
2055 pass # No persistent proxy.
2056 raise
2057 return GitConfig.ForUser().GetString('http.cookiefile')
2058
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002059 def _Checkout(self, rev, quiet=False):
2060 cmd = ['checkout']
2061 if quiet:
2062 cmd.append('-q')
2063 cmd.append(rev)
2064 cmd.append('--')
2065 if GitCommand(self, cmd).Wait() != 0:
2066 if self._allrefs:
2067 raise GitError('%s checkout %s ' % (self.name, rev))
2068
Pierre Tardye5a21222011-03-24 16:28:18 +01002069 def _CherryPick(self, rev, quiet=False):
2070 cmd = ['cherry-pick']
2071 cmd.append(rev)
2072 cmd.append('--')
2073 if GitCommand(self, cmd).Wait() != 0:
2074 if self._allrefs:
2075 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2076
Erwan Mahea94f1622011-08-19 13:56:09 +02002077 def _Revert(self, rev, quiet=False):
2078 cmd = ['revert']
2079 cmd.append('--no-edit')
2080 cmd.append(rev)
2081 cmd.append('--')
2082 if GitCommand(self, cmd).Wait() != 0:
2083 if self._allrefs:
2084 raise GitError('%s revert %s ' % (self.name, rev))
2085
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002086 def _ResetHard(self, rev, quiet=True):
2087 cmd = ['reset', '--hard']
2088 if quiet:
2089 cmd.append('-q')
2090 cmd.append(rev)
2091 if GitCommand(self, cmd).Wait() != 0:
2092 raise GitError('%s reset --hard %s ' % (self.name, rev))
2093
2094 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002095 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002096 if onto is not None:
2097 cmd.extend(['--onto', onto])
2098 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002099 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002100 raise GitError('%s rebase %s ' % (self.name, upstream))
2101
Pierre Tardy3d125942012-05-04 12:18:12 +02002102 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002103 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002104 if ffonly:
2105 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002106 if GitCommand(self, cmd).Wait() != 0:
2107 raise GitError('%s merge %s ' % (self.name, head))
2108
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002109 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002110 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002111
natalie.chenb7852052015-03-10 16:58:49 +08002112 if self.localcache and not os.path.exists(self.localcache):
2113 os.makedirs(self.localcache)
2114 self.bare_cache.init()
2115
David James8d201162013-10-11 17:03:19 -07002116 # Initialize the bare repository, which contains all of the objects.
2117 if not os.path.exists(self.objdir):
2118 os.makedirs(self.objdir)
2119 self.bare_objdir.init()
2120
2121 # If we have a separate directory to hold refs, initialize it as well.
2122 if self.objdir != self.gitdir:
2123 os.makedirs(self.gitdir)
2124 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2125 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002126
Shawn O. Pearce88443382010-10-08 10:02:09 +02002127 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002128 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002129
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002130 if ref_dir or mirror_git:
2131 if not mirror_git:
2132 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002133 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2134 self.relpath + '.git')
2135
2136 if os.path.exists(mirror_git):
2137 ref_dir = mirror_git
2138
2139 elif os.path.exists(repo_git):
2140 ref_dir = repo_git
2141
2142 else:
2143 ref_dir = None
2144
2145 if ref_dir:
2146 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2147 os.path.join(ref_dir, 'objects') + '\n')
natalie.chenb7852052015-03-10 16:58:49 +08002148 elif self.localcache:
2149 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2150 os.path.join(self.localcache, 'objects') + '\n')
2151
Shawn O. Pearce88443382010-10-08 10:02:09 +02002152
Jimmie Westera0444582012-10-24 13:44:42 +02002153 self._UpdateHooks()
2154
2155 m = self.manifest.manifestProject.config
2156 for key in ['user.name', 'user.email']:
2157 if m.Has(key, include_defaults = False):
2158 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002159 if self.manifest.IsMirror:
2160 self.config.SetString('core.bare', 'true')
2161 else:
2162 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002163
Jimmie Westera0444582012-10-24 13:44:42 +02002164 def _UpdateHooks(self):
2165 if os.path.exists(self.gitdir):
2166 # Always recreate hooks since they can have been changed
2167 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002168 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002169 try:
2170 to_rm = os.listdir(hooks)
2171 except OSError:
2172 to_rm = []
2173 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002174 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002175 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002176
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002177 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002178 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002179 if not os.path.exists(hooks):
2180 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002181 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002182 name = os.path.basename(stock_hook)
2183
Victor Boivie65e0f352011-04-18 11:23:29 +02002184 if name in ('commit-msg',) and not self.remote.review \
2185 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002186 # Don't install a Gerrit Code Review hook if this
2187 # project does not appear to use it for reviews.
2188 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002189 # Since the manifest project is one of those, but also
2190 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002191 continue
2192
2193 dst = os.path.join(hooks, name)
2194 if os.path.islink(dst):
2195 continue
2196 if os.path.exists(dst):
2197 if filecmp.cmp(stock_hook, dst, shallow=False):
2198 os.remove(dst)
2199 else:
2200 _error("%s: Not replacing %s hook", self.relpath, name)
2201 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002202 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002203 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002204 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002205 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002206 raise GitError('filesystem must support symlinks')
2207 else:
2208 raise
2209
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002210 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002211 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002212 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002213 remote.url = self.remote.url
2214 remote.review = self.remote.review
2215 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002216
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002217 if self.worktree:
2218 remote.ResetFetch(mirror=False)
2219 else:
2220 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002221 remote.Save()
2222
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002223 def _InitMRef(self):
2224 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002225 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002226
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002227 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002228 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002229
2230 def _InitAnyMRef(self, ref):
2231 cur = self.bare_ref.symref(ref)
2232
2233 if self.revisionId:
2234 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2235 msg = 'manifest set to %s' % self.revisionId
2236 dst = self.revisionId + '^0'
2237 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2238 else:
2239 remote = self.GetRemote(self.remote.name)
2240 dst = remote.ToLocal(self.revisionExpr)
2241 if cur != dst:
2242 msg = 'manifest set to %s' % self.revisionExpr
2243 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002244
David James8d201162013-10-11 17:03:19 -07002245 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2246 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2247
2248 Args:
2249 gitdir: The bare git repository. Must already be initialized.
2250 dotgit: The repository you would like to initialize.
2251 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2252 Only one work tree can store refs under a given |gitdir|.
2253 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2254 This saves you the effort of initializing |dotgit| yourself.
2255 """
2256 # These objects can be shared between several working trees.
2257 symlink_files = ['description', 'info']
2258 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2259 if share_refs:
2260 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002261 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002262 symlink_dirs += ['logs', 'refs']
2263 to_symlink = symlink_files + symlink_dirs
2264
2265 to_copy = []
2266 if copy_all:
2267 to_copy = os.listdir(gitdir)
2268
2269 for name in set(to_copy).union(to_symlink):
2270 try:
2271 src = os.path.realpath(os.path.join(gitdir, name))
2272 dst = os.path.realpath(os.path.join(dotgit, name))
2273
2274 if os.path.lexists(dst) and not os.path.islink(dst):
2275 raise GitError('cannot overwrite a local work tree')
2276
2277 # If the source dir doesn't exist, create an empty dir.
2278 if name in symlink_dirs and not os.path.lexists(src):
2279 os.makedirs(src)
2280
2281 if name in to_symlink:
2282 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2283 elif copy_all and not os.path.islink(dst):
2284 if os.path.isdir(src):
2285 shutil.copytree(src, dst)
2286 elif os.path.isfile(src):
2287 shutil.copy(src, dst)
2288 except OSError as e:
2289 if e.errno == errno.EPERM:
2290 raise GitError('filesystem must support symlinks')
2291 else:
2292 raise
2293
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002294 def _InitWorkTree(self):
2295 dotgit = os.path.join(self.worktree, '.git')
2296 if not os.path.exists(dotgit):
2297 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002298 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2299 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002300
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002301 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002302
2303 cmd = ['read-tree', '--reset', '-u']
2304 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002305 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002306 if GitCommand(self, cmd).Wait() != 0:
2307 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002308
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002309 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002310
2311 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002312 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002313
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002314 def _revlist(self, *args, **kw):
2315 a = []
2316 a.extend(args)
2317 a.append('--')
2318 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002319
2320 @property
2321 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002322 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002323
Julien Camperguedd654222014-01-09 16:21:37 +01002324 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2325 """Get logs between two revisions of this project."""
2326 comp = '..'
2327 if rev1:
2328 revs = [rev1]
2329 if rev2:
2330 revs.extend([comp, rev2])
2331 cmd = ['log', ''.join(revs)]
2332 out = DiffColoring(self.config)
2333 if out.is_on and color:
2334 cmd.append('--color')
2335 if oneline:
2336 cmd.append('--oneline')
2337
2338 try:
2339 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2340 if log.Wait() == 0:
2341 return log.stdout
2342 except GitError:
2343 # worktree may not exist if groups changed for example. In that case,
2344 # try in gitdir instead.
2345 if not os.path.exists(self.worktree):
2346 return self.bare_git.log(*cmd[1:])
2347 else:
2348 raise
2349 return None
2350
2351 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2352 """Get the list of logs from this revision to given revisionId"""
2353 logs = {}
2354 selfId = self.GetRevisionId(self._allrefs)
2355 toId = toProject.GetRevisionId(toProject._allrefs)
2356
2357 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2358 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2359 return logs
2360
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002361 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002362 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002363 self._project = project
2364 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002365 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002366
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002367 def LsOthers(self):
2368 p = GitCommand(self._project,
2369 ['ls-files',
2370 '-z',
2371 '--others',
2372 '--exclude-standard'],
2373 bare = False,
David James8d201162013-10-11 17:03:19 -07002374 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002375 capture_stdout = True,
2376 capture_stderr = True)
2377 if p.Wait() == 0:
2378 out = p.stdout
2379 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002380 return out[:-1].split('\0') # pylint: disable=W1401
2381 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002382 return []
2383
2384 def DiffZ(self, name, *args):
2385 cmd = [name]
2386 cmd.append('-z')
2387 cmd.extend(args)
2388 p = GitCommand(self._project,
2389 cmd,
David James8d201162013-10-11 17:03:19 -07002390 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002391 bare = False,
2392 capture_stdout = True,
2393 capture_stderr = True)
2394 try:
2395 out = p.process.stdout.read()
2396 r = {}
2397 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002398 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002399 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002400 try:
2401 info = out.next()
2402 path = out.next()
2403 except StopIteration:
2404 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002405
2406 class _Info(object):
2407 def __init__(self, path, omode, nmode, oid, nid, state):
2408 self.path = path
2409 self.src_path = None
2410 self.old_mode = omode
2411 self.new_mode = nmode
2412 self.old_id = oid
2413 self.new_id = nid
2414
2415 if len(state) == 1:
2416 self.status = state
2417 self.level = None
2418 else:
2419 self.status = state[:1]
2420 self.level = state[1:]
2421 while self.level.startswith('0'):
2422 self.level = self.level[1:]
2423
2424 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002425 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002426 if info.status in ('R', 'C'):
2427 info.src_path = info.path
2428 info.path = out.next()
2429 r[info.path] = info
2430 return r
2431 finally:
2432 p.Wait()
2433
2434 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002435 if self._bare:
2436 path = os.path.join(self._project.gitdir, HEAD)
2437 else:
2438 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002439 try:
2440 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002441 except IOError as e:
2442 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002443 try:
2444 line = fd.read()
2445 finally:
2446 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302447 try:
2448 line = line.decode()
2449 except AttributeError:
2450 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002451 if line.startswith('ref: '):
2452 return line[5:-1]
2453 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002454
2455 def SetHead(self, ref, message=None):
2456 cmdv = []
2457 if message is not None:
2458 cmdv.extend(['-m', message])
2459 cmdv.append(HEAD)
2460 cmdv.append(ref)
2461 self.symbolic_ref(*cmdv)
2462
2463 def DetachHead(self, new, message=None):
2464 cmdv = ['--no-deref']
2465 if message is not None:
2466 cmdv.extend(['-m', message])
2467 cmdv.append(HEAD)
2468 cmdv.append(new)
2469 self.update_ref(*cmdv)
2470
2471 def UpdateRef(self, name, new, old=None,
2472 message=None,
2473 detach=False):
2474 cmdv = []
2475 if message is not None:
2476 cmdv.extend(['-m', message])
2477 if detach:
2478 cmdv.append('--no-deref')
2479 cmdv.append(name)
2480 cmdv.append(new)
2481 if old is not None:
2482 cmdv.append(old)
2483 self.update_ref(*cmdv)
2484
2485 def DeleteRef(self, name, old=None):
2486 if not old:
2487 old = self.rev_parse(name)
2488 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002489 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002490
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002491 def rev_list(self, *args, **kw):
2492 if 'format' in kw:
2493 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2494 else:
2495 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002496 cmdv.extend(args)
2497 p = GitCommand(self._project,
2498 cmdv,
2499 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002500 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002501 capture_stdout = True,
2502 capture_stderr = True)
2503 r = []
2504 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002505 if line[-1] == '\n':
2506 line = line[:-1]
2507 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002508 if p.Wait() != 0:
2509 raise GitError('%s rev-list %s: %s' % (
2510 self._project.name,
2511 str(args),
2512 p.stderr))
2513 return r
2514
2515 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002516 """Allow arbitrary git commands using pythonic syntax.
2517
2518 This allows you to do things like:
2519 git_obj.rev_parse('HEAD')
2520
2521 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2522 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002523 Any other positional arguments will be passed to the git command, and the
2524 following keyword arguments are supported:
2525 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002526
2527 Args:
2528 name: The name of the git command to call. Any '_' characters will
2529 be replaced with '-'.
2530
2531 Returns:
2532 A callable object that will try to call git with the named command.
2533 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002534 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002535 def runner(*args, **kwargs):
2536 cmdv = []
2537 config = kwargs.pop('config', None)
2538 for k in kwargs:
2539 raise TypeError('%s() got an unexpected keyword argument %r'
2540 % (name, k))
2541 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002542 if not git_require((1, 7, 2)):
2543 raise ValueError('cannot set config on command line for %s()'
2544 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302545 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002546 cmdv.append('-c')
2547 cmdv.append('%s=%s' % (k, v))
2548 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002549 cmdv.extend(args)
2550 p = GitCommand(self._project,
2551 cmdv,
2552 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002553 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002554 capture_stdout = True,
2555 capture_stderr = True)
2556 if p.Wait() != 0:
2557 raise GitError('%s %s: %s' % (
2558 self._project.name,
2559 name,
2560 p.stderr))
2561 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302562 try:
Conley Owensedd01512013-09-26 12:59:58 -07002563 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302564 except AttributeError:
2565 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002566 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2567 return r[:-1]
2568 return r
2569 return runner
2570
2571
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002572class _PriorSyncFailedError(Exception):
2573 def __str__(self):
2574 return 'prior sync failed; rebase still in progress'
2575
2576class _DirtyError(Exception):
2577 def __str__(self):
2578 return 'contains uncommitted changes'
2579
2580class _InfoMessage(object):
2581 def __init__(self, project, text):
2582 self.project = project
2583 self.text = text
2584
2585 def Print(self, syncbuf):
2586 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2587 syncbuf.out.nl()
2588
2589class _Failure(object):
2590 def __init__(self, project, why):
2591 self.project = project
2592 self.why = why
2593
2594 def Print(self, syncbuf):
2595 syncbuf.out.fail('error: %s/: %s',
2596 self.project.relpath,
2597 str(self.why))
2598 syncbuf.out.nl()
2599
2600class _Later(object):
2601 def __init__(self, project, action):
2602 self.project = project
2603 self.action = action
2604
2605 def Run(self, syncbuf):
2606 out = syncbuf.out
2607 out.project('project %s/', self.project.relpath)
2608 out.nl()
2609 try:
2610 self.action()
2611 out.nl()
2612 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002613 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002614 out.nl()
2615 return False
2616
2617class _SyncColoring(Coloring):
2618 def __init__(self, config):
2619 Coloring.__init__(self, config, 'reposync')
2620 self.project = self.printer('header', attr = 'bold')
2621 self.info = self.printer('info')
2622 self.fail = self.printer('fail', fg='red')
2623
2624class SyncBuffer(object):
2625 def __init__(self, config, detach_head=False):
2626 self._messages = []
2627 self._failures = []
2628 self._later_queue1 = []
2629 self._later_queue2 = []
2630
2631 self.out = _SyncColoring(config)
2632 self.out.redirect(sys.stderr)
2633
2634 self.detach_head = detach_head
2635 self.clean = True
2636
2637 def info(self, project, fmt, *args):
2638 self._messages.append(_InfoMessage(project, fmt % args))
2639
2640 def fail(self, project, err=None):
2641 self._failures.append(_Failure(project, err))
2642 self.clean = False
2643
2644 def later1(self, project, what):
2645 self._later_queue1.append(_Later(project, what))
2646
2647 def later2(self, project, what):
2648 self._later_queue2.append(_Later(project, what))
2649
2650 def Finish(self):
2651 self._PrintMessages()
2652 self._RunLater()
2653 self._PrintMessages()
2654 return self.clean
2655
2656 def _RunLater(self):
2657 for q in ['_later_queue1', '_later_queue2']:
2658 if not self._RunQueue(q):
2659 return
2660
2661 def _RunQueue(self, queue):
2662 for m in getattr(self, queue):
2663 if not m.Run(self):
2664 self.clean = False
2665 return False
2666 setattr(self, queue, [])
2667 return True
2668
2669 def _PrintMessages(self):
2670 for m in self._messages:
2671 m.Print(self)
2672 for m in self._failures:
2673 m.Print(self)
2674
2675 self._messages = []
2676 self._failures = []
2677
2678
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002679class MetaProject(Project):
2680 """A special project housed under .repo.
2681 """
2682 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002683 Project.__init__(self,
2684 manifest = manifest,
2685 name = name,
2686 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002687 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002688 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002689 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002690 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002691 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002692 revisionId = None,
2693 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002694
2695 def PreSync(self):
2696 if self.Exists:
2697 cb = self.CurrentBranch
2698 if cb:
2699 base = self.GetBranch(cb).merge
2700 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002701 self.revisionExpr = base
2702 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002703
Florian Vallee5d016502012-06-07 17:19:26 +02002704 def MetaBranchSwitch(self, target):
2705 """ Prepare MetaProject for manifest branch switch
2706 """
2707
2708 # detach and delete manifest branch, allowing a new
2709 # branch to take over
2710 syncbuf = SyncBuffer(self.config, detach_head = True)
2711 self.Sync_LocalHalf(syncbuf)
2712 syncbuf.Finish()
2713
2714 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002715 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002716 capture_stdout = True,
2717 capture_stderr = True).Wait() == 0
2718
2719
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002720 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002721 def LastFetch(self):
2722 try:
2723 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2724 return os.path.getmtime(fh)
2725 except OSError:
2726 return 0
2727
2728 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002729 def HasChanges(self):
2730 """Has the remote received new commits not yet checked out?
2731 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002732 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002733 return False
2734
David Pursehouse8a68ff92012-09-24 12:15:13 +09002735 all_refs = self.bare_ref.all
2736 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002737 head = self.work_git.GetHead()
2738 if head.startswith(R_HEADS):
2739 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002740 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002741 except KeyError:
2742 head = None
2743
2744 if revid == head:
2745 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002746 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002747 return True
2748 return False