blob: a1249a86ef734aa1bf21bf6a5445653151eebde7 [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
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700234class RemoteSpec(object):
235 def __init__(self,
236 name,
237 url = None,
238 review = None):
239 self.name = name
240 self.url = url
241 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242
Doug Anderson37282b42011-03-04 11:54:18 -0800243class RepoHook(object):
244 """A RepoHook contains information about a script to run as a hook.
245
246 Hooks are used to run a python script before running an upload (for instance,
247 to run presubmit checks). Eventually, we may have hooks for other actions.
248
249 This shouldn't be confused with files in the 'repo/hooks' directory. Those
250 files are copied into each '.git/hooks' folder for each project. Repo-level
251 hooks are associated instead with repo actions.
252
253 Hooks are always python. When a hook is run, we will load the hook into the
254 interpreter and execute its main() function.
255 """
256 def __init__(self,
257 hook_type,
258 hooks_project,
259 topdir,
260 abort_if_user_denies=False):
261 """RepoHook constructor.
262
263 Params:
264 hook_type: A string representing the type of hook. This is also used
265 to figure out the name of the file containing the hook. For
266 example: 'pre-upload'.
267 hooks_project: The project containing the repo hooks. If you have a
268 manifest, this is manifest.repo_hooks_project. OK if this is None,
269 which will make the hook a no-op.
270 topdir: Repo's top directory (the one containing the .repo directory).
271 Scripts will run with CWD as this directory. If you have a manifest,
272 this is manifest.topdir
273 abort_if_user_denies: If True, we'll throw a HookError() if the user
274 doesn't allow us to run the hook.
275 """
276 self._hook_type = hook_type
277 self._hooks_project = hooks_project
278 self._topdir = topdir
279 self._abort_if_user_denies = abort_if_user_denies
280
281 # Store the full path to the script for convenience.
282 if self._hooks_project:
283 self._script_fullpath = os.path.join(self._hooks_project.worktree,
284 self._hook_type + '.py')
285 else:
286 self._script_fullpath = None
287
288 def _GetHash(self):
289 """Return a hash of the contents of the hooks directory.
290
291 We'll just use git to do this. This hash has the property that if anything
292 changes in the directory we will return a different has.
293
294 SECURITY CONSIDERATION:
295 This hash only represents the contents of files in the hook directory, not
296 any other files imported or called by hooks. Changes to imported files
297 can change the script behavior without affecting the hash.
298
299 Returns:
300 A string representing the hash. This will always be ASCII so that it can
301 be printed to the user easily.
302 """
303 assert self._hooks_project, "Must have hooks to calculate their hash."
304
305 # We will use the work_git object rather than just calling GetRevisionId().
306 # That gives us a hash of the latest checked in version of the files that
307 # the user will actually be executing. Specifically, GetRevisionId()
308 # doesn't appear to change even if a user checks out a different version
309 # of the hooks repo (via git checkout) nor if a user commits their own revs.
310 #
311 # NOTE: Local (non-committed) changes will not be factored into this hash.
312 # I think this is OK, since we're really only worried about warning the user
313 # about upstream changes.
314 return self._hooks_project.work_git.rev_parse('HEAD')
315
316 def _GetMustVerb(self):
317 """Return 'must' if the hook is required; 'should' if not."""
318 if self._abort_if_user_denies:
319 return 'must'
320 else:
321 return 'should'
322
323 def _CheckForHookApproval(self):
324 """Check to see whether this hook has been approved.
325
326 We'll look at the hash of all of the hooks. If this matches the hash that
327 the user last approved, we're done. If it doesn't, we'll ask the user
328 about approval.
329
330 Note that we ask permission for each individual hook even though we use
331 the hash of all hooks when detecting changes. We'd like the user to be
332 able to approve / deny each hook individually. We only use the hash of all
333 hooks because there is no other easy way to detect changes to local imports.
334
335 Returns:
336 True if this hook is approved to run; False otherwise.
337
338 Raises:
339 HookError: Raised if the user doesn't approve and abort_if_user_denies
340 was passed to the consturctor.
341 """
Doug Anderson37282b42011-03-04 11:54:18 -0800342 hooks_config = self._hooks_project.config
343 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
344
345 # Get the last hash that the user approved for this hook; may be None.
346 old_hash = hooks_config.GetString(git_approval_key)
347
348 # Get the current hash so we can tell if scripts changed since approval.
349 new_hash = self._GetHash()
350
351 if old_hash is not None:
352 # User previously approved hook and asked not to be prompted again.
353 if new_hash == old_hash:
354 # Approval matched. We're done.
355 return True
356 else:
357 # Give the user a reason why we're prompting, since they last told
358 # us to "never ask again".
359 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
360 self._hook_type)
361 else:
362 prompt = ''
363
364 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
365 if sys.stdout.isatty():
366 prompt += ('Repo %s run the script:\n'
367 ' %s\n'
368 '\n'
369 'Do you want to allow this script to run '
370 '(yes/yes-never-ask-again/NO)? ') % (
371 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530372 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900373 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800374
375 # User is doing a one-time approval.
376 if response in ('y', 'yes'):
377 return True
378 elif response == 'yes-never-ask-again':
379 hooks_config.SetString(git_approval_key, new_hash)
380 return True
381
382 # For anything else, we'll assume no approval.
383 if self._abort_if_user_denies:
384 raise HookError('You must allow the %s hook or use --no-verify.' %
385 self._hook_type)
386
387 return False
388
389 def _ExecuteHook(self, **kwargs):
390 """Actually execute the given hook.
391
392 This will run the hook's 'main' function in our python interpreter.
393
394 Args:
395 kwargs: Keyword arguments to pass to the hook. These are often specific
396 to the hook type. For instance, pre-upload hooks will contain
397 a project_list.
398 """
399 # Keep sys.path and CWD stashed away so that we can always restore them
400 # upon function exit.
401 orig_path = os.getcwd()
402 orig_syspath = sys.path
403
404 try:
405 # Always run hooks with CWD as topdir.
406 os.chdir(self._topdir)
407
408 # Put the hook dir as the first item of sys.path so hooks can do
409 # relative imports. We want to replace the repo dir as [0] so
410 # hooks can't import repo files.
411 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
412
413 # Exec, storing global context in the context dict. We catch exceptions
414 # and convert to a HookError w/ just the failing traceback.
415 context = {}
416 try:
417 execfile(self._script_fullpath, context)
418 except Exception:
419 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
420 traceback.format_exc(), self._hook_type))
421
422 # Running the script should have defined a main() function.
423 if 'main' not in context:
424 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
425
426
427 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
428 # We don't actually want hooks to define their main with this argument--
429 # it's there to remind them that their hook should always take **kwargs.
430 # For instance, a pre-upload hook should be defined like:
431 # def main(project_list, **kwargs):
432 #
433 # This allows us to later expand the API without breaking old hooks.
434 kwargs = kwargs.copy()
435 kwargs['hook_should_take_kwargs'] = True
436
437 # Call the main function in the hook. If the hook should cause the
438 # build to fail, it will raise an Exception. We'll catch that convert
439 # to a HookError w/ just the failing traceback.
440 try:
441 context['main'](**kwargs)
442 except Exception:
443 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
444 'above.' % (
445 traceback.format_exc(), self._hook_type))
446 finally:
447 # Restore sys.path and CWD.
448 sys.path = orig_syspath
449 os.chdir(orig_path)
450
451 def Run(self, user_allows_all_hooks, **kwargs):
452 """Run the hook.
453
454 If the hook doesn't exist (because there is no hooks project or because
455 this particular hook is not enabled), this is a no-op.
456
457 Args:
458 user_allows_all_hooks: If True, we will never prompt about running the
459 hook--we'll just assume it's OK to run it.
460 kwargs: Keyword arguments to pass to the hook. These are often specific
461 to the hook type. For instance, pre-upload hooks will contain
462 a project_list.
463
464 Raises:
465 HookError: If there was a problem finding the hook or the user declined
466 to run a required hook (from _CheckForHookApproval).
467 """
468 # No-op if there is no hooks project or if hook is disabled.
469 if ((not self._hooks_project) or
470 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
471 return
472
473 # Bail with a nice error if we can't find the hook.
474 if not os.path.isfile(self._script_fullpath):
475 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
476
477 # Make sure the user is OK with running the hook.
478 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
479 return
480
481 # Run the hook with the same version of python we're using.
482 self._ExecuteHook(**kwargs)
483
484
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700485class Project(object):
486 def __init__(self,
487 manifest,
488 name,
489 remote,
490 gitdir,
David James8d201162013-10-11 17:03:19 -0700491 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700492 worktree,
493 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700494 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800495 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700496 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700497 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700498 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800499 sync_s = False,
David Pursehouseede7f122012-11-27 22:25:30 +0900500 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800501 upstream = None,
502 parent = None,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400503 is_derived = False,
504 dest_branch = None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800505 """Init a Project object.
506
507 Args:
508 manifest: The XmlManifest object.
509 name: The `name` attribute of manifest.xml's project element.
510 remote: RemoteSpec object specifying its remote's properties.
511 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700512 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800513 worktree: Absolute path of git working tree.
514 relpath: Relative path of git working tree to repo's top directory.
515 revisionExpr: The `revision` attribute of manifest.xml's project element.
516 revisionId: git commit id for checking out.
517 rebase: The `rebase` attribute of manifest.xml's project element.
518 groups: The `groups` attribute of manifest.xml's project element.
519 sync_c: The `sync-c` attribute of manifest.xml's project element.
520 sync_s: The `sync-s` attribute of manifest.xml's project element.
521 upstream: The `upstream` attribute of manifest.xml's project element.
522 parent: The parent Project object.
523 is_derived: False if the project was explicitly defined in the manifest;
524 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400525 dest_branch: The branch to which to push changes for review by default.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800526 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700527 self.manifest = manifest
528 self.name = name
529 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800530 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700531 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800532 if worktree:
533 self.worktree = worktree.replace('\\', '/')
534 else:
535 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700536 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700537 self.revisionExpr = revisionExpr
538
539 if revisionId is None \
540 and revisionExpr \
541 and IsId(revisionExpr):
542 self.revisionId = revisionExpr
543 else:
544 self.revisionId = revisionId
545
Mike Pontillod3153822012-02-28 11:53:24 -0800546 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700547 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700548 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800549 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900550 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700551 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800552 self.parent = parent
553 self.is_derived = is_derived
554 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800555
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700556 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700557 self.copyfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500558 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700559 self.config = GitConfig.ForRepository(
560 gitdir = self.gitdir,
561 defaults = self.manifest.globalConfig)
562
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800563 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700564 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800565 else:
566 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700567 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700568 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700569 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400570 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700571
Doug Anderson37282b42011-03-04 11:54:18 -0800572 # This will be filled in if a project is later identified to be the
573 # project containing repo hooks.
574 self.enabled_repo_hooks = []
575
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700576 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800577 def Derived(self):
578 return self.is_derived
579
580 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700581 def Exists(self):
582 return os.path.isdir(self.gitdir)
583
584 @property
585 def CurrentBranch(self):
586 """Obtain the name of the currently checked out branch.
587 The branch name omits the 'refs/heads/' prefix.
588 None is returned if the project is on a detached HEAD.
589 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700590 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700591 if b.startswith(R_HEADS):
592 return b[len(R_HEADS):]
593 return None
594
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700595 def IsRebaseInProgress(self):
596 w = self.worktree
597 g = os.path.join(w, '.git')
598 return os.path.exists(os.path.join(g, 'rebase-apply')) \
599 or os.path.exists(os.path.join(g, 'rebase-merge')) \
600 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200601
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700602 def IsDirty(self, consider_untracked=True):
603 """Is the working directory modified in some way?
604 """
605 self.work_git.update_index('-q',
606 '--unmerged',
607 '--ignore-missing',
608 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900609 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700610 return True
611 if self.work_git.DiffZ('diff-files'):
612 return True
613 if consider_untracked and self.work_git.LsOthers():
614 return True
615 return False
616
617 _userident_name = None
618 _userident_email = None
619
620 @property
621 def UserName(self):
622 """Obtain the user's personal name.
623 """
624 if self._userident_name is None:
625 self._LoadUserIdentity()
626 return self._userident_name
627
628 @property
629 def UserEmail(self):
630 """Obtain the user's email address. This is very likely
631 to be their Gerrit login.
632 """
633 if self._userident_email is None:
634 self._LoadUserIdentity()
635 return self._userident_email
636
637 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900638 u = self.bare_git.var('GIT_COMMITTER_IDENT')
639 m = re.compile("^(.*) <([^>]*)> ").match(u)
640 if m:
641 self._userident_name = m.group(1)
642 self._userident_email = m.group(2)
643 else:
644 self._userident_name = ''
645 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700646
647 def GetRemote(self, name):
648 """Get the configuration for a single remote.
649 """
650 return self.config.GetRemote(name)
651
652 def GetBranch(self, name):
653 """Get the configuration for a single branch.
654 """
655 return self.config.GetBranch(name)
656
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700657 def GetBranches(self):
658 """Get all existing local branches.
659 """
660 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900661 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700662 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700663
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530664 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700665 if name.startswith(R_HEADS):
666 name = name[len(R_HEADS):]
667 b = self.GetBranch(name)
668 b.current = name == current
669 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900670 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700671 heads[name] = b
672
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530673 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700674 if name.startswith(R_PUB):
675 name = name[len(R_PUB):]
676 b = heads.get(name)
677 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900678 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700679
680 return heads
681
Colin Cross5acde752012-03-28 20:15:45 -0700682 def MatchesGroups(self, manifest_groups):
683 """Returns true if the manifest groups specified at init should cause
684 this project to be synced.
685 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700686 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700687
Conley Owens971de8e2012-04-16 10:36:08 -0700688 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700689 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700690 manifest_groups: "-group1,group2"
691 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500692
693 The special manifest group "default" will match any project that
694 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700695 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500696 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700697 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500698 if not 'notdefault' in expanded_project_groups:
699 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700700
Conley Owens971de8e2012-04-16 10:36:08 -0700701 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700702 for group in expanded_manifest_groups:
703 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700704 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700705 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700706 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700707
Conley Owens971de8e2012-04-16 10:36:08 -0700708 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700709
710## Status Display ##
711
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500712 def HasChanges(self):
713 """Returns true if there are uncommitted changes.
714 """
715 self.work_git.update_index('-q',
716 '--unmerged',
717 '--ignore-missing',
718 '--refresh')
719 if self.IsRebaseInProgress():
720 return True
721
722 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
723 return True
724
725 if self.work_git.DiffZ('diff-files'):
726 return True
727
728 if self.work_git.LsOthers():
729 return True
730
731 return False
732
Terence Haddock4655e812011-03-31 12:33:34 +0200733 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700734 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200735
736 Args:
737 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700738 """
739 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200740 if output_redir == None:
741 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700742 print(file=output_redir)
743 print('project %s/' % self.relpath, file=output_redir)
744 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700745 return
746
747 self.work_git.update_index('-q',
748 '--unmerged',
749 '--ignore-missing',
750 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700751 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700752 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
753 df = self.work_git.DiffZ('diff-files')
754 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100755 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700756 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757
758 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200759 if not output_redir == None:
760 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700761 out.project('project %-40s', self.relpath + '/')
762
763 branch = self.CurrentBranch
764 if branch is None:
765 out.nobranch('(*** NO BRANCH ***)')
766 else:
767 out.branch('branch %s', branch)
768 out.nl()
769
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700770 if rb:
771 out.important('prior sync failed; rebase still in progress')
772 out.nl()
773
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700774 paths = list()
775 paths.extend(di.keys())
776 paths.extend(df.keys())
777 paths.extend(do)
778
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530779 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900780 try:
781 i = di[p]
782 except KeyError:
783 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900785 try:
786 f = df[p]
787 except KeyError:
788 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200789
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900790 if i:
791 i_status = i.status.upper()
792 else:
793 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900795 if f:
796 f_status = f.status.lower()
797 else:
798 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700799
800 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800801 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700802 i.src_path, p, i.level)
803 else:
804 line = ' %s%s\t%s' % (i_status, f_status, p)
805
806 if i and not f:
807 out.added('%s', line)
808 elif (i and f) or (not i and f):
809 out.changed('%s', line)
810 elif not i and not f:
811 out.untracked('%s', line)
812 else:
813 out.write('%s', line)
814 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200815
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700816 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700817
pelyad67872d2012-03-28 14:49:58 +0300818 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700819 """Prints the status of the repository to stdout.
820 """
821 out = DiffColoring(self.config)
822 cmd = ['diff']
823 if out.is_on:
824 cmd.append('--color')
825 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300826 if absolute_paths:
827 cmd.append('--src-prefix=a/%s/' % self.relpath)
828 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829 cmd.append('--')
830 p = GitCommand(self,
831 cmd,
832 capture_stdout = True,
833 capture_stderr = True)
834 has_diff = False
835 for line in p.process.stdout:
836 if not has_diff:
837 out.nl()
838 out.project('project %s/' % self.relpath)
839 out.nl()
840 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700841 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700842 p.Wait()
843
844
845## Publish / Upload ##
846
David Pursehouse8a68ff92012-09-24 12:15:13 +0900847 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848 """Was the branch published (uploaded) for code review?
849 If so, returns the SHA-1 hash of the last published
850 state for the branch.
851 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700852 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900853 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700854 try:
855 return self.bare_git.rev_parse(key)
856 except GitError:
857 return None
858 else:
859 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900860 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700861 except KeyError:
862 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700863
David Pursehouse8a68ff92012-09-24 12:15:13 +0900864 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865 """Prunes any stale published refs.
866 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900867 if all_refs is None:
868 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 heads = set()
870 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530871 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872 if name.startswith(R_HEADS):
873 heads.add(name)
874 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900875 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700876
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530877 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878 n = name[len(R_PUB):]
879 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900880 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700882 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700883 """List any branches which can be uploaded for review.
884 """
885 heads = {}
886 pubed = {}
887
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530888 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900890 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700891 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900892 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893
894 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530895 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900896 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700897 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700898 if selected_branch and branch != selected_branch:
899 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800901 rb = self.GetUploadableBranch(branch)
902 if rb:
903 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904 return ready
905
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800906 def GetUploadableBranch(self, branch_name):
907 """Get a single uploadable branch, or None.
908 """
909 branch = self.GetBranch(branch_name)
910 base = branch.LocalMerge
911 if branch.LocalMerge:
912 rb = ReviewableBranch(self, branch, base)
913 if rb.commits:
914 return rb
915 return None
916
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700917 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700918 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700919 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400920 draft=False,
921 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 """Uploads the named branch for code review.
923 """
924 if branch is None:
925 branch = self.CurrentBranch
926 if branch is None:
927 raise GitError('not currently on a branch')
928
929 branch = self.GetBranch(branch)
930 if not branch.LocalMerge:
931 raise GitError('branch %s does not track a remote' % branch.name)
932 if not branch.remote.review:
933 raise GitError('remote %s has no review url' % branch.remote.name)
934
Bryan Jacobsf609f912013-05-06 13:36:24 -0400935 if dest_branch is None:
936 dest_branch = self.dest_branch
937 if dest_branch is None:
938 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 if not dest_branch.startswith(R_HEADS):
940 dest_branch = R_HEADS + dest_branch
941
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800942 if not branch.remote.projectname:
943 branch.remote.projectname = self.name
944 branch.remote.Save()
945
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800946 url = branch.remote.ReviewUrl(self.UserEmail)
947 if url is None:
948 raise UploadError('review not configured')
949 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800950
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800951 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800952 rp = ['gerrit receive-pack']
953 for e in people[0]:
954 rp.append('--reviewer=%s' % sq(e))
955 for e in people[1]:
956 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800957 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700958
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800959 cmd.append(url)
960
961 if dest_branch.startswith(R_HEADS):
962 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700963
964 upload_type = 'for'
965 if draft:
966 upload_type = 'drafts'
967
968 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
969 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800970 if auto_topic:
971 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800972 if not url.startswith('ssh://'):
973 rp = ['r=%s' % p for p in people[0]] + \
974 ['cc=%s' % p for p in people[1]]
975 if rp:
976 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800977 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800978
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800979 if GitCommand(self, cmd, bare = True).Wait() != 0:
980 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981
982 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
983 self.bare_git.UpdateRef(R_PUB + branch.name,
984 R_HEADS + branch.name,
985 message = msg)
986
987
988## Sync ##
989
Julien Campergue335f5ef2013-10-16 11:02:35 +0200990 def _ExtractArchive(self, tarpath, path=None):
991 """Extract the given tar on its current location
992
993 Args:
994 - tarpath: The path to the actual tar file
995
996 """
997 try:
998 with tarfile.open(tarpath, 'r') as tar:
999 tar.extractall(path=path)
1000 return True
1001 except (IOError, tarfile.TarError) as e:
1002 print("error: Cannot extract archive %s: "
1003 "%s" % (tarpath, str(e)), file=sys.stderr)
1004 return False
1005
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001006 def Sync_NetworkHalf(self,
1007 quiet=False,
1008 is_new=None,
1009 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001010 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001011 no_tags=False,
1012 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013 """Perform only the network IO portion of the sync process.
1014 Local working directory/branch state is not affected.
1015 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001016 if archive and not isinstance(self, MetaProject):
1017 if self.remote.url.startswith(('http://', 'https://')):
1018 print("error: %s: Cannot fetch archives from http/https "
1019 "remotes." % self.name, file=sys.stderr)
1020 return False
1021
1022 name = self.relpath.replace('\\', '/')
1023 name = name.replace('/', '_')
1024 tarpath = '%s.tar' % name
1025 topdir = self.manifest.topdir
1026
1027 try:
1028 self._FetchArchive(tarpath, cwd=topdir)
1029 except GitError as e:
1030 print('error: %s' % str(e), file=sys.stderr)
1031 return False
1032
1033 # From now on, we only need absolute tarpath
1034 tarpath = os.path.join(topdir, tarpath)
1035
1036 if not self._ExtractArchive(tarpath, path=topdir):
1037 return False
1038 try:
1039 os.remove(tarpath)
1040 except OSError as e:
1041 print("warn: Cannot remove archive %s: "
1042 "%s" % (tarpath, str(e)), file=sys.stderr)
1043 self._CopyFiles()
1044 return True
1045
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001046 if is_new is None:
1047 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001048 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001049 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001050 else:
1051 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001052 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001053
1054 if is_new:
1055 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1056 try:
1057 fd = open(alt, 'rb')
1058 try:
1059 alt_dir = fd.readline().rstrip()
1060 finally:
1061 fd.close()
1062 except IOError:
1063 alt_dir = None
1064 else:
1065 alt_dir = None
1066
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001067 if clone_bundle \
1068 and alt_dir is None \
1069 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001070 is_new = False
1071
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001072 if not current_branch_only:
1073 if self.sync_c:
1074 current_branch_only = True
1075 elif not self.manifest._loaded:
1076 # Manifest cannot check defaults until it syncs.
1077 current_branch_only = False
1078 elif self.manifest.default.sync_c:
1079 current_branch_only = True
1080
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001081 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001082 current_branch_only=current_branch_only,
1083 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001084 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001085
1086 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001087 self._InitMRef()
1088 else:
1089 self._InitMirrorHead()
1090 try:
1091 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1092 except OSError:
1093 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001094 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001095
1096 def PostRepoUpgrade(self):
1097 self._InitHooks()
1098
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001099 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001100 for copyfile in self.copyfiles:
1101 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102
David Pursehouse8a68ff92012-09-24 12:15:13 +09001103 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001104 if self.revisionId:
1105 return self.revisionId
1106
1107 rem = self.GetRemote(self.remote.name)
1108 rev = rem.ToLocal(self.revisionExpr)
1109
David Pursehouse8a68ff92012-09-24 12:15:13 +09001110 if all_refs is not None and rev in all_refs:
1111 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001112
1113 try:
1114 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1115 except GitError:
1116 raise ManifestInvalidRevisionError(
1117 'revision %s in %s not found' % (self.revisionExpr,
1118 self.name))
1119
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001120 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121 """Perform only the local IO portion of the sync process.
1122 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001123 """
David James8d201162013-10-11 17:03:19 -07001124 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001125 all_refs = self.bare_ref.all
1126 self.CleanPublishedCache(all_refs)
1127 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001128
David Pursehouse1d947b32012-10-25 12:23:11 +09001129 def _doff():
1130 self._FastForward(revid)
1131 self._CopyFiles()
1132
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001133 head = self.work_git.GetHead()
1134 if head.startswith(R_HEADS):
1135 branch = head[len(R_HEADS):]
1136 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001137 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001138 except KeyError:
1139 head = None
1140 else:
1141 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001142
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001143 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001144 # Currently on a detached HEAD. The user is assumed to
1145 # not have any local modifications worth worrying about.
1146 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001147 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001148 syncbuf.fail(self, _PriorSyncFailedError())
1149 return
1150
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001151 if head == revid:
1152 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001153 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001154 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001155 if not syncbuf.detach_head:
1156 return
1157 else:
1158 lost = self._revlist(not_rev(revid), HEAD)
1159 if lost:
1160 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001161
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001163 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001164 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001165 syncbuf.fail(self, e)
1166 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001168 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001170 if head == revid:
1171 # No changes; don't do anything further.
1172 #
1173 return
1174
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001175 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001177 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001178 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001179 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001180 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001181 syncbuf.info(self,
1182 "leaving %s; does not track upstream",
1183 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001184 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001185 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001186 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001187 syncbuf.fail(self, e)
1188 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001189 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001190 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001191
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001192 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001193 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001195 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196 if not_merged:
1197 if upstream_gain:
1198 # The user has published this branch and some of those
1199 # commits are not yet merged upstream. We do not want
1200 # to rewrite the published commits so we punt.
1201 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001202 syncbuf.fail(self,
1203 "branch %s is published (but not merged) and is now %d commits behind"
1204 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001205 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001206 elif pub == head:
1207 # All published commits are merged, and thus we are a
1208 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001209 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001210 syncbuf.later1(self, _doff)
1211 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001212
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001213 # Examine the local commits not in the remote. Find the
1214 # last one attributed to this user, if any.
1215 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001216 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001217 last_mine = None
1218 cnt_mine = 0
1219 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301220 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001221 if committer_email == self.UserEmail:
1222 last_mine = commit_id
1223 cnt_mine += 1
1224
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001225 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001226 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227
1228 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001229 syncbuf.fail(self, _DirtyError())
1230 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001231
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001232 # If the upstream switched on us, warn the user.
1233 #
1234 if branch.merge != self.revisionExpr:
1235 if branch.merge and self.revisionExpr:
1236 syncbuf.info(self,
1237 'manifest switched %s...%s',
1238 branch.merge,
1239 self.revisionExpr)
1240 elif branch.merge:
1241 syncbuf.info(self,
1242 'manifest no longer tracks %s',
1243 branch.merge)
1244
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001245 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001246 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001247 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001248 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001249 syncbuf.info(self,
1250 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001251 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001252
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001253 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001254 if not ID_RE.match(self.revisionExpr):
1255 # in case of manifest sync the revisionExpr might be a SHA1
1256 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001257 branch.Save()
1258
Mike Pontillod3153822012-02-28 11:53:24 -08001259 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001260 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001261 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001262 self._CopyFiles()
1263 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001264 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001265 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001266 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001267 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001268 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001269 syncbuf.fail(self, e)
1270 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001272 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001273
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001274 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001275 # dest should already be an absolute path, but src is project relative
1276 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001277 abssrc = os.path.join(self.worktree, src)
1278 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279
James W. Mills24c13082012-04-12 15:04:13 -05001280 def AddAnnotation(self, name, value, keep):
1281 self.annotations.append(_Annotation(name, value, keep))
1282
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001283 def DownloadPatchSet(self, change_id, patch_id):
1284 """Download a single patch set of a single change to FETCH_HEAD.
1285 """
1286 remote = self.GetRemote(self.remote.name)
1287
1288 cmd = ['fetch', remote.name]
1289 cmd.append('refs/changes/%2.2d/%d/%d' \
1290 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001291 if GitCommand(self, cmd, bare=True).Wait() != 0:
1292 return None
1293 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001294 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001295 change_id,
1296 patch_id,
1297 self.bare_git.rev_parse('FETCH_HEAD'))
1298
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299
1300## Branch Management ##
1301
1302 def StartBranch(self, name):
1303 """Create a new branch off the manifest's revision.
1304 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001305 head = self.work_git.GetHead()
1306 if head == (R_HEADS + name):
1307 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308
David Pursehouse8a68ff92012-09-24 12:15:13 +09001309 all_refs = self.bare_ref.all
1310 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001311 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001312 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001313 capture_stdout = True,
1314 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001315
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001316 branch = self.GetBranch(name)
1317 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001318 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001319 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001320
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001321 if head.startswith(R_HEADS):
1322 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001323 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001324 except KeyError:
1325 head = None
1326
1327 if revid and head and revid == head:
1328 ref = os.path.join(self.gitdir, R_HEADS + name)
1329 try:
1330 os.makedirs(os.path.dirname(ref))
1331 except OSError:
1332 pass
1333 _lwrite(ref, '%s\n' % revid)
1334 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1335 'ref: %s%s\n' % (R_HEADS, name))
1336 branch.Save()
1337 return True
1338
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001339 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001340 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001341 capture_stdout = True,
1342 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001343 branch.Save()
1344 return True
1345 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001346
Wink Saville02d79452009-04-10 13:01:24 -07001347 def CheckoutBranch(self, name):
1348 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001349
1350 Args:
1351 name: The name of the branch to checkout.
1352
1353 Returns:
1354 True if the checkout succeeded; False if it didn't; None if the branch
1355 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001356 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001357 rev = R_HEADS + name
1358 head = self.work_git.GetHead()
1359 if head == rev:
1360 # Already on the branch
1361 #
1362 return True
Wink Saville02d79452009-04-10 13:01:24 -07001363
David Pursehouse8a68ff92012-09-24 12:15:13 +09001364 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001365 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001366 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001367 except KeyError:
1368 # Branch does not exist in this project
1369 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001370 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001371
1372 if head.startswith(R_HEADS):
1373 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001374 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001375 except KeyError:
1376 head = None
1377
1378 if head == revid:
1379 # Same revision; just update HEAD to point to the new
1380 # target branch, but otherwise take no other action.
1381 #
1382 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1383 'ref: %s%s\n' % (R_HEADS, name))
1384 return True
Wink Saville02d79452009-04-10 13:01:24 -07001385
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001386 return GitCommand(self,
1387 ['checkout', name, '--'],
1388 capture_stdout = True,
1389 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001390
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001391 def AbandonBranch(self, name):
1392 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001393
1394 Args:
1395 name: The name of the branch to abandon.
1396
1397 Returns:
1398 True if the abandon succeeded; False if it didn't; None if the branch
1399 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001400 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001401 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001402 all_refs = self.bare_ref.all
1403 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001404 # Doesn't exist
1405 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001406
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001407 head = self.work_git.GetHead()
1408 if head == rev:
1409 # We can't destroy the branch while we are sitting
1410 # on it. Switch to a detached HEAD.
1411 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001412 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001413
David Pursehouse8a68ff92012-09-24 12:15:13 +09001414 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001415 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001416 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1417 '%s\n' % revid)
1418 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001419 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001420
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001421 return GitCommand(self,
1422 ['branch', '-D', name],
1423 capture_stdout = True,
1424 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001425
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001426 def PruneHeads(self):
1427 """Prune any topic branches already merged into upstream.
1428 """
1429 cb = self.CurrentBranch
1430 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001431 left = self._allrefs
1432 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001433 if name.startswith(R_HEADS):
1434 name = name[len(R_HEADS):]
1435 if cb is None or name != cb:
1436 kill.append(name)
1437
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001438 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001439 if cb is not None \
1440 and not self._revlist(HEAD + '...' + rev) \
1441 and not self.IsDirty(consider_untracked = False):
1442 self.work_git.DetachHead(HEAD)
1443 kill.append(cb)
1444
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001445 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001446 old = self.bare_git.GetHead()
1447 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001448 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1449
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001450 try:
1451 self.bare_git.DetachHead(rev)
1452
1453 b = ['branch', '-d']
1454 b.extend(kill)
1455 b = GitCommand(self, b, bare=True,
1456 capture_stdout=True,
1457 capture_stderr=True)
1458 b.Wait()
1459 finally:
1460 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001461 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001462
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001463 for branch in kill:
1464 if (R_HEADS + branch) not in left:
1465 self.CleanPublishedCache()
1466 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001467
1468 if cb and cb not in kill:
1469 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001470 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001471
1472 kept = []
1473 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001474 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001475 branch = self.GetBranch(branch)
1476 base = branch.LocalMerge
1477 if not base:
1478 base = rev
1479 kept.append(ReviewableBranch(self, branch, base))
1480 return kept
1481
1482
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001483## Submodule Management ##
1484
1485 def GetRegisteredSubprojects(self):
1486 result = []
1487 def rec(subprojects):
1488 if not subprojects:
1489 return
1490 result.extend(subprojects)
1491 for p in subprojects:
1492 rec(p.subprojects)
1493 rec(self.subprojects)
1494 return result
1495
1496 def _GetSubmodules(self):
1497 # Unfortunately we cannot call `git submodule status --recursive` here
1498 # because the working tree might not exist yet, and it cannot be used
1499 # without a working tree in its current implementation.
1500
1501 def get_submodules(gitdir, rev):
1502 # Parse .gitmodules for submodule sub_paths and sub_urls
1503 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1504 if not sub_paths:
1505 return []
1506 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1507 # revision of submodule repository
1508 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1509 submodules = []
1510 for sub_path, sub_url in zip(sub_paths, sub_urls):
1511 try:
1512 sub_rev = sub_revs[sub_path]
1513 except KeyError:
1514 # Ignore non-exist submodules
1515 continue
1516 submodules.append((sub_rev, sub_path, sub_url))
1517 return submodules
1518
1519 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1520 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1521 def parse_gitmodules(gitdir, rev):
1522 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1523 try:
1524 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1525 bare = True, gitdir = gitdir)
1526 except GitError:
1527 return [], []
1528 if p.Wait() != 0:
1529 return [], []
1530
1531 gitmodules_lines = []
1532 fd, temp_gitmodules_path = tempfile.mkstemp()
1533 try:
1534 os.write(fd, p.stdout)
1535 os.close(fd)
1536 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1537 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1538 bare = True, gitdir = gitdir)
1539 if p.Wait() != 0:
1540 return [], []
1541 gitmodules_lines = p.stdout.split('\n')
1542 except GitError:
1543 return [], []
1544 finally:
1545 os.remove(temp_gitmodules_path)
1546
1547 names = set()
1548 paths = {}
1549 urls = {}
1550 for line in gitmodules_lines:
1551 if not line:
1552 continue
1553 m = re_path.match(line)
1554 if m:
1555 names.add(m.group(1))
1556 paths[m.group(1)] = m.group(2)
1557 continue
1558 m = re_url.match(line)
1559 if m:
1560 names.add(m.group(1))
1561 urls[m.group(1)] = m.group(2)
1562 continue
1563 names = sorted(names)
1564 return ([paths.get(name, '') for name in names],
1565 [urls.get(name, '') for name in names])
1566
1567 def git_ls_tree(gitdir, rev, paths):
1568 cmd = ['ls-tree', rev, '--']
1569 cmd.extend(paths)
1570 try:
1571 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1572 bare = True, gitdir = gitdir)
1573 except GitError:
1574 return []
1575 if p.Wait() != 0:
1576 return []
1577 objects = {}
1578 for line in p.stdout.split('\n'):
1579 if not line.strip():
1580 continue
1581 object_rev, object_path = line.split()[2:4]
1582 objects[object_path] = object_rev
1583 return objects
1584
1585 try:
1586 rev = self.GetRevisionId()
1587 except GitError:
1588 return []
1589 return get_submodules(self.gitdir, rev)
1590
1591 def GetDerivedSubprojects(self):
1592 result = []
1593 if not self.Exists:
1594 # If git repo does not exist yet, querying its submodules will
1595 # mess up its states; so return here.
1596 return result
1597 for rev, path, url in self._GetSubmodules():
1598 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001599 relpath, worktree, gitdir, objdir = \
1600 self.manifest.GetSubprojectPaths(self, name, path)
1601 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001602 if project:
1603 result.extend(project.GetDerivedSubprojects())
1604 continue
David James8d201162013-10-11 17:03:19 -07001605
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001606 remote = RemoteSpec(self.remote.name,
1607 url = url,
1608 review = self.remote.review)
1609 subproject = Project(manifest = self.manifest,
1610 name = name,
1611 remote = remote,
1612 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001613 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001614 worktree = worktree,
1615 relpath = relpath,
1616 revisionExpr = self.revisionExpr,
1617 revisionId = rev,
1618 rebase = self.rebase,
1619 groups = self.groups,
1620 sync_c = self.sync_c,
1621 sync_s = self.sync_s,
1622 parent = self,
1623 is_derived = True)
1624 result.append(subproject)
1625 result.extend(subproject.GetDerivedSubprojects())
1626 return result
1627
1628
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001629## Direct Git Commands ##
1630
Julien Campergue335f5ef2013-10-16 11:02:35 +02001631 def _FetchArchive(self, tarpath, cwd=None):
1632 cmd = ['archive', '-v', '-o', tarpath]
1633 cmd.append('--remote=%s' % self.remote.url)
1634 cmd.append('--prefix=%s/' % self.relpath)
1635 cmd.append(self.revisionExpr)
1636
1637 command = GitCommand(self, cmd, cwd=cwd,
1638 capture_stdout=True,
1639 capture_stderr=True)
1640
1641 if command.Wait() != 0:
1642 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1643
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001644 def _RemoteFetch(self, name=None,
1645 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001646 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001647 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001648 alt_dir=None,
1649 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001650
1651 is_sha1 = False
1652 tag_name = None
1653
Brian Harring14a66742012-09-28 20:21:57 -07001654 def CheckForSha1():
David Pursehousec1b86a22012-11-14 11:36:51 +09001655 try:
1656 # if revision (sha or tag) is not present then following function
1657 # throws an error.
1658 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1659 return True
1660 except GitError:
1661 # There is no such persistent revision. We have to fetch it.
1662 return False
Brian Harring14a66742012-09-28 20:21:57 -07001663
Shawn Pearce69e04d82014-01-29 12:48:54 -08001664 if self.clone_depth:
1665 depth = self.clone_depth
1666 else:
1667 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1668 if depth:
1669 current_branch_only = True
1670
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001671 if current_branch_only:
1672 if ID_RE.match(self.revisionExpr) is not None:
1673 is_sha1 = True
1674 elif self.revisionExpr.startswith(R_TAGS):
1675 # this is a tag and its sha1 value should never change
1676 tag_name = self.revisionExpr[len(R_TAGS):]
1677
1678 if is_sha1 or tag_name is not None:
Brian Harring14a66742012-09-28 20:21:57 -07001679 if CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001680 return True
Brian Harring14a66742012-09-28 20:21:57 -07001681 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1682 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001683
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001684 if not name:
1685 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001686
1687 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001688 remote = self.GetRemote(name)
1689 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001690 ssh_proxy = True
1691
Shawn O. Pearce88443382010-10-08 10:02:09 +02001692 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001693 if alt_dir and 'objects' == os.path.basename(alt_dir):
1694 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001695 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1696 remote = self.GetRemote(name)
1697
David Pursehouse8a68ff92012-09-24 12:15:13 +09001698 all_refs = self.bare_ref.all
1699 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001700 tmp = set()
1701
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301702 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001703 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001704 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001705 all_refs[r] = ref_id
1706 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001707 continue
1708
David Pursehouse8a68ff92012-09-24 12:15:13 +09001709 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001710 continue
1711
David Pursehouse8a68ff92012-09-24 12:15:13 +09001712 r = 'refs/_alt/%s' % ref_id
1713 all_refs[r] = ref_id
1714 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001715 tmp.add(r)
1716
Shawn O. Pearce88443382010-10-08 10:02:09 +02001717 tmp_packed = ''
1718 old_packed = ''
1719
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301720 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001721 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001722 tmp_packed += line
1723 if r not in tmp:
1724 old_packed += line
1725
1726 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001727 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001728 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001729
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001730 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001731
1732 # The --depth option only affects the initial fetch; after that we'll do
1733 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001734 if depth and initial:
1735 cmd.append('--depth=%s' % depth)
1736
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001737 if quiet:
1738 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001739 if not self.worktree:
1740 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001741 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001742
Brian Harring14a66742012-09-28 20:21:57 -07001743 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001744 # Fetch whole repo
Jimmie Wester2f992cb2012-12-07 12:49:51 +01001745 # If using depth then we should not get all the tags since they may
1746 # be outside of the depth.
1747 if no_tags or depth:
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001748 cmd.append('--no-tags')
1749 else:
1750 cmd.append('--tags')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301751 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001752 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001753 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001754 cmd.append(tag_name)
1755 else:
1756 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001757 if is_sha1:
1758 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001759 if branch.startswith(R_HEADS):
1760 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301761 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001762
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001763 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001764 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001765 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1766 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001767 ok = True
1768 break
Brian Harring14a66742012-09-28 20:21:57 -07001769 elif current_branch_only and is_sha1 and ret == 128:
1770 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1771 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1772 # abort the optimization attempt and do a full sync.
1773 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001774 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001775
1776 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001777 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001778 if old_packed != '':
1779 _lwrite(packed_refs, old_packed)
1780 else:
1781 os.remove(packed_refs)
1782 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001783
1784 if is_sha1 and current_branch_only and self.upstream:
1785 # We just synced the upstream given branch; verify we
1786 # got what we wanted, else trigger a second run of all
1787 # refs.
1788 if not CheckForSha1():
1789 return self._RemoteFetch(name=name, current_branch_only=False,
1790 initial=False, quiet=quiet, alt_dir=alt_dir)
1791
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001792 return ok
1793
1794 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001795 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001796 return False
1797
1798 remote = self.GetRemote(self.remote.name)
1799 bundle_url = remote.url + '/clone.bundle'
1800 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001801 if GetSchemeFromUrl(bundle_url) not in (
1802 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001803 return False
1804
1805 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1806 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001807
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001808 exist_dst = os.path.exists(bundle_dst)
1809 exist_tmp = os.path.exists(bundle_tmp)
1810
1811 if not initial and not exist_dst and not exist_tmp:
1812 return False
1813
1814 if not exist_dst:
1815 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1816 if not exist_dst:
1817 return False
1818
1819 cmd = ['fetch']
1820 if quiet:
1821 cmd.append('--quiet')
1822 if not self.worktree:
1823 cmd.append('--update-head-ok')
1824 cmd.append(bundle_dst)
1825 for f in remote.fetch:
1826 cmd.append(str(f))
1827 cmd.append('refs/tags/*:refs/tags/*')
1828
1829 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001830 if os.path.exists(bundle_dst):
1831 os.remove(bundle_dst)
1832 if os.path.exists(bundle_tmp):
1833 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001834 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001835
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001836 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001837 if os.path.exists(dstPath):
1838 os.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07001839
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001840 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001841 if quiet:
1842 cmd += ['--silent']
1843 if os.path.exists(tmpPath):
1844 size = os.stat(tmpPath).st_size
1845 if size >= 1024:
1846 cmd += ['--continue-at', '%d' % (size,)]
1847 else:
1848 os.remove(tmpPath)
1849 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1850 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001851 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001852 if cookiefile:
1853 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001854 if srcUrl.startswith('persistent-'):
1855 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001856 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001857
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001858 if IsTrace():
1859 Trace('%s', ' '.join(cmd))
1860 try:
1861 proc = subprocess.Popen(cmd)
1862 except OSError:
1863 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001864
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001865 curlret = proc.wait()
1866
1867 if curlret == 22:
1868 # From curl man page:
1869 # 22: HTTP page not retrieved. The requested url was not found or
1870 # returned another error with the HTTP error code being 400 or above.
1871 # This return code only appears if -f, --fail is used.
1872 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001873 print("Server does not provide clone.bundle; ignoring.",
1874 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001875 return False
1876
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001877 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001878 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001879 os.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001880 return True
1881 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001882 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001883 return False
1884 else:
1885 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001886
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001887 def _IsValidBundle(self, path):
1888 try:
1889 with open(path) as f:
1890 if f.read(16) == '# v2 git bundle\n':
1891 return True
1892 else:
1893 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1894 return False
1895 except OSError:
1896 return False
1897
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001898 def _GetBundleCookieFile(self, url):
1899 if url.startswith('persistent-'):
1900 try:
1901 p = subprocess.Popen(
1902 ['git-remote-persistent-https', '-print_config', url],
1903 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1904 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001905 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001906 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001907 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001908 for line in p.stdout:
1909 line = line.strip()
1910 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001911 cookiefile = line[len(prefix):]
1912 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001913 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001914 err_msg = p.stderr.read()
1915 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001916 pass # Persistent proxy doesn't support -print_config.
1917 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001918 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001919 if cookiefile:
1920 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001921 except OSError as e:
1922 if e.errno == errno.ENOENT:
1923 pass # No persistent proxy.
1924 raise
1925 return GitConfig.ForUser().GetString('http.cookiefile')
1926
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001927 def _Checkout(self, rev, quiet=False):
1928 cmd = ['checkout']
1929 if quiet:
1930 cmd.append('-q')
1931 cmd.append(rev)
1932 cmd.append('--')
1933 if GitCommand(self, cmd).Wait() != 0:
1934 if self._allrefs:
1935 raise GitError('%s checkout %s ' % (self.name, rev))
1936
Pierre Tardye5a21222011-03-24 16:28:18 +01001937 def _CherryPick(self, rev, quiet=False):
1938 cmd = ['cherry-pick']
1939 cmd.append(rev)
1940 cmd.append('--')
1941 if GitCommand(self, cmd).Wait() != 0:
1942 if self._allrefs:
1943 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1944
Erwan Mahea94f1622011-08-19 13:56:09 +02001945 def _Revert(self, rev, quiet=False):
1946 cmd = ['revert']
1947 cmd.append('--no-edit')
1948 cmd.append(rev)
1949 cmd.append('--')
1950 if GitCommand(self, cmd).Wait() != 0:
1951 if self._allrefs:
1952 raise GitError('%s revert %s ' % (self.name, rev))
1953
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001954 def _ResetHard(self, rev, quiet=True):
1955 cmd = ['reset', '--hard']
1956 if quiet:
1957 cmd.append('-q')
1958 cmd.append(rev)
1959 if GitCommand(self, cmd).Wait() != 0:
1960 raise GitError('%s reset --hard %s ' % (self.name, rev))
1961
1962 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001963 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001964 if onto is not None:
1965 cmd.extend(['--onto', onto])
1966 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001967 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001968 raise GitError('%s rebase %s ' % (self.name, upstream))
1969
Pierre Tardy3d125942012-05-04 12:18:12 +02001970 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001971 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02001972 if ffonly:
1973 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001974 if GitCommand(self, cmd).Wait() != 0:
1975 raise GitError('%s merge %s ' % (self.name, head))
1976
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001977 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001978 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07001979
1980 # Initialize the bare repository, which contains all of the objects.
1981 if not os.path.exists(self.objdir):
1982 os.makedirs(self.objdir)
1983 self.bare_objdir.init()
1984
1985 # If we have a separate directory to hold refs, initialize it as well.
1986 if self.objdir != self.gitdir:
1987 os.makedirs(self.gitdir)
1988 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
1989 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001990
Shawn O. Pearce88443382010-10-08 10:02:09 +02001991 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001992 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02001993
Victor Boivie2b30e3a2012-10-05 12:37:58 +02001994 if ref_dir or mirror_git:
1995 if not mirror_git:
1996 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02001997 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1998 self.relpath + '.git')
1999
2000 if os.path.exists(mirror_git):
2001 ref_dir = mirror_git
2002
2003 elif os.path.exists(repo_git):
2004 ref_dir = repo_git
2005
2006 else:
2007 ref_dir = None
2008
2009 if ref_dir:
2010 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2011 os.path.join(ref_dir, 'objects') + '\n')
2012
Jimmie Westera0444582012-10-24 13:44:42 +02002013 self._UpdateHooks()
2014
2015 m = self.manifest.manifestProject.config
2016 for key in ['user.name', 'user.email']:
2017 if m.Has(key, include_defaults = False):
2018 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002019 if self.manifest.IsMirror:
2020 self.config.SetString('core.bare', 'true')
2021 else:
2022 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002023
Jimmie Westera0444582012-10-24 13:44:42 +02002024 def _UpdateHooks(self):
2025 if os.path.exists(self.gitdir):
2026 # Always recreate hooks since they can have been changed
2027 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002028 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002029 try:
2030 to_rm = os.listdir(hooks)
2031 except OSError:
2032 to_rm = []
2033 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002034 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002035 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002036
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002037 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002038 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002039 if not os.path.exists(hooks):
2040 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002041 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002042 name = os.path.basename(stock_hook)
2043
Victor Boivie65e0f352011-04-18 11:23:29 +02002044 if name in ('commit-msg',) and not self.remote.review \
2045 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002046 # Don't install a Gerrit Code Review hook if this
2047 # project does not appear to use it for reviews.
2048 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002049 # Since the manifest project is one of those, but also
2050 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002051 continue
2052
2053 dst = os.path.join(hooks, name)
2054 if os.path.islink(dst):
2055 continue
2056 if os.path.exists(dst):
2057 if filecmp.cmp(stock_hook, dst, shallow=False):
2058 os.remove(dst)
2059 else:
2060 _error("%s: Not replacing %s hook", self.relpath, name)
2061 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002062 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002063 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002064 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002065 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002066 raise GitError('filesystem must support symlinks')
2067 else:
2068 raise
2069
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002070 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002071 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002072 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002073 remote.url = self.remote.url
2074 remote.review = self.remote.review
2075 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002076
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002077 if self.worktree:
2078 remote.ResetFetch(mirror=False)
2079 else:
2080 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002081 remote.Save()
2082
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002083 def _InitMRef(self):
2084 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002085 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002086
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002087 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002088 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002089
2090 def _InitAnyMRef(self, ref):
2091 cur = self.bare_ref.symref(ref)
2092
2093 if self.revisionId:
2094 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2095 msg = 'manifest set to %s' % self.revisionId
2096 dst = self.revisionId + '^0'
2097 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2098 else:
2099 remote = self.GetRemote(self.remote.name)
2100 dst = remote.ToLocal(self.revisionExpr)
2101 if cur != dst:
2102 msg = 'manifest set to %s' % self.revisionExpr
2103 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002104
David James8d201162013-10-11 17:03:19 -07002105 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2106 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2107
2108 Args:
2109 gitdir: The bare git repository. Must already be initialized.
2110 dotgit: The repository you would like to initialize.
2111 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2112 Only one work tree can store refs under a given |gitdir|.
2113 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2114 This saves you the effort of initializing |dotgit| yourself.
2115 """
2116 # These objects can be shared between several working trees.
2117 symlink_files = ['description', 'info']
2118 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2119 if share_refs:
2120 # These objects can only be used by a single working tree.
2121 symlink_files += ['config', 'packed-refs']
2122 symlink_dirs += ['logs', 'refs']
2123 to_symlink = symlink_files + symlink_dirs
2124
2125 to_copy = []
2126 if copy_all:
2127 to_copy = os.listdir(gitdir)
2128
2129 for name in set(to_copy).union(to_symlink):
2130 try:
2131 src = os.path.realpath(os.path.join(gitdir, name))
2132 dst = os.path.realpath(os.path.join(dotgit, name))
2133
2134 if os.path.lexists(dst) and not os.path.islink(dst):
2135 raise GitError('cannot overwrite a local work tree')
2136
2137 # If the source dir doesn't exist, create an empty dir.
2138 if name in symlink_dirs and not os.path.lexists(src):
2139 os.makedirs(src)
2140
2141 if name in to_symlink:
2142 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2143 elif copy_all and not os.path.islink(dst):
2144 if os.path.isdir(src):
2145 shutil.copytree(src, dst)
2146 elif os.path.isfile(src):
2147 shutil.copy(src, dst)
2148 except OSError as e:
2149 if e.errno == errno.EPERM:
2150 raise GitError('filesystem must support symlinks')
2151 else:
2152 raise
2153
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002154 def _InitWorkTree(self):
2155 dotgit = os.path.join(self.worktree, '.git')
2156 if not os.path.exists(dotgit):
2157 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002158 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2159 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002160
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002161 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002162
2163 cmd = ['read-tree', '--reset', '-u']
2164 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002165 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002166 if GitCommand(self, cmd).Wait() != 0:
2167 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002168
Shawn O. Pearce93609662009-04-21 10:50:33 -07002169 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002170
2171 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002172 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002173
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002174 def _revlist(self, *args, **kw):
2175 a = []
2176 a.extend(args)
2177 a.append('--')
2178 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002179
2180 @property
2181 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002182 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002183
2184 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002185 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002186 self._project = project
2187 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002188 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002189
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002190 def LsOthers(self):
2191 p = GitCommand(self._project,
2192 ['ls-files',
2193 '-z',
2194 '--others',
2195 '--exclude-standard'],
2196 bare = False,
David James8d201162013-10-11 17:03:19 -07002197 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002198 capture_stdout = True,
2199 capture_stderr = True)
2200 if p.Wait() == 0:
2201 out = p.stdout
2202 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002203 return out[:-1].split('\0') # pylint: disable=W1401
2204 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002205 return []
2206
2207 def DiffZ(self, name, *args):
2208 cmd = [name]
2209 cmd.append('-z')
2210 cmd.extend(args)
2211 p = GitCommand(self._project,
2212 cmd,
David James8d201162013-10-11 17:03:19 -07002213 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002214 bare = False,
2215 capture_stdout = True,
2216 capture_stderr = True)
2217 try:
2218 out = p.process.stdout.read()
2219 r = {}
2220 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002221 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002222 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002223 try:
2224 info = out.next()
2225 path = out.next()
2226 except StopIteration:
2227 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002228
2229 class _Info(object):
2230 def __init__(self, path, omode, nmode, oid, nid, state):
2231 self.path = path
2232 self.src_path = None
2233 self.old_mode = omode
2234 self.new_mode = nmode
2235 self.old_id = oid
2236 self.new_id = nid
2237
2238 if len(state) == 1:
2239 self.status = state
2240 self.level = None
2241 else:
2242 self.status = state[:1]
2243 self.level = state[1:]
2244 while self.level.startswith('0'):
2245 self.level = self.level[1:]
2246
2247 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002248 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002249 if info.status in ('R', 'C'):
2250 info.src_path = info.path
2251 info.path = out.next()
2252 r[info.path] = info
2253 return r
2254 finally:
2255 p.Wait()
2256
2257 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002258 if self._bare:
2259 path = os.path.join(self._project.gitdir, HEAD)
2260 else:
2261 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002262 try:
2263 fd = open(path, 'rb')
2264 except IOError:
2265 raise NoManifestException(path)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002266 try:
2267 line = fd.read()
2268 finally:
2269 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302270 try:
2271 line = line.decode()
2272 except AttributeError:
2273 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002274 if line.startswith('ref: '):
2275 return line[5:-1]
2276 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002277
2278 def SetHead(self, ref, message=None):
2279 cmdv = []
2280 if message is not None:
2281 cmdv.extend(['-m', message])
2282 cmdv.append(HEAD)
2283 cmdv.append(ref)
2284 self.symbolic_ref(*cmdv)
2285
2286 def DetachHead(self, new, message=None):
2287 cmdv = ['--no-deref']
2288 if message is not None:
2289 cmdv.extend(['-m', message])
2290 cmdv.append(HEAD)
2291 cmdv.append(new)
2292 self.update_ref(*cmdv)
2293
2294 def UpdateRef(self, name, new, old=None,
2295 message=None,
2296 detach=False):
2297 cmdv = []
2298 if message is not None:
2299 cmdv.extend(['-m', message])
2300 if detach:
2301 cmdv.append('--no-deref')
2302 cmdv.append(name)
2303 cmdv.append(new)
2304 if old is not None:
2305 cmdv.append(old)
2306 self.update_ref(*cmdv)
2307
2308 def DeleteRef(self, name, old=None):
2309 if not old:
2310 old = self.rev_parse(name)
2311 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002312 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002313
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002314 def rev_list(self, *args, **kw):
2315 if 'format' in kw:
2316 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2317 else:
2318 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002319 cmdv.extend(args)
2320 p = GitCommand(self._project,
2321 cmdv,
2322 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002323 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002324 capture_stdout = True,
2325 capture_stderr = True)
2326 r = []
2327 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002328 if line[-1] == '\n':
2329 line = line[:-1]
2330 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002331 if p.Wait() != 0:
2332 raise GitError('%s rev-list %s: %s' % (
2333 self._project.name,
2334 str(args),
2335 p.stderr))
2336 return r
2337
2338 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002339 """Allow arbitrary git commands using pythonic syntax.
2340
2341 This allows you to do things like:
2342 git_obj.rev_parse('HEAD')
2343
2344 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2345 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002346 Any other positional arguments will be passed to the git command, and the
2347 following keyword arguments are supported:
2348 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002349
2350 Args:
2351 name: The name of the git command to call. Any '_' characters will
2352 be replaced with '-'.
2353
2354 Returns:
2355 A callable object that will try to call git with the named command.
2356 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002357 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002358 def runner(*args, **kwargs):
2359 cmdv = []
2360 config = kwargs.pop('config', None)
2361 for k in kwargs:
2362 raise TypeError('%s() got an unexpected keyword argument %r'
2363 % (name, k))
2364 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002365 if not git_require((1, 7, 2)):
2366 raise ValueError('cannot set config on command line for %s()'
2367 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302368 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002369 cmdv.append('-c')
2370 cmdv.append('%s=%s' % (k, v))
2371 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002372 cmdv.extend(args)
2373 p = GitCommand(self._project,
2374 cmdv,
2375 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002376 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002377 capture_stdout = True,
2378 capture_stderr = True)
2379 if p.Wait() != 0:
2380 raise GitError('%s %s: %s' % (
2381 self._project.name,
2382 name,
2383 p.stderr))
2384 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302385 try:
Conley Owensedd01512013-09-26 12:59:58 -07002386 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302387 except AttributeError:
2388 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002389 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2390 return r[:-1]
2391 return r
2392 return runner
2393
2394
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002395class _PriorSyncFailedError(Exception):
2396 def __str__(self):
2397 return 'prior sync failed; rebase still in progress'
2398
2399class _DirtyError(Exception):
2400 def __str__(self):
2401 return 'contains uncommitted changes'
2402
2403class _InfoMessage(object):
2404 def __init__(self, project, text):
2405 self.project = project
2406 self.text = text
2407
2408 def Print(self, syncbuf):
2409 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2410 syncbuf.out.nl()
2411
2412class _Failure(object):
2413 def __init__(self, project, why):
2414 self.project = project
2415 self.why = why
2416
2417 def Print(self, syncbuf):
2418 syncbuf.out.fail('error: %s/: %s',
2419 self.project.relpath,
2420 str(self.why))
2421 syncbuf.out.nl()
2422
2423class _Later(object):
2424 def __init__(self, project, action):
2425 self.project = project
2426 self.action = action
2427
2428 def Run(self, syncbuf):
2429 out = syncbuf.out
2430 out.project('project %s/', self.project.relpath)
2431 out.nl()
2432 try:
2433 self.action()
2434 out.nl()
2435 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002436 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002437 out.nl()
2438 return False
2439
2440class _SyncColoring(Coloring):
2441 def __init__(self, config):
2442 Coloring.__init__(self, config, 'reposync')
2443 self.project = self.printer('header', attr = 'bold')
2444 self.info = self.printer('info')
2445 self.fail = self.printer('fail', fg='red')
2446
2447class SyncBuffer(object):
2448 def __init__(self, config, detach_head=False):
2449 self._messages = []
2450 self._failures = []
2451 self._later_queue1 = []
2452 self._later_queue2 = []
2453
2454 self.out = _SyncColoring(config)
2455 self.out.redirect(sys.stderr)
2456
2457 self.detach_head = detach_head
2458 self.clean = True
2459
2460 def info(self, project, fmt, *args):
2461 self._messages.append(_InfoMessage(project, fmt % args))
2462
2463 def fail(self, project, err=None):
2464 self._failures.append(_Failure(project, err))
2465 self.clean = False
2466
2467 def later1(self, project, what):
2468 self._later_queue1.append(_Later(project, what))
2469
2470 def later2(self, project, what):
2471 self._later_queue2.append(_Later(project, what))
2472
2473 def Finish(self):
2474 self._PrintMessages()
2475 self._RunLater()
2476 self._PrintMessages()
2477 return self.clean
2478
2479 def _RunLater(self):
2480 for q in ['_later_queue1', '_later_queue2']:
2481 if not self._RunQueue(q):
2482 return
2483
2484 def _RunQueue(self, queue):
2485 for m in getattr(self, queue):
2486 if not m.Run(self):
2487 self.clean = False
2488 return False
2489 setattr(self, queue, [])
2490 return True
2491
2492 def _PrintMessages(self):
2493 for m in self._messages:
2494 m.Print(self)
2495 for m in self._failures:
2496 m.Print(self)
2497
2498 self._messages = []
2499 self._failures = []
2500
2501
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002502class MetaProject(Project):
2503 """A special project housed under .repo.
2504 """
2505 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002506 Project.__init__(self,
2507 manifest = manifest,
2508 name = name,
2509 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002510 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002511 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002512 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002513 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002514 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002515 revisionId = None,
2516 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002517
2518 def PreSync(self):
2519 if self.Exists:
2520 cb = self.CurrentBranch
2521 if cb:
2522 base = self.GetBranch(cb).merge
2523 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002524 self.revisionExpr = base
2525 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002526
Florian Vallee5d016502012-06-07 17:19:26 +02002527 def MetaBranchSwitch(self, target):
2528 """ Prepare MetaProject for manifest branch switch
2529 """
2530
2531 # detach and delete manifest branch, allowing a new
2532 # branch to take over
2533 syncbuf = SyncBuffer(self.config, detach_head = True)
2534 self.Sync_LocalHalf(syncbuf)
2535 syncbuf.Finish()
2536
2537 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002538 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002539 capture_stdout = True,
2540 capture_stderr = True).Wait() == 0
2541
2542
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002543 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002544 def LastFetch(self):
2545 try:
2546 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2547 return os.path.getmtime(fh)
2548 except OSError:
2549 return 0
2550
2551 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002552 def HasChanges(self):
2553 """Has the remote received new commits not yet checked out?
2554 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002555 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002556 return False
2557
David Pursehouse8a68ff92012-09-24 12:15:13 +09002558 all_refs = self.bare_ref.all
2559 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002560 head = self.work_git.GetHead()
2561 if head.startswith(R_HEADS):
2562 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002563 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002564 except KeyError:
2565 head = None
2566
2567 if revid == head:
2568 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002569 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002570 return True
2571 return False