blob: 876b86ac0259b73c5de9015f468d30256ef1b244 [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".
Conley Owens971de8e2012-04-16 10:36:08 -0700687
688 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"
Colin Cross5acde752012-03-28 20:15:45 -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)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800960
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800961 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)
978
979 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
Chris AtLee2fb64662014-01-16 21:32:33 -05001081 is_sha1 = False
1082 if ID_RE.match(self.revisionExpr) is not None:
1083 is_sha1 = True
1084 if is_sha1 and self._CheckForSha1():
1085 # Don't need to fetch since we already have this revision
1086 return True
1087
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001088 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001089 current_branch_only=current_branch_only,
1090 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001091 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001092
1093 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001094 self._InitMRef()
1095 else:
1096 self._InitMirrorHead()
1097 try:
1098 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1099 except OSError:
1100 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001102
1103 def PostRepoUpgrade(self):
1104 self._InitHooks()
1105
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106 def _CopyFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001107 for copyfile in self.copyfiles:
1108 copyfile._Copy()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001109
Julien Camperguedd654222014-01-09 16:21:37 +01001110 def GetCommitRevisionId(self):
1111 """Get revisionId of a commit.
1112
1113 Use this method instead of GetRevisionId to get the id of the commit rather
1114 than the id of the current git object (for example, a tag)
1115
1116 """
1117 if not self.revisionExpr.startswith(R_TAGS):
1118 return self.GetRevisionId(self._allrefs)
1119
1120 try:
1121 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1122 except GitError:
1123 raise ManifestInvalidRevisionError(
1124 'revision %s in %s not found' % (self.revisionExpr,
1125 self.name))
1126
David Pursehouse8a68ff92012-09-24 12:15:13 +09001127 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001128 if self.revisionId:
1129 return self.revisionId
1130
1131 rem = self.GetRemote(self.remote.name)
1132 rev = rem.ToLocal(self.revisionExpr)
1133
David Pursehouse8a68ff92012-09-24 12:15:13 +09001134 if all_refs is not None and rev in all_refs:
1135 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001136
1137 try:
1138 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1139 except GitError:
1140 raise ManifestInvalidRevisionError(
1141 'revision %s in %s not found' % (self.revisionExpr,
1142 self.name))
1143
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001144 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001145 """Perform only the local IO portion of the sync process.
1146 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001147 """
David James8d201162013-10-11 17:03:19 -07001148 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001149 all_refs = self.bare_ref.all
1150 self.CleanPublishedCache(all_refs)
1151 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001152
David Pursehouse1d947b32012-10-25 12:23:11 +09001153 def _doff():
1154 self._FastForward(revid)
1155 self._CopyFiles()
1156
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001157 head = self.work_git.GetHead()
1158 if head.startswith(R_HEADS):
1159 branch = head[len(R_HEADS):]
1160 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001161 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001162 except KeyError:
1163 head = None
1164 else:
1165 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001166
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001167 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001168 # Currently on a detached HEAD. The user is assumed to
1169 # not have any local modifications worth worrying about.
1170 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001171 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001172 syncbuf.fail(self, _PriorSyncFailedError())
1173 return
1174
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001175 if head == revid:
1176 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001177 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001178 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001179 if not syncbuf.detach_head:
1180 return
1181 else:
1182 lost = self._revlist(not_rev(revid), HEAD)
1183 if lost:
1184 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001185
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001186 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001187 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001188 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001189 syncbuf.fail(self, e)
1190 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001191 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001192 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001194 if head == revid:
1195 # No changes; don't do anything further.
1196 #
1197 return
1198
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001199 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001201 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001202 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001203 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001205 syncbuf.info(self,
1206 "leaving %s; does not track upstream",
1207 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001208 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001209 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001210 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001211 syncbuf.fail(self, e)
1212 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001214 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001215
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001216 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001217 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001218 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001219 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001220 if not_merged:
1221 if upstream_gain:
1222 # The user has published this branch and some of those
1223 # commits are not yet merged upstream. We do not want
1224 # to rewrite the published commits so we punt.
1225 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001226 syncbuf.fail(self,
1227 "branch %s is published (but not merged) and is now %d commits behind"
1228 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001229 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001230 elif pub == head:
1231 # All published commits are merged, and thus we are a
1232 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001233 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001234 syncbuf.later1(self, _doff)
1235 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001236
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001237 # Examine the local commits not in the remote. Find the
1238 # last one attributed to this user, if any.
1239 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001240 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001241 last_mine = None
1242 cnt_mine = 0
1243 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301244 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001245 if committer_email == self.UserEmail:
1246 last_mine = commit_id
1247 cnt_mine += 1
1248
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001249 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001250 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001251
1252 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001253 syncbuf.fail(self, _DirtyError())
1254 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001255
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001256 # If the upstream switched on us, warn the user.
1257 #
1258 if branch.merge != self.revisionExpr:
1259 if branch.merge and self.revisionExpr:
1260 syncbuf.info(self,
1261 'manifest switched %s...%s',
1262 branch.merge,
1263 self.revisionExpr)
1264 elif branch.merge:
1265 syncbuf.info(self,
1266 'manifest no longer tracks %s',
1267 branch.merge)
1268
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001269 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001270 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001271 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001272 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001273 syncbuf.info(self,
1274 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001275 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001276
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001277 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001278 if not ID_RE.match(self.revisionExpr):
1279 # in case of manifest sync the revisionExpr might be a SHA1
1280 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001281 branch.Save()
1282
Mike Pontillod3153822012-02-28 11:53:24 -08001283 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001284 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001285 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001286 self._CopyFiles()
1287 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001288 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001289 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001290 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001291 self._CopyFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001292 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001293 syncbuf.fail(self, e)
1294 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001295 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001296 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001298 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299 # dest should already be an absolute path, but src is project relative
1300 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001301 abssrc = os.path.join(self.worktree, src)
1302 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303
James W. Mills24c13082012-04-12 15:04:13 -05001304 def AddAnnotation(self, name, value, keep):
1305 self.annotations.append(_Annotation(name, value, keep))
1306
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001307 def DownloadPatchSet(self, change_id, patch_id):
1308 """Download a single patch set of a single change to FETCH_HEAD.
1309 """
1310 remote = self.GetRemote(self.remote.name)
1311
1312 cmd = ['fetch', remote.name]
1313 cmd.append('refs/changes/%2.2d/%d/%d' \
1314 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001315 if GitCommand(self, cmd, bare=True).Wait() != 0:
1316 return None
1317 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001318 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001319 change_id,
1320 patch_id,
1321 self.bare_git.rev_parse('FETCH_HEAD'))
1322
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323
1324## Branch Management ##
1325
1326 def StartBranch(self, name):
1327 """Create a new branch off the manifest's revision.
1328 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001329 head = self.work_git.GetHead()
1330 if head == (R_HEADS + name):
1331 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001332
David Pursehouse8a68ff92012-09-24 12:15:13 +09001333 all_refs = self.bare_ref.all
1334 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001335 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001336 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001337 capture_stdout = True,
1338 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001339
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001340 branch = self.GetBranch(name)
1341 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001342 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001343 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001344
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001345 if head.startswith(R_HEADS):
1346 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001347 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001348 except KeyError:
1349 head = None
1350
1351 if revid and head and revid == head:
1352 ref = os.path.join(self.gitdir, R_HEADS + name)
1353 try:
1354 os.makedirs(os.path.dirname(ref))
1355 except OSError:
1356 pass
1357 _lwrite(ref, '%s\n' % revid)
1358 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1359 'ref: %s%s\n' % (R_HEADS, name))
1360 branch.Save()
1361 return True
1362
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001363 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001364 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001365 capture_stdout = True,
1366 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001367 branch.Save()
1368 return True
1369 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001370
Wink Saville02d79452009-04-10 13:01:24 -07001371 def CheckoutBranch(self, name):
1372 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001373
1374 Args:
1375 name: The name of the branch to checkout.
1376
1377 Returns:
1378 True if the checkout succeeded; False if it didn't; None if the branch
1379 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001380 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001381 rev = R_HEADS + name
1382 head = self.work_git.GetHead()
1383 if head == rev:
1384 # Already on the branch
1385 #
1386 return True
Wink Saville02d79452009-04-10 13:01:24 -07001387
David Pursehouse8a68ff92012-09-24 12:15:13 +09001388 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001389 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001390 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001391 except KeyError:
1392 # Branch does not exist in this project
1393 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001394 return None
Wink Saville02d79452009-04-10 13:01:24 -07001395
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001396 if head.startswith(R_HEADS):
1397 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001398 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001399 except KeyError:
1400 head = None
1401
1402 if head == revid:
1403 # Same revision; just update HEAD to point to the new
1404 # target branch, but otherwise take no other action.
1405 #
1406 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1407 'ref: %s%s\n' % (R_HEADS, name))
1408 return True
1409
1410 return GitCommand(self,
1411 ['checkout', name, '--'],
1412 capture_stdout = True,
1413 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001414
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001415 def AbandonBranch(self, name):
1416 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001417
1418 Args:
1419 name: The name of the branch to abandon.
1420
1421 Returns:
1422 True if the abandon succeeded; False if it didn't; None if the branch
1423 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001424 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001425 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001426 all_refs = self.bare_ref.all
1427 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001428 # Doesn't exist
1429 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001430
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001431 head = self.work_git.GetHead()
1432 if head == rev:
1433 # We can't destroy the branch while we are sitting
1434 # on it. Switch to a detached HEAD.
1435 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001436 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001437
David Pursehouse8a68ff92012-09-24 12:15:13 +09001438 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001439 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001440 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1441 '%s\n' % revid)
1442 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001443 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001444
1445 return GitCommand(self,
1446 ['branch', '-D', name],
1447 capture_stdout = True,
1448 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001449
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001450 def PruneHeads(self):
1451 """Prune any topic branches already merged into upstream.
1452 """
1453 cb = self.CurrentBranch
1454 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001455 left = self._allrefs
1456 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001457 if name.startswith(R_HEADS):
1458 name = name[len(R_HEADS):]
1459 if cb is None or name != cb:
1460 kill.append(name)
1461
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001462 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001463 if cb is not None \
1464 and not self._revlist(HEAD + '...' + rev) \
1465 and not self.IsDirty(consider_untracked = False):
1466 self.work_git.DetachHead(HEAD)
1467 kill.append(cb)
1468
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001469 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001470 old = self.bare_git.GetHead()
1471 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001472 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1473
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001474 try:
1475 self.bare_git.DetachHead(rev)
1476
1477 b = ['branch', '-d']
1478 b.extend(kill)
1479 b = GitCommand(self, b, bare=True,
1480 capture_stdout=True,
1481 capture_stderr=True)
1482 b.Wait()
1483 finally:
1484 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001485 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001486
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001487 for branch in kill:
1488 if (R_HEADS + branch) not in left:
1489 self.CleanPublishedCache()
1490 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491
1492 if cb and cb not in kill:
1493 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001494 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001495
1496 kept = []
1497 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001498 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001499 branch = self.GetBranch(branch)
1500 base = branch.LocalMerge
1501 if not base:
1502 base = rev
1503 kept.append(ReviewableBranch(self, branch, base))
1504 return kept
1505
1506
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001507## Submodule Management ##
1508
1509 def GetRegisteredSubprojects(self):
1510 result = []
1511 def rec(subprojects):
1512 if not subprojects:
1513 return
1514 result.extend(subprojects)
1515 for p in subprojects:
1516 rec(p.subprojects)
1517 rec(self.subprojects)
1518 return result
1519
1520 def _GetSubmodules(self):
1521 # Unfortunately we cannot call `git submodule status --recursive` here
1522 # because the working tree might not exist yet, and it cannot be used
1523 # without a working tree in its current implementation.
1524
1525 def get_submodules(gitdir, rev):
1526 # Parse .gitmodules for submodule sub_paths and sub_urls
1527 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1528 if not sub_paths:
1529 return []
1530 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1531 # revision of submodule repository
1532 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1533 submodules = []
1534 for sub_path, sub_url in zip(sub_paths, sub_urls):
1535 try:
1536 sub_rev = sub_revs[sub_path]
1537 except KeyError:
1538 # Ignore non-exist submodules
1539 continue
1540 submodules.append((sub_rev, sub_path, sub_url))
1541 return submodules
1542
1543 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1544 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1545 def parse_gitmodules(gitdir, rev):
1546 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1547 try:
1548 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1549 bare = True, gitdir = gitdir)
1550 except GitError:
1551 return [], []
1552 if p.Wait() != 0:
1553 return [], []
1554
1555 gitmodules_lines = []
1556 fd, temp_gitmodules_path = tempfile.mkstemp()
1557 try:
1558 os.write(fd, p.stdout)
1559 os.close(fd)
1560 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1561 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1562 bare = True, gitdir = gitdir)
1563 if p.Wait() != 0:
1564 return [], []
1565 gitmodules_lines = p.stdout.split('\n')
1566 except GitError:
1567 return [], []
1568 finally:
1569 os.remove(temp_gitmodules_path)
1570
1571 names = set()
1572 paths = {}
1573 urls = {}
1574 for line in gitmodules_lines:
1575 if not line:
1576 continue
1577 m = re_path.match(line)
1578 if m:
1579 names.add(m.group(1))
1580 paths[m.group(1)] = m.group(2)
1581 continue
1582 m = re_url.match(line)
1583 if m:
1584 names.add(m.group(1))
1585 urls[m.group(1)] = m.group(2)
1586 continue
1587 names = sorted(names)
1588 return ([paths.get(name, '') for name in names],
1589 [urls.get(name, '') for name in names])
1590
1591 def git_ls_tree(gitdir, rev, paths):
1592 cmd = ['ls-tree', rev, '--']
1593 cmd.extend(paths)
1594 try:
1595 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1596 bare = True, gitdir = gitdir)
1597 except GitError:
1598 return []
1599 if p.Wait() != 0:
1600 return []
1601 objects = {}
1602 for line in p.stdout.split('\n'):
1603 if not line.strip():
1604 continue
1605 object_rev, object_path = line.split()[2:4]
1606 objects[object_path] = object_rev
1607 return objects
1608
1609 try:
1610 rev = self.GetRevisionId()
1611 except GitError:
1612 return []
1613 return get_submodules(self.gitdir, rev)
1614
1615 def GetDerivedSubprojects(self):
1616 result = []
1617 if not self.Exists:
1618 # If git repo does not exist yet, querying its submodules will
1619 # mess up its states; so return here.
1620 return result
1621 for rev, path, url in self._GetSubmodules():
1622 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001623 relpath, worktree, gitdir, objdir = \
1624 self.manifest.GetSubprojectPaths(self, name, path)
1625 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001626 if project:
1627 result.extend(project.GetDerivedSubprojects())
1628 continue
David James8d201162013-10-11 17:03:19 -07001629
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001630 remote = RemoteSpec(self.remote.name,
1631 url = url,
1632 review = self.remote.review)
1633 subproject = Project(manifest = self.manifest,
1634 name = name,
1635 remote = remote,
1636 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001637 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001638 worktree = worktree,
1639 relpath = relpath,
1640 revisionExpr = self.revisionExpr,
1641 revisionId = rev,
1642 rebase = self.rebase,
1643 groups = self.groups,
1644 sync_c = self.sync_c,
1645 sync_s = self.sync_s,
1646 parent = self,
1647 is_derived = True)
1648 result.append(subproject)
1649 result.extend(subproject.GetDerivedSubprojects())
1650 return result
1651
1652
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001653## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001654 def _CheckForSha1(self):
1655 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
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001663
Julien Campergue335f5ef2013-10-16 11:02:35 +02001664 def _FetchArchive(self, tarpath, cwd=None):
1665 cmd = ['archive', '-v', '-o', tarpath]
1666 cmd.append('--remote=%s' % self.remote.url)
1667 cmd.append('--prefix=%s/' % self.relpath)
1668 cmd.append(self.revisionExpr)
1669
1670 command = GitCommand(self, cmd, cwd=cwd,
1671 capture_stdout=True,
1672 capture_stderr=True)
1673
1674 if command.Wait() != 0:
1675 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1676
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001677 def _RemoteFetch(self, name=None,
1678 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001679 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001680 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001681 alt_dir=None,
1682 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001683
1684 is_sha1 = False
1685 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001686 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001687
David Pursehouse9bc422f2014-04-15 10:28:56 +09001688 # The depth should not be used when fetching to a mirror because
1689 # it will result in a shallow repository that cannot be cloned or
1690 # fetched from.
1691 if not self.manifest.IsMirror:
1692 if self.clone_depth:
1693 depth = self.clone_depth
1694 else:
1695 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1696
Shawn Pearce69e04d82014-01-29 12:48:54 -08001697 if depth:
1698 current_branch_only = True
1699
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001700 if current_branch_only:
1701 if ID_RE.match(self.revisionExpr) is not None:
1702 is_sha1 = True
1703 elif self.revisionExpr.startswith(R_TAGS):
1704 # this is a tag and its sha1 value should never change
1705 tag_name = self.revisionExpr[len(R_TAGS):]
1706
1707 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001708 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001709 return True
Brian Harring14a66742012-09-28 20:21:57 -07001710 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1711 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001712
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001713 if not name:
1714 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001715
1716 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001717 remote = self.GetRemote(name)
1718 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001719 ssh_proxy = True
1720
Shawn O. Pearce88443382010-10-08 10:02:09 +02001721 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001722 if alt_dir and 'objects' == os.path.basename(alt_dir):
1723 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001724 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1725 remote = self.GetRemote(name)
1726
David Pursehouse8a68ff92012-09-24 12:15:13 +09001727 all_refs = self.bare_ref.all
1728 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001729 tmp = set()
1730
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301731 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001732 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001733 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001734 all_refs[r] = ref_id
1735 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001736 continue
1737
David Pursehouse8a68ff92012-09-24 12:15:13 +09001738 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001739 continue
1740
David Pursehouse8a68ff92012-09-24 12:15:13 +09001741 r = 'refs/_alt/%s' % ref_id
1742 all_refs[r] = ref_id
1743 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001744 tmp.add(r)
1745
Shawn O. Pearce88443382010-10-08 10:02:09 +02001746 tmp_packed = ''
1747 old_packed = ''
1748
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301749 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001750 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001751 tmp_packed += line
1752 if r not in tmp:
1753 old_packed += line
1754
1755 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001756 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001757 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001758
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001759 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001760
1761 # The --depth option only affects the initial fetch; after that we'll do
1762 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001763 if depth and initial:
1764 cmd.append('--depth=%s' % depth)
1765
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001766 if quiet:
1767 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001768 if not self.worktree:
1769 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001770 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001771
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001772 # If using depth then we should not get all the tags since they may
1773 # be outside of the depth.
1774 if no_tags or depth:
1775 cmd.append('--no-tags')
1776 else:
1777 cmd.append('--tags')
1778
Brian Harring14a66742012-09-28 20:21:57 -07001779 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001780 # Fetch whole repo
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301781 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001782 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001783 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001784 cmd.append(tag_name)
1785 else:
1786 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001787 if is_sha1:
1788 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001789 if branch.startswith(R_HEADS):
1790 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301791 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001792
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001793 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001794 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001795 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1796 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001797 ok = True
1798 break
Brian Harring14a66742012-09-28 20:21:57 -07001799 elif current_branch_only and is_sha1 and ret == 128:
1800 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1801 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1802 # abort the optimization attempt and do a full sync.
1803 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001804 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001805
1806 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001807 # Ensure that some refs exist. Otherwise, we probably aren't looking
1808 # at a real git repository and may have a bad url.
1809 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001810 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001811
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001812 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001813 if old_packed != '':
1814 _lwrite(packed_refs, old_packed)
1815 else:
1816 os.remove(packed_refs)
1817 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001818
1819 if is_sha1 and current_branch_only and self.upstream:
1820 # We just synced the upstream given branch; verify we
1821 # got what we wanted, else trigger a second run of all
1822 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001823 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001824 return self._RemoteFetch(name=name, current_branch_only=False,
1825 initial=False, quiet=quiet, alt_dir=alt_dir)
1826
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001827 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001828
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001829 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001830 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001831 return False
1832
1833 remote = self.GetRemote(self.remote.name)
1834 bundle_url = remote.url + '/clone.bundle'
1835 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001836 if GetSchemeFromUrl(bundle_url) not in (
1837 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001838 return False
1839
1840 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1841 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1842
1843 exist_dst = os.path.exists(bundle_dst)
1844 exist_tmp = os.path.exists(bundle_tmp)
1845
1846 if not initial and not exist_dst and not exist_tmp:
1847 return False
1848
1849 if not exist_dst:
1850 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1851 if not exist_dst:
1852 return False
1853
1854 cmd = ['fetch']
1855 if quiet:
1856 cmd.append('--quiet')
1857 if not self.worktree:
1858 cmd.append('--update-head-ok')
1859 cmd.append(bundle_dst)
1860 for f in remote.fetch:
1861 cmd.append(str(f))
1862 cmd.append('refs/tags/*:refs/tags/*')
1863
1864 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001865 if os.path.exists(bundle_dst):
1866 os.remove(bundle_dst)
1867 if os.path.exists(bundle_tmp):
1868 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001869 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001870
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001871 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001872 if os.path.exists(dstPath):
1873 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001874
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001875 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001876 if quiet:
1877 cmd += ['--silent']
1878 if os.path.exists(tmpPath):
1879 size = os.stat(tmpPath).st_size
1880 if size >= 1024:
1881 cmd += ['--continue-at', '%d' % (size,)]
1882 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001883 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001884 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1885 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001886 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001887 if cookiefile:
1888 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001889 if srcUrl.startswith('persistent-'):
1890 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001891 cmd += [srcUrl]
1892
1893 if IsTrace():
1894 Trace('%s', ' '.join(cmd))
1895 try:
1896 proc = subprocess.Popen(cmd)
1897 except OSError:
1898 return False
1899
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001900 curlret = proc.wait()
1901
1902 if curlret == 22:
1903 # From curl man page:
1904 # 22: HTTP page not retrieved. The requested url was not found or
1905 # returned another error with the HTTP error code being 400 or above.
1906 # This return code only appears if -f, --fail is used.
1907 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001908 print("Server does not provide clone.bundle; ignoring.",
1909 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001910 return False
1911
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001912 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001913 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001914 os.rename(tmpPath, dstPath)
1915 return True
1916 else:
1917 os.remove(tmpPath)
1918 return False
1919 else:
1920 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001921
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001922 def _IsValidBundle(self, path):
1923 try:
1924 with open(path) as f:
1925 if f.read(16) == '# v2 git bundle\n':
1926 return True
1927 else:
1928 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1929 return False
1930 except OSError:
1931 return False
1932
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001933 def _GetBundleCookieFile(self, url):
1934 if url.startswith('persistent-'):
1935 try:
1936 p = subprocess.Popen(
1937 ['git-remote-persistent-https', '-print_config', url],
1938 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1939 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001940 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001941 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001942 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001943 for line in p.stdout:
1944 line = line.strip()
1945 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001946 cookiefile = line[len(prefix):]
1947 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001948 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001949 err_msg = p.stderr.read()
1950 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001951 pass # Persistent proxy doesn't support -print_config.
1952 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001953 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001954 if cookiefile:
1955 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001956 except OSError as e:
1957 if e.errno == errno.ENOENT:
1958 pass # No persistent proxy.
1959 raise
1960 return GitConfig.ForUser().GetString('http.cookiefile')
1961
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001962 def _Checkout(self, rev, quiet=False):
1963 cmd = ['checkout']
1964 if quiet:
1965 cmd.append('-q')
1966 cmd.append(rev)
1967 cmd.append('--')
1968 if GitCommand(self, cmd).Wait() != 0:
1969 if self._allrefs:
1970 raise GitError('%s checkout %s ' % (self.name, rev))
1971
Pierre Tardye5a21222011-03-24 16:28:18 +01001972 def _CherryPick(self, rev, quiet=False):
1973 cmd = ['cherry-pick']
1974 cmd.append(rev)
1975 cmd.append('--')
1976 if GitCommand(self, cmd).Wait() != 0:
1977 if self._allrefs:
1978 raise GitError('%s cherry-pick %s ' % (self.name, rev))
1979
Erwan Mahea94f1622011-08-19 13:56:09 +02001980 def _Revert(self, rev, quiet=False):
1981 cmd = ['revert']
1982 cmd.append('--no-edit')
1983 cmd.append(rev)
1984 cmd.append('--')
1985 if GitCommand(self, cmd).Wait() != 0:
1986 if self._allrefs:
1987 raise GitError('%s revert %s ' % (self.name, rev))
1988
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001989 def _ResetHard(self, rev, quiet=True):
1990 cmd = ['reset', '--hard']
1991 if quiet:
1992 cmd.append('-q')
1993 cmd.append(rev)
1994 if GitCommand(self, cmd).Wait() != 0:
1995 raise GitError('%s reset --hard %s ' % (self.name, rev))
1996
1997 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001998 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001999 if onto is not None:
2000 cmd.extend(['--onto', onto])
2001 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002002 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002003 raise GitError('%s rebase %s ' % (self.name, upstream))
2004
Pierre Tardy3d125942012-05-04 12:18:12 +02002005 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002006 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002007 if ffonly:
2008 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002009 if GitCommand(self, cmd).Wait() != 0:
2010 raise GitError('%s merge %s ' % (self.name, head))
2011
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002012 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002013 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002014
2015 # Initialize the bare repository, which contains all of the objects.
2016 if not os.path.exists(self.objdir):
2017 os.makedirs(self.objdir)
2018 self.bare_objdir.init()
2019
2020 # If we have a separate directory to hold refs, initialize it as well.
2021 if self.objdir != self.gitdir:
2022 os.makedirs(self.gitdir)
2023 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2024 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002025
Shawn O. Pearce88443382010-10-08 10:02:09 +02002026 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002027 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002028
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002029 if ref_dir or mirror_git:
2030 if not mirror_git:
2031 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002032 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2033 self.relpath + '.git')
2034
2035 if os.path.exists(mirror_git):
2036 ref_dir = mirror_git
2037
2038 elif os.path.exists(repo_git):
2039 ref_dir = repo_git
2040
2041 else:
2042 ref_dir = None
2043
2044 if ref_dir:
2045 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2046 os.path.join(ref_dir, 'objects') + '\n')
2047
Jimmie Westera0444582012-10-24 13:44:42 +02002048 self._UpdateHooks()
2049
2050 m = self.manifest.manifestProject.config
2051 for key in ['user.name', 'user.email']:
2052 if m.Has(key, include_defaults = False):
2053 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002054 if self.manifest.IsMirror:
2055 self.config.SetString('core.bare', 'true')
2056 else:
2057 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002058
Jimmie Westera0444582012-10-24 13:44:42 +02002059 def _UpdateHooks(self):
2060 if os.path.exists(self.gitdir):
2061 # Always recreate hooks since they can have been changed
2062 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002063 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002064 try:
2065 to_rm = os.listdir(hooks)
2066 except OSError:
2067 to_rm = []
2068 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002069 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002070 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002071
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002072 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002073 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002074 if not os.path.exists(hooks):
2075 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002076 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002077 name = os.path.basename(stock_hook)
2078
Victor Boivie65e0f352011-04-18 11:23:29 +02002079 if name in ('commit-msg',) and not self.remote.review \
2080 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002081 # Don't install a Gerrit Code Review hook if this
2082 # project does not appear to use it for reviews.
2083 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002084 # Since the manifest project is one of those, but also
2085 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002086 continue
2087
2088 dst = os.path.join(hooks, name)
2089 if os.path.islink(dst):
2090 continue
2091 if os.path.exists(dst):
2092 if filecmp.cmp(stock_hook, dst, shallow=False):
2093 os.remove(dst)
2094 else:
2095 _error("%s: Not replacing %s hook", self.relpath, name)
2096 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002097 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002098 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002099 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002100 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002101 raise GitError('filesystem must support symlinks')
2102 else:
2103 raise
2104
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002105 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002106 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002107 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002108 remote.url = self.remote.url
2109 remote.review = self.remote.review
2110 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002111
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002112 if self.worktree:
2113 remote.ResetFetch(mirror=False)
2114 else:
2115 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002116 remote.Save()
2117
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002118 def _InitMRef(self):
2119 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002120 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002121
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002122 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002123 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002124
2125 def _InitAnyMRef(self, ref):
2126 cur = self.bare_ref.symref(ref)
2127
2128 if self.revisionId:
2129 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2130 msg = 'manifest set to %s' % self.revisionId
2131 dst = self.revisionId + '^0'
2132 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2133 else:
2134 remote = self.GetRemote(self.remote.name)
2135 dst = remote.ToLocal(self.revisionExpr)
2136 if cur != dst:
2137 msg = 'manifest set to %s' % self.revisionExpr
2138 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002139
David James8d201162013-10-11 17:03:19 -07002140 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2141 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2142
2143 Args:
2144 gitdir: The bare git repository. Must already be initialized.
2145 dotgit: The repository you would like to initialize.
2146 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2147 Only one work tree can store refs under a given |gitdir|.
2148 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2149 This saves you the effort of initializing |dotgit| yourself.
2150 """
2151 # These objects can be shared between several working trees.
2152 symlink_files = ['description', 'info']
2153 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2154 if share_refs:
2155 # These objects can only be used by a single working tree.
2156 symlink_files += ['config', 'packed-refs']
2157 symlink_dirs += ['logs', 'refs']
2158 to_symlink = symlink_files + symlink_dirs
2159
2160 to_copy = []
2161 if copy_all:
2162 to_copy = os.listdir(gitdir)
2163
2164 for name in set(to_copy).union(to_symlink):
2165 try:
2166 src = os.path.realpath(os.path.join(gitdir, name))
2167 dst = os.path.realpath(os.path.join(dotgit, name))
2168
2169 if os.path.lexists(dst) and not os.path.islink(dst):
2170 raise GitError('cannot overwrite a local work tree')
2171
2172 # If the source dir doesn't exist, create an empty dir.
2173 if name in symlink_dirs and not os.path.lexists(src):
2174 os.makedirs(src)
2175
2176 if name in to_symlink:
2177 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2178 elif copy_all and not os.path.islink(dst):
2179 if os.path.isdir(src):
2180 shutil.copytree(src, dst)
2181 elif os.path.isfile(src):
2182 shutil.copy(src, dst)
2183 except OSError as e:
2184 if e.errno == errno.EPERM:
2185 raise GitError('filesystem must support symlinks')
2186 else:
2187 raise
2188
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002189 def _InitWorkTree(self):
2190 dotgit = os.path.join(self.worktree, '.git')
2191 if not os.path.exists(dotgit):
2192 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002193 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2194 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002195
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002196 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002197
2198 cmd = ['read-tree', '--reset', '-u']
2199 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002200 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002201 if GitCommand(self, cmd).Wait() != 0:
2202 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002203
Shawn O. Pearce93609662009-04-21 10:50:33 -07002204 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002205
2206 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002207 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002208
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002209 def _revlist(self, *args, **kw):
2210 a = []
2211 a.extend(args)
2212 a.append('--')
2213 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002214
2215 @property
2216 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002217 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002218
Julien Camperguedd654222014-01-09 16:21:37 +01002219 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2220 """Get logs between two revisions of this project."""
2221 comp = '..'
2222 if rev1:
2223 revs = [rev1]
2224 if rev2:
2225 revs.extend([comp, rev2])
2226 cmd = ['log', ''.join(revs)]
2227 out = DiffColoring(self.config)
2228 if out.is_on and color:
2229 cmd.append('--color')
2230 if oneline:
2231 cmd.append('--oneline')
2232
2233 try:
2234 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2235 if log.Wait() == 0:
2236 return log.stdout
2237 except GitError:
2238 # worktree may not exist if groups changed for example. In that case,
2239 # try in gitdir instead.
2240 if not os.path.exists(self.worktree):
2241 return self.bare_git.log(*cmd[1:])
2242 else:
2243 raise
2244 return None
2245
2246 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2247 """Get the list of logs from this revision to given revisionId"""
2248 logs = {}
2249 selfId = self.GetRevisionId(self._allrefs)
2250 toId = toProject.GetRevisionId(toProject._allrefs)
2251
2252 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2253 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2254 return logs
2255
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002256 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002257 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002258 self._project = project
2259 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002260 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002261
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002262 def LsOthers(self):
2263 p = GitCommand(self._project,
2264 ['ls-files',
2265 '-z',
2266 '--others',
2267 '--exclude-standard'],
2268 bare = False,
David James8d201162013-10-11 17:03:19 -07002269 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002270 capture_stdout = True,
2271 capture_stderr = True)
2272 if p.Wait() == 0:
2273 out = p.stdout
2274 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002275 return out[:-1].split('\0') # pylint: disable=W1401
2276 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002277 return []
2278
2279 def DiffZ(self, name, *args):
2280 cmd = [name]
2281 cmd.append('-z')
2282 cmd.extend(args)
2283 p = GitCommand(self._project,
2284 cmd,
David James8d201162013-10-11 17:03:19 -07002285 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002286 bare = False,
2287 capture_stdout = True,
2288 capture_stderr = True)
2289 try:
2290 out = p.process.stdout.read()
2291 r = {}
2292 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002293 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002294 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002295 try:
2296 info = out.next()
2297 path = out.next()
2298 except StopIteration:
2299 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002300
2301 class _Info(object):
2302 def __init__(self, path, omode, nmode, oid, nid, state):
2303 self.path = path
2304 self.src_path = None
2305 self.old_mode = omode
2306 self.new_mode = nmode
2307 self.old_id = oid
2308 self.new_id = nid
2309
2310 if len(state) == 1:
2311 self.status = state
2312 self.level = None
2313 else:
2314 self.status = state[:1]
2315 self.level = state[1:]
2316 while self.level.startswith('0'):
2317 self.level = self.level[1:]
2318
2319 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002320 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002321 if info.status in ('R', 'C'):
2322 info.src_path = info.path
2323 info.path = out.next()
2324 r[info.path] = info
2325 return r
2326 finally:
2327 p.Wait()
2328
2329 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002330 if self._bare:
2331 path = os.path.join(self._project.gitdir, HEAD)
2332 else:
2333 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002334 try:
2335 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002336 except IOError as e:
2337 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002338 try:
2339 line = fd.read()
2340 finally:
2341 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302342 try:
2343 line = line.decode()
2344 except AttributeError:
2345 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002346 if line.startswith('ref: '):
2347 return line[5:-1]
2348 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002349
2350 def SetHead(self, ref, message=None):
2351 cmdv = []
2352 if message is not None:
2353 cmdv.extend(['-m', message])
2354 cmdv.append(HEAD)
2355 cmdv.append(ref)
2356 self.symbolic_ref(*cmdv)
2357
2358 def DetachHead(self, new, message=None):
2359 cmdv = ['--no-deref']
2360 if message is not None:
2361 cmdv.extend(['-m', message])
2362 cmdv.append(HEAD)
2363 cmdv.append(new)
2364 self.update_ref(*cmdv)
2365
2366 def UpdateRef(self, name, new, old=None,
2367 message=None,
2368 detach=False):
2369 cmdv = []
2370 if message is not None:
2371 cmdv.extend(['-m', message])
2372 if detach:
2373 cmdv.append('--no-deref')
2374 cmdv.append(name)
2375 cmdv.append(new)
2376 if old is not None:
2377 cmdv.append(old)
2378 self.update_ref(*cmdv)
2379
2380 def DeleteRef(self, name, old=None):
2381 if not old:
2382 old = self.rev_parse(name)
2383 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002384 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002385
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002386 def rev_list(self, *args, **kw):
2387 if 'format' in kw:
2388 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2389 else:
2390 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002391 cmdv.extend(args)
2392 p = GitCommand(self._project,
2393 cmdv,
2394 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002395 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002396 capture_stdout = True,
2397 capture_stderr = True)
2398 r = []
2399 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002400 if line[-1] == '\n':
2401 line = line[:-1]
2402 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002403 if p.Wait() != 0:
2404 raise GitError('%s rev-list %s: %s' % (
2405 self._project.name,
2406 str(args),
2407 p.stderr))
2408 return r
2409
2410 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002411 """Allow arbitrary git commands using pythonic syntax.
2412
2413 This allows you to do things like:
2414 git_obj.rev_parse('HEAD')
2415
2416 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2417 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002418 Any other positional arguments will be passed to the git command, and the
2419 following keyword arguments are supported:
2420 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002421
2422 Args:
2423 name: The name of the git command to call. Any '_' characters will
2424 be replaced with '-'.
2425
2426 Returns:
2427 A callable object that will try to call git with the named command.
2428 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002429 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002430 def runner(*args, **kwargs):
2431 cmdv = []
2432 config = kwargs.pop('config', None)
2433 for k in kwargs:
2434 raise TypeError('%s() got an unexpected keyword argument %r'
2435 % (name, k))
2436 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002437 if not git_require((1, 7, 2)):
2438 raise ValueError('cannot set config on command line for %s()'
2439 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302440 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002441 cmdv.append('-c')
2442 cmdv.append('%s=%s' % (k, v))
2443 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002444 cmdv.extend(args)
2445 p = GitCommand(self._project,
2446 cmdv,
2447 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002448 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002449 capture_stdout = True,
2450 capture_stderr = True)
2451 if p.Wait() != 0:
2452 raise GitError('%s %s: %s' % (
2453 self._project.name,
2454 name,
2455 p.stderr))
2456 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302457 try:
Conley Owensedd01512013-09-26 12:59:58 -07002458 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302459 except AttributeError:
2460 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002461 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2462 return r[:-1]
2463 return r
2464 return runner
2465
2466
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002467class _PriorSyncFailedError(Exception):
2468 def __str__(self):
2469 return 'prior sync failed; rebase still in progress'
2470
2471class _DirtyError(Exception):
2472 def __str__(self):
2473 return 'contains uncommitted changes'
2474
2475class _InfoMessage(object):
2476 def __init__(self, project, text):
2477 self.project = project
2478 self.text = text
2479
2480 def Print(self, syncbuf):
2481 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2482 syncbuf.out.nl()
2483
2484class _Failure(object):
2485 def __init__(self, project, why):
2486 self.project = project
2487 self.why = why
2488
2489 def Print(self, syncbuf):
2490 syncbuf.out.fail('error: %s/: %s',
2491 self.project.relpath,
2492 str(self.why))
2493 syncbuf.out.nl()
2494
2495class _Later(object):
2496 def __init__(self, project, action):
2497 self.project = project
2498 self.action = action
2499
2500 def Run(self, syncbuf):
2501 out = syncbuf.out
2502 out.project('project %s/', self.project.relpath)
2503 out.nl()
2504 try:
2505 self.action()
2506 out.nl()
2507 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002508 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002509 out.nl()
2510 return False
2511
2512class _SyncColoring(Coloring):
2513 def __init__(self, config):
2514 Coloring.__init__(self, config, 'reposync')
2515 self.project = self.printer('header', attr = 'bold')
2516 self.info = self.printer('info')
2517 self.fail = self.printer('fail', fg='red')
2518
2519class SyncBuffer(object):
2520 def __init__(self, config, detach_head=False):
2521 self._messages = []
2522 self._failures = []
2523 self._later_queue1 = []
2524 self._later_queue2 = []
2525
2526 self.out = _SyncColoring(config)
2527 self.out.redirect(sys.stderr)
2528
2529 self.detach_head = detach_head
2530 self.clean = True
2531
2532 def info(self, project, fmt, *args):
2533 self._messages.append(_InfoMessage(project, fmt % args))
2534
2535 def fail(self, project, err=None):
2536 self._failures.append(_Failure(project, err))
2537 self.clean = False
2538
2539 def later1(self, project, what):
2540 self._later_queue1.append(_Later(project, what))
2541
2542 def later2(self, project, what):
2543 self._later_queue2.append(_Later(project, what))
2544
2545 def Finish(self):
2546 self._PrintMessages()
2547 self._RunLater()
2548 self._PrintMessages()
2549 return self.clean
2550
2551 def _RunLater(self):
2552 for q in ['_later_queue1', '_later_queue2']:
2553 if not self._RunQueue(q):
2554 return
2555
2556 def _RunQueue(self, queue):
2557 for m in getattr(self, queue):
2558 if not m.Run(self):
2559 self.clean = False
2560 return False
2561 setattr(self, queue, [])
2562 return True
2563
2564 def _PrintMessages(self):
2565 for m in self._messages:
2566 m.Print(self)
2567 for m in self._failures:
2568 m.Print(self)
2569
2570 self._messages = []
2571 self._failures = []
2572
2573
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002574class MetaProject(Project):
2575 """A special project housed under .repo.
2576 """
2577 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002578 Project.__init__(self,
2579 manifest = manifest,
2580 name = name,
2581 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002582 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002583 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002584 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002585 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002586 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002587 revisionId = None,
2588 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002589
2590 def PreSync(self):
2591 if self.Exists:
2592 cb = self.CurrentBranch
2593 if cb:
2594 base = self.GetBranch(cb).merge
2595 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002596 self.revisionExpr = base
2597 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002598
Florian Vallee5d016502012-06-07 17:19:26 +02002599 def MetaBranchSwitch(self, target):
2600 """ Prepare MetaProject for manifest branch switch
2601 """
2602
2603 # detach and delete manifest branch, allowing a new
2604 # branch to take over
2605 syncbuf = SyncBuffer(self.config, detach_head = True)
2606 self.Sync_LocalHalf(syncbuf)
2607 syncbuf.Finish()
2608
2609 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002610 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002611 capture_stdout = True,
2612 capture_stderr = True).Wait() == 0
2613
2614
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002615 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002616 def LastFetch(self):
2617 try:
2618 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2619 return os.path.getmtime(fh)
2620 except OSError:
2621 return 0
2622
2623 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002624 def HasChanges(self):
2625 """Has the remote received new commits not yet checked out?
2626 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002627 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002628 return False
2629
David Pursehouse8a68ff92012-09-24 12:15:13 +09002630 all_refs = self.bare_ref.all
2631 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002632 head = self.work_git.GetHead()
2633 if head.startswith(R_HEADS):
2634 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002635 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002636 except KeyError:
2637 head = None
2638
2639 if revid == head:
2640 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002641 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002642 return True
2643 return False