blob: 3c2a58a0565572efb4003dd4cab3d1db40725eb3 [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)))
natalie.chenab119022015-04-17 10:11:27 +08001749
1750 ok = False
natalie.chenb7852052015-03-10 16:58:49 +08001751 for _i in range(2):
1752 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy, gitdir=self.localcache).Wait()
1753 if ret == 0:
1754 ok = True
1755 break
1756 elif ret == 128:
1757 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1758 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1759 # abort the optimization attempt and do a full sync.
1760 break
1761 time.sleep(random.randint(30, 45))
1762 if ok and depth:
1763 os.remove('%s/shallow' % self.localcache)
1764
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001765 def _RemoteFetch(self, name=None,
1766 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001767 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001768 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001769 alt_dir=None,
1770 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001771
1772 is_sha1 = False
1773 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001774 depth = None
1775
1776 # The depth should not be used when fetching to a mirror because
1777 # it will result in a shallow repository that cannot be cloned or
1778 # fetched from.
1779 if not self.manifest.IsMirror:
1780 if self.clone_depth:
1781 depth = self.clone_depth
1782 else:
1783 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001784
Shawn Pearce69e04d82014-01-29 12:48:54 -08001785 if depth:
1786 current_branch_only = True
1787
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001788 if current_branch_only:
1789 if ID_RE.match(self.revisionExpr) is not None:
1790 is_sha1 = True
1791 elif self.revisionExpr.startswith(R_TAGS):
1792 # this is a tag and its sha1 value should never change
1793 tag_name = self.revisionExpr[len(R_TAGS):]
1794
1795 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001796 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001797 return True
Brian Harring14a66742012-09-28 20:21:57 -07001798 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1799 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001800
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001801 if not name:
1802 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001803
1804 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001805 remote = self.GetRemote(name)
1806 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001807 ssh_proxy = True
1808
natalie.chenb7852052015-03-10 16:58:49 +08001809 try:
1810 if self.localcache and not depth:
1811 self._UpdateCache(name, ssh_proxy, tag_name, is_sha1)
1812 except Exception as e:
1813 print("update cache failed. %s" % traceback.format_exc(), file=sys.stderr)
1814
Shawn O. Pearce88443382010-10-08 10:02:09 +02001815 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001816 if alt_dir and 'objects' == os.path.basename(alt_dir):
1817 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001818 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1819 remote = self.GetRemote(name)
1820
David Pursehouse8a68ff92012-09-24 12:15:13 +09001821 all_refs = self.bare_ref.all
1822 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001823 tmp = set()
1824
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301825 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001826 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001827 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001828 all_refs[r] = ref_id
1829 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001830 continue
1831
David Pursehouse8a68ff92012-09-24 12:15:13 +09001832 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001833 continue
1834
David Pursehouse8a68ff92012-09-24 12:15:13 +09001835 r = 'refs/_alt/%s' % ref_id
1836 all_refs[r] = ref_id
1837 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001838 tmp.add(r)
1839
Shawn O. Pearce88443382010-10-08 10:02:09 +02001840 tmp_packed = ''
1841 old_packed = ''
1842
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301843 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001844 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001845 tmp_packed += line
1846 if r not in tmp:
1847 old_packed += line
1848
1849 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001850 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001851 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001852
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001853 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001854
1855 # The --depth option only affects the initial fetch; after that we'll do
1856 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001857 if depth and initial:
1858 cmd.append('--depth=%s' % depth)
1859
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001860 if quiet:
1861 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001862 if not self.worktree:
1863 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001864 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001865
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001866 # If using depth then we should not get all the tags since they may
1867 # be outside of the depth.
1868 if no_tags or depth:
1869 cmd.append('--no-tags')
1870 else:
1871 cmd.append('--tags')
1872
Brian Harring14a66742012-09-28 20:21:57 -07001873 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001874 # Fetch whole repo
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301875 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
natalie.chen59964f02014-12-19 20:06:47 +08001876 if self.upstream and self.upstream.startswith('refs/builds'):
1877 cmd.append((u'+%s:' % self.upstream) + remote.ToLocal(self.upstream))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001878 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001879 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001880 cmd.append(tag_name)
1881 else:
1882 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001883 if is_sha1:
1884 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001885 if branch.startswith(R_HEADS):
1886 branch = branch[len(R_HEADS):]
natalie.chen59964f02014-12-19 20:06:47 +08001887 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
1888 else:
1889 cmd.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001890
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001891 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001892 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001893 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1894 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001895 ok = True
1896 break
Brian Harring14a66742012-09-28 20:21:57 -07001897 elif current_branch_only and is_sha1 and ret == 128:
1898 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1899 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1900 # abort the optimization attempt and do a full sync.
1901 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001902 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001903
1904 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001905 # Ensure that some refs exist. Otherwise, we probably aren't looking
1906 # at a real git repository and may have a bad url.
1907 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001908 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001909
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001910 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001911 if old_packed != '':
1912 _lwrite(packed_refs, old_packed)
1913 else:
1914 os.remove(packed_refs)
1915 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001916
1917 if is_sha1 and current_branch_only and self.upstream:
1918 # We just synced the upstream given branch; verify we
1919 # got what we wanted, else trigger a second run of all
1920 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001921 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001922 return self._RemoteFetch(name=name, current_branch_only=False,
1923 initial=False, quiet=quiet, alt_dir=alt_dir)
1924
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001925 return ok
1926
1927 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001928 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001929 return False
1930
1931 remote = self.GetRemote(self.remote.name)
1932 bundle_url = remote.url + '/clone.bundle'
1933 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001934 if GetSchemeFromUrl(bundle_url) not in (
1935 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001936 return False
1937
1938 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1939 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001940
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001941 exist_dst = os.path.exists(bundle_dst)
1942 exist_tmp = os.path.exists(bundle_tmp)
1943
1944 if not initial and not exist_dst and not exist_tmp:
1945 return False
1946
1947 if not exist_dst:
1948 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1949 if not exist_dst:
1950 return False
1951
1952 cmd = ['fetch']
1953 if quiet:
1954 cmd.append('--quiet')
1955 if not self.worktree:
1956 cmd.append('--update-head-ok')
1957 cmd.append(bundle_dst)
1958 for f in remote.fetch:
1959 cmd.append(str(f))
1960 cmd.append('refs/tags/*:refs/tags/*')
1961
1962 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001963 if os.path.exists(bundle_dst):
1964 os.remove(bundle_dst)
1965 if os.path.exists(bundle_tmp):
1966 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001967 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001968
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001969 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001970 if os.path.exists(dstPath):
1971 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001972
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001973 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001974 if quiet:
1975 cmd += ['--silent']
1976 if os.path.exists(tmpPath):
1977 size = os.stat(tmpPath).st_size
1978 if size >= 1024:
1979 cmd += ['--continue-at', '%d' % (size,)]
1980 else:
1981 os.remove(tmpPath)
1982 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1983 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001984 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001985 if cookiefile:
1986 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001987 if srcUrl.startswith('persistent-'):
1988 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001989 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001990
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001991 if IsTrace():
1992 Trace('%s', ' '.join(cmd))
1993 try:
1994 proc = subprocess.Popen(cmd)
1995 except OSError:
1996 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001997
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001998 curlret = proc.wait()
1999
2000 if curlret == 22:
2001 # From curl man page:
2002 # 22: HTTP page not retrieved. The requested url was not found or
2003 # returned another error with the HTTP error code being 400 or above.
2004 # This return code only appears if -f, --fail is used.
2005 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07002006 print("Server does not provide clone.bundle; ignoring.",
2007 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002008 return False
2009
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002010 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002011 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002012 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002013 return True
2014 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002015 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002016 return False
2017 else:
2018 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002019
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002020 def _IsValidBundle(self, path):
2021 try:
2022 with open(path) as f:
2023 if f.read(16) == '# v2 git bundle\n':
2024 return True
2025 else:
2026 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
2027 return False
2028 except OSError:
2029 return False
2030
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002031 def _GetBundleCookieFile(self, url):
2032 if url.startswith('persistent-'):
2033 try:
2034 p = subprocess.Popen(
2035 ['git-remote-persistent-https', '-print_config', url],
2036 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2037 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07002038 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002039 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07002040 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002041 for line in p.stdout:
2042 line = line.strip()
2043 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07002044 cookiefile = line[len(prefix):]
2045 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002046 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08002047 err_msg = p.stderr.read()
2048 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002049 pass # Persistent proxy doesn't support -print_config.
2050 else:
Conley Owenscbc07982013-11-21 10:38:03 -08002051 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07002052 if cookiefile:
2053 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07002054 except OSError as e:
2055 if e.errno == errno.ENOENT:
2056 pass # No persistent proxy.
2057 raise
2058 return GitConfig.ForUser().GetString('http.cookiefile')
2059
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002060 def _Checkout(self, rev, quiet=False):
2061 cmd = ['checkout']
2062 if quiet:
2063 cmd.append('-q')
2064 cmd.append(rev)
2065 cmd.append('--')
2066 if GitCommand(self, cmd).Wait() != 0:
2067 if self._allrefs:
2068 raise GitError('%s checkout %s ' % (self.name, rev))
2069
Pierre Tardye5a21222011-03-24 16:28:18 +01002070 def _CherryPick(self, rev, quiet=False):
2071 cmd = ['cherry-pick']
2072 cmd.append(rev)
2073 cmd.append('--')
2074 if GitCommand(self, cmd).Wait() != 0:
2075 if self._allrefs:
2076 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2077
Erwan Mahea94f1622011-08-19 13:56:09 +02002078 def _Revert(self, rev, quiet=False):
2079 cmd = ['revert']
2080 cmd.append('--no-edit')
2081 cmd.append(rev)
2082 cmd.append('--')
2083 if GitCommand(self, cmd).Wait() != 0:
2084 if self._allrefs:
2085 raise GitError('%s revert %s ' % (self.name, rev))
2086
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002087 def _ResetHard(self, rev, quiet=True):
2088 cmd = ['reset', '--hard']
2089 if quiet:
2090 cmd.append('-q')
2091 cmd.append(rev)
2092 if GitCommand(self, cmd).Wait() != 0:
2093 raise GitError('%s reset --hard %s ' % (self.name, rev))
2094
2095 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002096 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002097 if onto is not None:
2098 cmd.extend(['--onto', onto])
2099 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002100 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002101 raise GitError('%s rebase %s ' % (self.name, upstream))
2102
Pierre Tardy3d125942012-05-04 12:18:12 +02002103 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002104 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002105 if ffonly:
2106 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002107 if GitCommand(self, cmd).Wait() != 0:
2108 raise GitError('%s merge %s ' % (self.name, head))
2109
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002110 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002111 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002112
natalie.chenb7852052015-03-10 16:58:49 +08002113 if self.localcache and not os.path.exists(self.localcache):
natalie.chenab119022015-04-17 10:11:27 +08002114 try:
2115 os.makedirs(self.localcache)
2116 self.bare_cache.init()
2117 except:
2118 print("init local cache %s failed. %s" % (self.localcache, traceback.format_exc()), file=sys.stderr)
2119 self.localcache = None
natalie.chenb7852052015-03-10 16:58:49 +08002120
David James8d201162013-10-11 17:03:19 -07002121 # Initialize the bare repository, which contains all of the objects.
2122 if not os.path.exists(self.objdir):
2123 os.makedirs(self.objdir)
2124 self.bare_objdir.init()
2125
2126 # If we have a separate directory to hold refs, initialize it as well.
2127 if self.objdir != self.gitdir:
2128 os.makedirs(self.gitdir)
2129 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2130 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002131
Shawn O. Pearce88443382010-10-08 10:02:09 +02002132 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002133 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002134
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002135 if ref_dir or mirror_git:
2136 if not mirror_git:
2137 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002138 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2139 self.relpath + '.git')
2140
2141 if os.path.exists(mirror_git):
2142 ref_dir = mirror_git
2143
2144 elif os.path.exists(repo_git):
2145 ref_dir = repo_git
2146
2147 else:
2148 ref_dir = None
2149
2150 if ref_dir:
2151 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2152 os.path.join(ref_dir, 'objects') + '\n')
natalie.chenb7852052015-03-10 16:58:49 +08002153 elif self.localcache:
2154 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2155 os.path.join(self.localcache, 'objects') + '\n')
2156
Shawn O. Pearce88443382010-10-08 10:02:09 +02002157
Jimmie Westera0444582012-10-24 13:44:42 +02002158 self._UpdateHooks()
2159
2160 m = self.manifest.manifestProject.config
2161 for key in ['user.name', 'user.email']:
2162 if m.Has(key, include_defaults = False):
2163 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002164 if self.manifest.IsMirror:
2165 self.config.SetString('core.bare', 'true')
2166 else:
2167 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002168
Jimmie Westera0444582012-10-24 13:44:42 +02002169 def _UpdateHooks(self):
2170 if os.path.exists(self.gitdir):
2171 # Always recreate hooks since they can have been changed
2172 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002173 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002174 try:
2175 to_rm = os.listdir(hooks)
2176 except OSError:
2177 to_rm = []
2178 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002179 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002180 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002181
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002182 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002183 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002184 if not os.path.exists(hooks):
2185 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002186 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002187 name = os.path.basename(stock_hook)
2188
Victor Boivie65e0f352011-04-18 11:23:29 +02002189 if name in ('commit-msg',) and not self.remote.review \
2190 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002191 # Don't install a Gerrit Code Review hook if this
2192 # project does not appear to use it for reviews.
2193 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002194 # Since the manifest project is one of those, but also
2195 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002196 continue
2197
2198 dst = os.path.join(hooks, name)
2199 if os.path.islink(dst):
2200 continue
2201 if os.path.exists(dst):
2202 if filecmp.cmp(stock_hook, dst, shallow=False):
2203 os.remove(dst)
2204 else:
2205 _error("%s: Not replacing %s hook", self.relpath, name)
2206 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002207 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002208 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002209 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002210 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002211 raise GitError('filesystem must support symlinks')
2212 else:
2213 raise
2214
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002215 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002216 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002217 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002218 remote.url = self.remote.url
2219 remote.review = self.remote.review
2220 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002221
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002222 if self.worktree:
2223 remote.ResetFetch(mirror=False)
2224 else:
2225 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002226 remote.Save()
2227
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002228 def _InitMRef(self):
2229 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002230 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002231
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002232 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002233 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002234
2235 def _InitAnyMRef(self, ref):
2236 cur = self.bare_ref.symref(ref)
2237
2238 if self.revisionId:
2239 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2240 msg = 'manifest set to %s' % self.revisionId
2241 dst = self.revisionId + '^0'
2242 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2243 else:
2244 remote = self.GetRemote(self.remote.name)
2245 dst = remote.ToLocal(self.revisionExpr)
2246 if cur != dst:
2247 msg = 'manifest set to %s' % self.revisionExpr
2248 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002249
David James8d201162013-10-11 17:03:19 -07002250 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2251 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2252
2253 Args:
2254 gitdir: The bare git repository. Must already be initialized.
2255 dotgit: The repository you would like to initialize.
2256 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2257 Only one work tree can store refs under a given |gitdir|.
2258 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2259 This saves you the effort of initializing |dotgit| yourself.
2260 """
2261 # These objects can be shared between several working trees.
2262 symlink_files = ['description', 'info']
2263 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2264 if share_refs:
2265 # These objects can only be used by a single working tree.
Conley Owensf2af7562014-04-30 11:31:01 -07002266 symlink_files += ['config', 'packed-refs', 'shallow']
David James8d201162013-10-11 17:03:19 -07002267 symlink_dirs += ['logs', 'refs']
2268 to_symlink = symlink_files + symlink_dirs
2269
2270 to_copy = []
2271 if copy_all:
2272 to_copy = os.listdir(gitdir)
2273
2274 for name in set(to_copy).union(to_symlink):
2275 try:
2276 src = os.path.realpath(os.path.join(gitdir, name))
2277 dst = os.path.realpath(os.path.join(dotgit, name))
2278
2279 if os.path.lexists(dst) and not os.path.islink(dst):
2280 raise GitError('cannot overwrite a local work tree')
2281
2282 # If the source dir doesn't exist, create an empty dir.
2283 if name in symlink_dirs and not os.path.lexists(src):
2284 os.makedirs(src)
2285
2286 if name in to_symlink:
2287 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2288 elif copy_all and not os.path.islink(dst):
2289 if os.path.isdir(src):
2290 shutil.copytree(src, dst)
2291 elif os.path.isfile(src):
2292 shutil.copy(src, dst)
2293 except OSError as e:
2294 if e.errno == errno.EPERM:
2295 raise GitError('filesystem must support symlinks')
2296 else:
2297 raise
2298
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002299 def _InitWorkTree(self):
2300 dotgit = os.path.join(self.worktree, '.git')
2301 if not os.path.exists(dotgit):
2302 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002303 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2304 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002305
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002306 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002307
2308 cmd = ['read-tree', '--reset', '-u']
2309 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002310 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002311 if GitCommand(self, cmd).Wait() != 0:
2312 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002313
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002314 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002315
2316 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002317 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002318
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002319 def _revlist(self, *args, **kw):
2320 a = []
2321 a.extend(args)
2322 a.append('--')
2323 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002324
2325 @property
2326 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002327 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002328
Julien Camperguedd654222014-01-09 16:21:37 +01002329 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2330 """Get logs between two revisions of this project."""
2331 comp = '..'
2332 if rev1:
2333 revs = [rev1]
2334 if rev2:
2335 revs.extend([comp, rev2])
2336 cmd = ['log', ''.join(revs)]
2337 out = DiffColoring(self.config)
2338 if out.is_on and color:
2339 cmd.append('--color')
2340 if oneline:
2341 cmd.append('--oneline')
2342
2343 try:
2344 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2345 if log.Wait() == 0:
2346 return log.stdout
2347 except GitError:
2348 # worktree may not exist if groups changed for example. In that case,
2349 # try in gitdir instead.
2350 if not os.path.exists(self.worktree):
2351 return self.bare_git.log(*cmd[1:])
2352 else:
2353 raise
2354 return None
2355
2356 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2357 """Get the list of logs from this revision to given revisionId"""
2358 logs = {}
2359 selfId = self.GetRevisionId(self._allrefs)
2360 toId = toProject.GetRevisionId(toProject._allrefs)
2361
2362 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2363 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2364 return logs
2365
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002366 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002367 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002368 self._project = project
2369 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002370 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002371
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002372 def LsOthers(self):
2373 p = GitCommand(self._project,
2374 ['ls-files',
2375 '-z',
2376 '--others',
2377 '--exclude-standard'],
2378 bare = False,
David James8d201162013-10-11 17:03:19 -07002379 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002380 capture_stdout = True,
2381 capture_stderr = True)
2382 if p.Wait() == 0:
2383 out = p.stdout
2384 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002385 return out[:-1].split('\0') # pylint: disable=W1401
2386 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002387 return []
2388
2389 def DiffZ(self, name, *args):
2390 cmd = [name]
2391 cmd.append('-z')
2392 cmd.extend(args)
2393 p = GitCommand(self._project,
2394 cmd,
David James8d201162013-10-11 17:03:19 -07002395 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002396 bare = False,
2397 capture_stdout = True,
2398 capture_stderr = True)
2399 try:
2400 out = p.process.stdout.read()
2401 r = {}
2402 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002403 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002404 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002405 try:
2406 info = out.next()
2407 path = out.next()
2408 except StopIteration:
2409 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002410
2411 class _Info(object):
2412 def __init__(self, path, omode, nmode, oid, nid, state):
2413 self.path = path
2414 self.src_path = None
2415 self.old_mode = omode
2416 self.new_mode = nmode
2417 self.old_id = oid
2418 self.new_id = nid
2419
2420 if len(state) == 1:
2421 self.status = state
2422 self.level = None
2423 else:
2424 self.status = state[:1]
2425 self.level = state[1:]
2426 while self.level.startswith('0'):
2427 self.level = self.level[1:]
2428
2429 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002430 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002431 if info.status in ('R', 'C'):
2432 info.src_path = info.path
2433 info.path = out.next()
2434 r[info.path] = info
2435 return r
2436 finally:
2437 p.Wait()
2438
2439 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002440 if self._bare:
2441 path = os.path.join(self._project.gitdir, HEAD)
2442 else:
2443 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002444 try:
2445 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002446 except IOError as e:
2447 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002448 try:
2449 line = fd.read()
2450 finally:
2451 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302452 try:
2453 line = line.decode()
2454 except AttributeError:
2455 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002456 if line.startswith('ref: '):
2457 return line[5:-1]
2458 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002459
2460 def SetHead(self, ref, message=None):
2461 cmdv = []
2462 if message is not None:
2463 cmdv.extend(['-m', message])
2464 cmdv.append(HEAD)
2465 cmdv.append(ref)
2466 self.symbolic_ref(*cmdv)
2467
2468 def DetachHead(self, new, message=None):
2469 cmdv = ['--no-deref']
2470 if message is not None:
2471 cmdv.extend(['-m', message])
2472 cmdv.append(HEAD)
2473 cmdv.append(new)
2474 self.update_ref(*cmdv)
2475
2476 def UpdateRef(self, name, new, old=None,
2477 message=None,
2478 detach=False):
2479 cmdv = []
2480 if message is not None:
2481 cmdv.extend(['-m', message])
2482 if detach:
2483 cmdv.append('--no-deref')
2484 cmdv.append(name)
2485 cmdv.append(new)
2486 if old is not None:
2487 cmdv.append(old)
2488 self.update_ref(*cmdv)
2489
2490 def DeleteRef(self, name, old=None):
2491 if not old:
2492 old = self.rev_parse(name)
2493 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002494 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002495
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002496 def rev_list(self, *args, **kw):
2497 if 'format' in kw:
2498 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2499 else:
2500 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002501 cmdv.extend(args)
2502 p = GitCommand(self._project,
2503 cmdv,
2504 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002505 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002506 capture_stdout = True,
2507 capture_stderr = True)
2508 r = []
2509 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002510 if line[-1] == '\n':
2511 line = line[:-1]
2512 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002513 if p.Wait() != 0:
2514 raise GitError('%s rev-list %s: %s' % (
2515 self._project.name,
2516 str(args),
2517 p.stderr))
2518 return r
2519
2520 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002521 """Allow arbitrary git commands using pythonic syntax.
2522
2523 This allows you to do things like:
2524 git_obj.rev_parse('HEAD')
2525
2526 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2527 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002528 Any other positional arguments will be passed to the git command, and the
2529 following keyword arguments are supported:
2530 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002531
2532 Args:
2533 name: The name of the git command to call. Any '_' characters will
2534 be replaced with '-'.
2535
2536 Returns:
2537 A callable object that will try to call git with the named command.
2538 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002539 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002540 def runner(*args, **kwargs):
2541 cmdv = []
2542 config = kwargs.pop('config', None)
2543 for k in kwargs:
2544 raise TypeError('%s() got an unexpected keyword argument %r'
2545 % (name, k))
2546 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002547 if not git_require((1, 7, 2)):
2548 raise ValueError('cannot set config on command line for %s()'
2549 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302550 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002551 cmdv.append('-c')
2552 cmdv.append('%s=%s' % (k, v))
2553 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002554 cmdv.extend(args)
2555 p = GitCommand(self._project,
2556 cmdv,
2557 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002558 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002559 capture_stdout = True,
2560 capture_stderr = True)
2561 if p.Wait() != 0:
2562 raise GitError('%s %s: %s' % (
2563 self._project.name,
2564 name,
2565 p.stderr))
2566 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302567 try:
Conley Owensedd01512013-09-26 12:59:58 -07002568 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302569 except AttributeError:
2570 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002571 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2572 return r[:-1]
2573 return r
2574 return runner
2575
2576
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002577class _PriorSyncFailedError(Exception):
2578 def __str__(self):
2579 return 'prior sync failed; rebase still in progress'
2580
2581class _DirtyError(Exception):
2582 def __str__(self):
2583 return 'contains uncommitted changes'
2584
2585class _InfoMessage(object):
2586 def __init__(self, project, text):
2587 self.project = project
2588 self.text = text
2589
2590 def Print(self, syncbuf):
2591 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2592 syncbuf.out.nl()
2593
2594class _Failure(object):
2595 def __init__(self, project, why):
2596 self.project = project
2597 self.why = why
2598
2599 def Print(self, syncbuf):
2600 syncbuf.out.fail('error: %s/: %s',
2601 self.project.relpath,
2602 str(self.why))
2603 syncbuf.out.nl()
2604
2605class _Later(object):
2606 def __init__(self, project, action):
2607 self.project = project
2608 self.action = action
2609
2610 def Run(self, syncbuf):
2611 out = syncbuf.out
2612 out.project('project %s/', self.project.relpath)
2613 out.nl()
2614 try:
2615 self.action()
2616 out.nl()
2617 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002618 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002619 out.nl()
2620 return False
2621
2622class _SyncColoring(Coloring):
2623 def __init__(self, config):
2624 Coloring.__init__(self, config, 'reposync')
2625 self.project = self.printer('header', attr = 'bold')
2626 self.info = self.printer('info')
2627 self.fail = self.printer('fail', fg='red')
2628
2629class SyncBuffer(object):
2630 def __init__(self, config, detach_head=False):
2631 self._messages = []
2632 self._failures = []
2633 self._later_queue1 = []
2634 self._later_queue2 = []
2635
2636 self.out = _SyncColoring(config)
2637 self.out.redirect(sys.stderr)
2638
2639 self.detach_head = detach_head
2640 self.clean = True
2641
2642 def info(self, project, fmt, *args):
2643 self._messages.append(_InfoMessage(project, fmt % args))
2644
2645 def fail(self, project, err=None):
2646 self._failures.append(_Failure(project, err))
2647 self.clean = False
2648
2649 def later1(self, project, what):
2650 self._later_queue1.append(_Later(project, what))
2651
2652 def later2(self, project, what):
2653 self._later_queue2.append(_Later(project, what))
2654
2655 def Finish(self):
2656 self._PrintMessages()
2657 self._RunLater()
2658 self._PrintMessages()
2659 return self.clean
2660
2661 def _RunLater(self):
2662 for q in ['_later_queue1', '_later_queue2']:
2663 if not self._RunQueue(q):
2664 return
2665
2666 def _RunQueue(self, queue):
2667 for m in getattr(self, queue):
2668 if not m.Run(self):
2669 self.clean = False
2670 return False
2671 setattr(self, queue, [])
2672 return True
2673
2674 def _PrintMessages(self):
2675 for m in self._messages:
2676 m.Print(self)
2677 for m in self._failures:
2678 m.Print(self)
2679
2680 self._messages = []
2681 self._failures = []
2682
2683
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002684class MetaProject(Project):
2685 """A special project housed under .repo.
2686 """
2687 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002688 Project.__init__(self,
2689 manifest = manifest,
2690 name = name,
2691 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002692 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002693 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002694 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002695 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002696 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002697 revisionId = None,
2698 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002699
2700 def PreSync(self):
2701 if self.Exists:
2702 cb = self.CurrentBranch
2703 if cb:
2704 base = self.GetBranch(cb).merge
2705 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002706 self.revisionExpr = base
2707 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002708
Florian Vallee5d016502012-06-07 17:19:26 +02002709 def MetaBranchSwitch(self, target):
2710 """ Prepare MetaProject for manifest branch switch
2711 """
2712
2713 # detach and delete manifest branch, allowing a new
2714 # branch to take over
2715 syncbuf = SyncBuffer(self.config, detach_head = True)
2716 self.Sync_LocalHalf(syncbuf)
2717 syncbuf.Finish()
2718
2719 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002720 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002721 capture_stdout = True,
2722 capture_stderr = True).Wait() == 0
2723
2724
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002725 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002726 def LastFetch(self):
2727 try:
2728 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2729 return os.path.getmtime(fh)
2730 except OSError:
2731 return 0
2732
2733 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002734 def HasChanges(self):
2735 """Has the remote received new commits not yet checked out?
2736 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002737 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002738 return False
2739
David Pursehouse8a68ff92012-09-24 12:15:13 +09002740 all_refs = self.bare_ref.all
2741 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002742 head = self.work_git.GetHead()
2743 if head.startswith(R_HEADS):
2744 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002745 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002746 except KeyError:
2747 head = None
2748
2749 if revid == head:
2750 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002751 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002752 return True
2753 return False