blob: c2bedde6910593a8473e194203368a629c8728e9 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sarah Owenscecd1d82012-11-01 22:59:27 -070015from __future__ import print_function
Doug Anderson37282b42011-03-04 11:54:18 -080016import traceback
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080017import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import filecmp
19import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070029
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070031from git_command import GitCommand, git_require
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -070032from git_config import GitConfig, IsId, GetSchemeFromUrl, ID_RE
David Pursehousee15c65a2012-08-22 10:46:11 +090033from error import GitError, HookError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080034from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080035from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070036from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037
Shawn O. Pearced237b692009-04-17 18:49:50 -070038from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
David Pursehouse59bbb582013-05-17 10:49:33 +090040from pyversion import is_python3
41if not is_python3():
42 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053043 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090044 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053045
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070046def _lwrite(path, content):
47 lock = '%s.lock' % path
48
49 fd = open(lock, 'wb')
50 try:
51 fd.write(content)
52 finally:
53 fd.close()
54
55 try:
56 os.rename(lock, path)
57 except OSError:
58 os.remove(lock)
59 raise
60
Shawn O. Pearce48244782009-04-16 08:25:57 -070061def _error(fmt, *args):
62 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070063 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070064
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070065def not_rev(r):
66 return '^' + r
67
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080068def sq(r):
69 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080070
Doug Anderson8ced8642011-01-10 14:16:30 -080071_project_hook_list = None
72def _ProjectHooks():
73 """List the hooks present in the 'hooks' directory.
74
75 These hooks are project hooks and are copied to the '.git/hooks' directory
76 of all subprojects.
77
78 This function caches the list of hooks (based on the contents of the
79 'repo/hooks' directory) on the first call.
80
81 Returns:
82 A list of absolute paths to all of the files in the hooks directory.
83 """
84 global _project_hook_list
85 if _project_hook_list is None:
Jesse Hall672cc492013-11-27 11:17:13 -080086 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087 d = os.path.join(d , 'hooks')
Chirayu Desai217ea7d2013-03-01 19:14:38 +053088 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
Doug Anderson8ced8642011-01-10 14:16:30 -080089 return _project_hook_list
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080090
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080091
Shawn O. Pearce632768b2008-10-23 11:58:52 -070092class DownloadedChange(object):
93 _commit_cache = None
94
95 def __init__(self, project, base, change_id, ps_id, commit):
96 self.project = project
97 self.base = base
98 self.change_id = change_id
99 self.ps_id = ps_id
100 self.commit = commit
101
102 @property
103 def commits(self):
104 if self._commit_cache is None:
105 self._commit_cache = self.project.bare_git.rev_list(
106 '--abbrev=8',
107 '--abbrev-commit',
108 '--pretty=oneline',
109 '--reverse',
110 '--date-order',
111 not_rev(self.base),
112 self.commit,
113 '--')
114 return self._commit_cache
115
116
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700117class ReviewableBranch(object):
118 _commit_cache = None
119
120 def __init__(self, project, branch, base):
121 self.project = project
122 self.branch = branch
123 self.base = base
124
125 @property
126 def name(self):
127 return self.branch.name
128
129 @property
130 def commits(self):
131 if self._commit_cache is None:
132 self._commit_cache = self.project.bare_git.rev_list(
133 '--abbrev=8',
134 '--abbrev-commit',
135 '--pretty=oneline',
136 '--reverse',
137 '--date-order',
138 not_rev(self.base),
139 R_HEADS + self.name,
140 '--')
141 return self._commit_cache
142
143 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800144 def unabbrev_commits(self):
145 r = dict()
146 for commit in self.project.bare_git.rev_list(
147 not_rev(self.base),
148 R_HEADS + self.name,
149 '--'):
150 r[commit[0:8]] = commit
151 return r
152
153 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154 def date(self):
155 return self.project.bare_git.log(
156 '--pretty=format:%cd',
157 '-n', '1',
158 R_HEADS + self.name,
159 '--')
160
Bryan Jacobsf609f912013-05-06 13:36:24 -0400161 def UploadForReview(self, people, auto_topic=False, draft=False, dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700163 people,
Brian Harring435370c2012-07-28 15:37:04 -0700164 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400165 draft=draft,
166 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700168 def GetPublishedRefs(self):
169 refs = {}
170 output = self.project.bare_git.ls_remote(
171 self.branch.remote.SshReviewUrl(self.project.UserEmail),
172 'refs/changes/*')
173 for line in output.split('\n'):
174 try:
175 (sha, ref) = line.split()
176 refs[sha] = ref
177 except ValueError:
178 pass
179
180 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
182class StatusColoring(Coloring):
183 def __init__(self, config):
184 Coloring.__init__(self, config, 'status')
185 self.project = self.printer('header', attr = 'bold')
186 self.branch = self.printer('header', attr = 'bold')
187 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700188 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700189
190 self.added = self.printer('added', fg = 'green')
191 self.changed = self.printer('changed', fg = 'red')
192 self.untracked = self.printer('untracked', fg = 'red')
193
194
195class DiffColoring(Coloring):
196 def __init__(self, config):
197 Coloring.__init__(self, config, 'diff')
198 self.project = self.printer('header', attr = 'bold')
199
James W. Mills24c13082012-04-12 15:04:13 -0500200class _Annotation:
201 def __init__(self, name, value, keep):
202 self.name = name
203 self.value = value
204 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205
206class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800207 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 self.src = src
209 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800210 self.abs_src = abssrc
211 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700212
213 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800214 src = self.abs_src
215 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 # copy file if it does not exist or is out of date
217 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
218 try:
219 # remove existing file first, since it might be read-only
220 if os.path.exists(dest):
221 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400222 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200223 dest_dir = os.path.dirname(dest)
224 if not os.path.isdir(dest_dir):
225 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226 shutil.copy(src, dest)
227 # make the file read-only
228 mode = os.stat(dest)[stat.ST_MODE]
229 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
230 os.chmod(dest, mode)
231 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700232 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500234class _LinkFile:
235 def __init__(self, src, dest, abssrc, absdest):
236 self.src = src
237 self.dest = dest
238 self.abs_src = abssrc
239 self.abs_dest = absdest
240
241 def _Link(self):
242 src = self.abs_src
243 dest = self.abs_dest
244 # link file if it does not exist or is out of date
245 if not os.path.islink(dest) or os.readlink(dest) != src:
246 try:
247 # remove existing file first, since it might be read-only
248 if os.path.exists(dest):
249 os.remove(dest)
250 else:
251 dest_dir = os.path.dirname(dest)
252 if not os.path.isdir(dest_dir):
253 os.makedirs(dest_dir)
254 os.symlink(src, dest)
255 except IOError:
256 _error('Cannot link file %s to %s', src, dest)
257
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700258class RemoteSpec(object):
259 def __init__(self,
260 name,
261 url = None,
262 review = None):
263 self.name = name
264 self.url = url
265 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700266
Doug Anderson37282b42011-03-04 11:54:18 -0800267class RepoHook(object):
268 """A RepoHook contains information about a script to run as a hook.
269
270 Hooks are used to run a python script before running an upload (for instance,
271 to run presubmit checks). Eventually, we may have hooks for other actions.
272
273 This shouldn't be confused with files in the 'repo/hooks' directory. Those
274 files are copied into each '.git/hooks' folder for each project. Repo-level
275 hooks are associated instead with repo actions.
276
277 Hooks are always python. When a hook is run, we will load the hook into the
278 interpreter and execute its main() function.
279 """
280 def __init__(self,
281 hook_type,
282 hooks_project,
283 topdir,
284 abort_if_user_denies=False):
285 """RepoHook constructor.
286
287 Params:
288 hook_type: A string representing the type of hook. This is also used
289 to figure out the name of the file containing the hook. For
290 example: 'pre-upload'.
291 hooks_project: The project containing the repo hooks. If you have a
292 manifest, this is manifest.repo_hooks_project. OK if this is None,
293 which will make the hook a no-op.
294 topdir: Repo's top directory (the one containing the .repo directory).
295 Scripts will run with CWD as this directory. If you have a manifest,
296 this is manifest.topdir
297 abort_if_user_denies: If True, we'll throw a HookError() if the user
298 doesn't allow us to run the hook.
299 """
300 self._hook_type = hook_type
301 self._hooks_project = hooks_project
302 self._topdir = topdir
303 self._abort_if_user_denies = abort_if_user_denies
304
305 # Store the full path to the script for convenience.
306 if self._hooks_project:
307 self._script_fullpath = os.path.join(self._hooks_project.worktree,
308 self._hook_type + '.py')
309 else:
310 self._script_fullpath = None
311
312 def _GetHash(self):
313 """Return a hash of the contents of the hooks directory.
314
315 We'll just use git to do this. This hash has the property that if anything
316 changes in the directory we will return a different has.
317
318 SECURITY CONSIDERATION:
319 This hash only represents the contents of files in the hook directory, not
320 any other files imported or called by hooks. Changes to imported files
321 can change the script behavior without affecting the hash.
322
323 Returns:
324 A string representing the hash. This will always be ASCII so that it can
325 be printed to the user easily.
326 """
327 assert self._hooks_project, "Must have hooks to calculate their hash."
328
329 # We will use the work_git object rather than just calling GetRevisionId().
330 # That gives us a hash of the latest checked in version of the files that
331 # the user will actually be executing. Specifically, GetRevisionId()
332 # doesn't appear to change even if a user checks out a different version
333 # of the hooks repo (via git checkout) nor if a user commits their own revs.
334 #
335 # NOTE: Local (non-committed) changes will not be factored into this hash.
336 # I think this is OK, since we're really only worried about warning the user
337 # about upstream changes.
338 return self._hooks_project.work_git.rev_parse('HEAD')
339
340 def _GetMustVerb(self):
341 """Return 'must' if the hook is required; 'should' if not."""
342 if self._abort_if_user_denies:
343 return 'must'
344 else:
345 return 'should'
346
347 def _CheckForHookApproval(self):
348 """Check to see whether this hook has been approved.
349
350 We'll look at the hash of all of the hooks. If this matches the hash that
351 the user last approved, we're done. If it doesn't, we'll ask the user
352 about approval.
353
354 Note that we ask permission for each individual hook even though we use
355 the hash of all hooks when detecting changes. We'd like the user to be
356 able to approve / deny each hook individually. We only use the hash of all
357 hooks because there is no other easy way to detect changes to local imports.
358
359 Returns:
360 True if this hook is approved to run; False otherwise.
361
362 Raises:
363 HookError: Raised if the user doesn't approve and abort_if_user_denies
364 was passed to the consturctor.
365 """
Doug Anderson37282b42011-03-04 11:54:18 -0800366 hooks_config = self._hooks_project.config
367 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
368
369 # Get the last hash that the user approved for this hook; may be None.
370 old_hash = hooks_config.GetString(git_approval_key)
371
372 # Get the current hash so we can tell if scripts changed since approval.
373 new_hash = self._GetHash()
374
375 if old_hash is not None:
376 # User previously approved hook and asked not to be prompted again.
377 if new_hash == old_hash:
378 # Approval matched. We're done.
379 return True
380 else:
381 # Give the user a reason why we're prompting, since they last told
382 # us to "never ask again".
383 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
384 self._hook_type)
385 else:
386 prompt = ''
387
388 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
389 if sys.stdout.isatty():
390 prompt += ('Repo %s run the script:\n'
391 ' %s\n'
392 '\n'
393 'Do you want to allow this script to run '
394 '(yes/yes-never-ask-again/NO)? ') % (
395 self._GetMustVerb(), self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530396 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900397 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800398
399 # User is doing a one-time approval.
400 if response in ('y', 'yes'):
401 return True
402 elif response == 'yes-never-ask-again':
403 hooks_config.SetString(git_approval_key, new_hash)
404 return True
405
406 # For anything else, we'll assume no approval.
407 if self._abort_if_user_denies:
408 raise HookError('You must allow the %s hook or use --no-verify.' %
409 self._hook_type)
410
411 return False
412
413 def _ExecuteHook(self, **kwargs):
414 """Actually execute the given hook.
415
416 This will run the hook's 'main' function in our python interpreter.
417
418 Args:
419 kwargs: Keyword arguments to pass to the hook. These are often specific
420 to the hook type. For instance, pre-upload hooks will contain
421 a project_list.
422 """
423 # Keep sys.path and CWD stashed away so that we can always restore them
424 # upon function exit.
425 orig_path = os.getcwd()
426 orig_syspath = sys.path
427
428 try:
429 # Always run hooks with CWD as topdir.
430 os.chdir(self._topdir)
431
432 # Put the hook dir as the first item of sys.path so hooks can do
433 # relative imports. We want to replace the repo dir as [0] so
434 # hooks can't import repo files.
435 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
436
437 # Exec, storing global context in the context dict. We catch exceptions
438 # and convert to a HookError w/ just the failing traceback.
439 context = {}
440 try:
441 execfile(self._script_fullpath, context)
442 except Exception:
443 raise HookError('%s\nFailed to import %s hook; see traceback above.' % (
444 traceback.format_exc(), self._hook_type))
445
446 # Running the script should have defined a main() function.
447 if 'main' not in context:
448 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
449
450
451 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
452 # We don't actually want hooks to define their main with this argument--
453 # it's there to remind them that their hook should always take **kwargs.
454 # For instance, a pre-upload hook should be defined like:
455 # def main(project_list, **kwargs):
456 #
457 # This allows us to later expand the API without breaking old hooks.
458 kwargs = kwargs.copy()
459 kwargs['hook_should_take_kwargs'] = True
460
461 # Call the main function in the hook. If the hook should cause the
462 # build to fail, it will raise an Exception. We'll catch that convert
463 # to a HookError w/ just the failing traceback.
464 try:
465 context['main'](**kwargs)
466 except Exception:
467 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
468 'above.' % (
469 traceback.format_exc(), self._hook_type))
470 finally:
471 # Restore sys.path and CWD.
472 sys.path = orig_syspath
473 os.chdir(orig_path)
474
475 def Run(self, user_allows_all_hooks, **kwargs):
476 """Run the hook.
477
478 If the hook doesn't exist (because there is no hooks project or because
479 this particular hook is not enabled), this is a no-op.
480
481 Args:
482 user_allows_all_hooks: If True, we will never prompt about running the
483 hook--we'll just assume it's OK to run it.
484 kwargs: Keyword arguments to pass to the hook. These are often specific
485 to the hook type. For instance, pre-upload hooks will contain
486 a project_list.
487
488 Raises:
489 HookError: If there was a problem finding the hook or the user declined
490 to run a required hook (from _CheckForHookApproval).
491 """
492 # No-op if there is no hooks project or if hook is disabled.
493 if ((not self._hooks_project) or
494 (self._hook_type not in self._hooks_project.enabled_repo_hooks)):
495 return
496
497 # Bail with a nice error if we can't find the hook.
498 if not os.path.isfile(self._script_fullpath):
499 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
500
501 # Make sure the user is OK with running the hook.
502 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
503 return
504
505 # Run the hook with the same version of python we're using.
506 self._ExecuteHook(**kwargs)
507
508
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700509class Project(object):
510 def __init__(self,
511 manifest,
512 name,
513 remote,
514 gitdir,
David James8d201162013-10-11 17:03:19 -0700515 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700516 worktree,
517 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700518 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800519 revisionId,
Colin Cross5acde752012-03-28 20:15:45 -0700520 rebase = True,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700521 groups = None,
Brian Harring14a66742012-09-28 20:21:57 -0700522 sync_c = False,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800523 sync_s = False,
David Pursehouseede7f122012-11-27 22:25:30 +0900524 clone_depth = None,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800525 upstream = None,
526 parent = None,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400527 is_derived = False,
528 dest_branch = None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800529 """Init a Project object.
530
531 Args:
532 manifest: The XmlManifest object.
533 name: The `name` attribute of manifest.xml's project element.
534 remote: RemoteSpec object specifying its remote's properties.
535 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700536 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800537 worktree: Absolute path of git working tree.
538 relpath: Relative path of git working tree to repo's top directory.
539 revisionExpr: The `revision` attribute of manifest.xml's project element.
540 revisionId: git commit id for checking out.
541 rebase: The `rebase` attribute of manifest.xml's project element.
542 groups: The `groups` attribute of manifest.xml's project element.
543 sync_c: The `sync-c` attribute of manifest.xml's project element.
544 sync_s: The `sync-s` attribute of manifest.xml's project element.
545 upstream: The `upstream` attribute of manifest.xml's project element.
546 parent: The parent Project object.
547 is_derived: False if the project was explicitly defined in the manifest;
548 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400549 dest_branch: The branch to which to push changes for review by default.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800550 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700551 self.manifest = manifest
552 self.name = name
553 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800554 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700555 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800556 if worktree:
557 self.worktree = worktree.replace('\\', '/')
558 else:
559 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700560 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700561 self.revisionExpr = revisionExpr
562
563 if revisionId is None \
564 and revisionExpr \
565 and IsId(revisionExpr):
566 self.revisionId = revisionExpr
567 else:
568 self.revisionId = revisionId
569
Mike Pontillod3153822012-02-28 11:53:24 -0800570 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700571 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700572 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800573 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900574 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700575 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800576 self.parent = parent
577 self.is_derived = is_derived
578 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800579
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700580 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700581 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500582 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500583 self.annotations = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700584 self.config = GitConfig.ForRepository(
585 gitdir = self.gitdir,
586 defaults = self.manifest.globalConfig)
587
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800588 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700589 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800590 else:
591 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700592 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700593 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700594 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400595 self.dest_branch = dest_branch
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700596
Doug Anderson37282b42011-03-04 11:54:18 -0800597 # This will be filled in if a project is later identified to be the
598 # project containing repo hooks.
599 self.enabled_repo_hooks = []
600
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700601 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800602 def Derived(self):
603 return self.is_derived
604
605 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700606 def Exists(self):
607 return os.path.isdir(self.gitdir)
608
609 @property
610 def CurrentBranch(self):
611 """Obtain the name of the currently checked out branch.
612 The branch name omits the 'refs/heads/' prefix.
613 None is returned if the project is on a detached HEAD.
614 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700615 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700616 if b.startswith(R_HEADS):
617 return b[len(R_HEADS):]
618 return None
619
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700620 def IsRebaseInProgress(self):
621 w = self.worktree
622 g = os.path.join(w, '.git')
623 return os.path.exists(os.path.join(g, 'rebase-apply')) \
624 or os.path.exists(os.path.join(g, 'rebase-merge')) \
625 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200626
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700627 def IsDirty(self, consider_untracked=True):
628 """Is the working directory modified in some way?
629 """
630 self.work_git.update_index('-q',
631 '--unmerged',
632 '--ignore-missing',
633 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900634 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635 return True
636 if self.work_git.DiffZ('diff-files'):
637 return True
638 if consider_untracked and self.work_git.LsOthers():
639 return True
640 return False
641
642 _userident_name = None
643 _userident_email = None
644
645 @property
646 def UserName(self):
647 """Obtain the user's personal name.
648 """
649 if self._userident_name is None:
650 self._LoadUserIdentity()
651 return self._userident_name
652
653 @property
654 def UserEmail(self):
655 """Obtain the user's email address. This is very likely
656 to be their Gerrit login.
657 """
658 if self._userident_email is None:
659 self._LoadUserIdentity()
660 return self._userident_email
661
662 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900663 u = self.bare_git.var('GIT_COMMITTER_IDENT')
664 m = re.compile("^(.*) <([^>]*)> ").match(u)
665 if m:
666 self._userident_name = m.group(1)
667 self._userident_email = m.group(2)
668 else:
669 self._userident_name = ''
670 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700671
672 def GetRemote(self, name):
673 """Get the configuration for a single remote.
674 """
675 return self.config.GetRemote(name)
676
677 def GetBranch(self, name):
678 """Get the configuration for a single branch.
679 """
680 return self.config.GetBranch(name)
681
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700682 def GetBranches(self):
683 """Get all existing local branches.
684 """
685 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900686 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700687 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700688
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530689 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700690 if name.startswith(R_HEADS):
691 name = name[len(R_HEADS):]
692 b = self.GetBranch(name)
693 b.current = name == current
694 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900695 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700696 heads[name] = b
697
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530698 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700699 if name.startswith(R_PUB):
700 name = name[len(R_PUB):]
701 b = heads.get(name)
702 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900703 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700704
705 return heads
706
Colin Cross5acde752012-03-28 20:15:45 -0700707 def MatchesGroups(self, manifest_groups):
708 """Returns true if the manifest groups specified at init should cause
709 this project to be synced.
710 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700711 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700712
713 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700714 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700715 manifest_groups: "-group1,group2"
716 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500717
718 The special manifest group "default" will match any project that
719 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700720 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500721 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700722 expanded_project_groups = ['all'] + (self.groups or [])
David Holmer0a1c6a12012-11-14 19:19:00 -0500723 if not 'notdefault' in expanded_project_groups:
724 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700725
Conley Owens971de8e2012-04-16 10:36:08 -0700726 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700727 for group in expanded_manifest_groups:
728 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700729 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700730 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700731 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700732
Conley Owens971de8e2012-04-16 10:36:08 -0700733 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700734
735## Status Display ##
736
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500737 def HasChanges(self):
738 """Returns true if there are uncommitted changes.
739 """
740 self.work_git.update_index('-q',
741 '--unmerged',
742 '--ignore-missing',
743 '--refresh')
744 if self.IsRebaseInProgress():
745 return True
746
747 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
748 return True
749
750 if self.work_git.DiffZ('diff-files'):
751 return True
752
753 if self.work_git.LsOthers():
754 return True
755
756 return False
757
Terence Haddock4655e812011-03-31 12:33:34 +0200758 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200760
761 Args:
762 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700763 """
764 if not os.path.isdir(self.worktree):
Terence Haddock4655e812011-03-31 12:33:34 +0200765 if output_redir == None:
766 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700767 print(file=output_redir)
768 print('project %s/' % self.relpath, file=output_redir)
769 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770 return
771
772 self.work_git.update_index('-q',
773 '--unmerged',
774 '--ignore-missing',
775 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700776 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700777 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
778 df = self.work_git.DiffZ('diff-files')
779 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100780 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700781 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782
783 out = StatusColoring(self.config)
Terence Haddock4655e812011-03-31 12:33:34 +0200784 if not output_redir == None:
785 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700786 out.project('project %-40s', self.relpath + '/')
787
788 branch = self.CurrentBranch
789 if branch is None:
790 out.nobranch('(*** NO BRANCH ***)')
791 else:
792 out.branch('branch %s', branch)
793 out.nl()
794
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700795 if rb:
796 out.important('prior sync failed; rebase still in progress')
797 out.nl()
798
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700799 paths = list()
800 paths.extend(di.keys())
801 paths.extend(df.keys())
802 paths.extend(do)
803
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530804 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900805 try:
806 i = di[p]
807 except KeyError:
808 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900810 try:
811 f = df[p]
812 except KeyError:
813 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200814
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900815 if i:
816 i_status = i.status.upper()
817 else:
818 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700819
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900820 if f:
821 f_status = f.status.lower()
822 else:
823 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824
825 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800826 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700827 i.src_path, p, i.level)
828 else:
829 line = ' %s%s\t%s' % (i_status, f_status, p)
830
831 if i and not f:
832 out.added('%s', line)
833 elif (i and f) or (not i and f):
834 out.changed('%s', line)
835 elif not i and not f:
836 out.untracked('%s', line)
837 else:
838 out.write('%s', line)
839 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200840
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700841 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700842
pelyad67872d2012-03-28 14:49:58 +0300843 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844 """Prints the status of the repository to stdout.
845 """
846 out = DiffColoring(self.config)
847 cmd = ['diff']
848 if out.is_on:
849 cmd.append('--color')
850 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300851 if absolute_paths:
852 cmd.append('--src-prefix=a/%s/' % self.relpath)
853 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854 cmd.append('--')
855 p = GitCommand(self,
856 cmd,
857 capture_stdout = True,
858 capture_stderr = True)
859 has_diff = False
860 for line in p.process.stdout:
861 if not has_diff:
862 out.nl()
863 out.project('project %s/' % self.relpath)
864 out.nl()
865 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700866 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867 p.Wait()
868
869
870## Publish / Upload ##
871
David Pursehouse8a68ff92012-09-24 12:15:13 +0900872 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700873 """Was the branch published (uploaded) for code review?
874 If so, returns the SHA-1 hash of the last published
875 state for the branch.
876 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700877 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900878 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700879 try:
880 return self.bare_git.rev_parse(key)
881 except GitError:
882 return None
883 else:
884 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900885 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700886 except KeyError:
887 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888
David Pursehouse8a68ff92012-09-24 12:15:13 +0900889 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890 """Prunes any stale published refs.
891 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900892 if all_refs is None:
893 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894 heads = set()
895 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530896 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700897 if name.startswith(R_HEADS):
898 heads.add(name)
899 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900900 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530902 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700903 n = name[len(R_PUB):]
904 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900905 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700907 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700908 """List any branches which can be uploaded for review.
909 """
910 heads = {}
911 pubed = {}
912
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530913 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900915 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900917 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918
919 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530920 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900921 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700923 if selected_branch and branch != selected_branch:
924 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800926 rb = self.GetUploadableBranch(branch)
927 if rb:
928 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929 return ready
930
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800931 def GetUploadableBranch(self, branch_name):
932 """Get a single uploadable branch, or None.
933 """
934 branch = self.GetBranch(branch_name)
935 base = branch.LocalMerge
936 if branch.LocalMerge:
937 rb = ReviewableBranch(self, branch, base)
938 if rb.commits:
939 return rb
940 return None
941
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700942 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700943 people=([],[]),
Brian Harring435370c2012-07-28 15:37:04 -0700944 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400945 draft=False,
946 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947 """Uploads the named branch for code review.
948 """
949 if branch is None:
950 branch = self.CurrentBranch
951 if branch is None:
952 raise GitError('not currently on a branch')
953
954 branch = self.GetBranch(branch)
955 if not branch.LocalMerge:
956 raise GitError('branch %s does not track a remote' % branch.name)
957 if not branch.remote.review:
958 raise GitError('remote %s has no review url' % branch.remote.name)
959
Bryan Jacobsf609f912013-05-06 13:36:24 -0400960 if dest_branch is None:
961 dest_branch = self.dest_branch
962 if dest_branch is None:
963 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964 if not dest_branch.startswith(R_HEADS):
965 dest_branch = R_HEADS + dest_branch
966
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800967 if not branch.remote.projectname:
968 branch.remote.projectname = self.name
969 branch.remote.Save()
970
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800971 url = branch.remote.ReviewUrl(self.UserEmail)
972 if url is None:
973 raise UploadError('review not configured')
974 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800975
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800976 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800977 rp = ['gerrit receive-pack']
978 for e in people[0]:
979 rp.append('--reviewer=%s' % sq(e))
980 for e in people[1]:
981 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800982 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700983
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800984 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800985
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800986 if dest_branch.startswith(R_HEADS):
987 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700988
989 upload_type = 'for'
990 if draft:
991 upload_type = 'drafts'
992
993 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
994 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800995 if auto_topic:
996 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -0800997 if not url.startswith('ssh://'):
998 rp = ['r=%s' % p for p in people[0]] + \
999 ['cc=%s' % p for p in people[1]]
1000 if rp:
1001 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001002 cmd.append(ref_spec)
1003
1004 if GitCommand(self, cmd, bare = True).Wait() != 0:
1005 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001006
1007 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1008 self.bare_git.UpdateRef(R_PUB + branch.name,
1009 R_HEADS + branch.name,
1010 message = msg)
1011
1012
1013## Sync ##
1014
Julien Campergue335f5ef2013-10-16 11:02:35 +02001015 def _ExtractArchive(self, tarpath, path=None):
1016 """Extract the given tar on its current location
1017
1018 Args:
1019 - tarpath: The path to the actual tar file
1020
1021 """
1022 try:
1023 with tarfile.open(tarpath, 'r') as tar:
1024 tar.extractall(path=path)
1025 return True
1026 except (IOError, tarfile.TarError) as e:
1027 print("error: Cannot extract archive %s: "
1028 "%s" % (tarpath, str(e)), file=sys.stderr)
1029 return False
1030
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001031 def Sync_NetworkHalf(self,
1032 quiet=False,
1033 is_new=None,
1034 current_branch_only=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001035 clone_bundle=True,
Julien Campergue335f5ef2013-10-16 11:02:35 +02001036 no_tags=False,
1037 archive=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038 """Perform only the network IO portion of the sync process.
1039 Local working directory/branch state is not affected.
1040 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001041 if archive and not isinstance(self, MetaProject):
1042 if self.remote.url.startswith(('http://', 'https://')):
1043 print("error: %s: Cannot fetch archives from http/https "
1044 "remotes." % self.name, file=sys.stderr)
1045 return False
1046
1047 name = self.relpath.replace('\\', '/')
1048 name = name.replace('/', '_')
1049 tarpath = '%s.tar' % name
1050 topdir = self.manifest.topdir
1051
1052 try:
1053 self._FetchArchive(tarpath, cwd=topdir)
1054 except GitError as e:
1055 print('error: %s' % str(e), file=sys.stderr)
1056 return False
1057
1058 # From now on, we only need absolute tarpath
1059 tarpath = os.path.join(topdir, tarpath)
1060
1061 if not self._ExtractArchive(tarpath, path=topdir):
1062 return False
1063 try:
1064 os.remove(tarpath)
1065 except OSError as e:
1066 print("warn: Cannot remove archive %s: "
1067 "%s" % (tarpath, str(e)), file=sys.stderr)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001068 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001069 return True
1070
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001071 if is_new is None:
1072 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001073 if is_new:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074 self._InitGitDir()
Jimmie Westera0444582012-10-24 13:44:42 +02001075 else:
1076 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001078
1079 if is_new:
1080 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1081 try:
1082 fd = open(alt, 'rb')
1083 try:
1084 alt_dir = fd.readline().rstrip()
1085 finally:
1086 fd.close()
1087 except IOError:
1088 alt_dir = None
1089 else:
1090 alt_dir = None
1091
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001092 if clone_bundle \
1093 and alt_dir is None \
1094 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001095 is_new = False
1096
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001097 if not current_branch_only:
1098 if self.sync_c:
1099 current_branch_only = True
1100 elif not self.manifest._loaded:
1101 # Manifest cannot check defaults until it syncs.
1102 current_branch_only = False
1103 elif self.manifest.default.sync_c:
1104 current_branch_only = True
1105
Chris AtLee2fb64662014-01-16 21:32:33 -05001106 is_sha1 = False
1107 if ID_RE.match(self.revisionExpr) is not None:
1108 is_sha1 = True
1109 if is_sha1 and self._CheckForSha1():
1110 # Don't need to fetch since we already have this revision
1111 return True
1112
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001113 if not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001114 current_branch_only=current_branch_only,
1115 no_tags=no_tags):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001116 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001117
1118 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001119 self._InitMRef()
1120 else:
1121 self._InitMirrorHead()
1122 try:
1123 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1124 except OSError:
1125 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001127
1128 def PostRepoUpgrade(self):
1129 self._InitHooks()
1130
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001131 def _CopyAndLinkFiles(self):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001132 for copyfile in self.copyfiles:
1133 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001134 for linkfile in self.linkfiles:
1135 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136
Julien Camperguedd654222014-01-09 16:21:37 +01001137 def GetCommitRevisionId(self):
1138 """Get revisionId of a commit.
1139
1140 Use this method instead of GetRevisionId to get the id of the commit rather
1141 than the id of the current git object (for example, a tag)
1142
1143 """
1144 if not self.revisionExpr.startswith(R_TAGS):
1145 return self.GetRevisionId(self._allrefs)
1146
1147 try:
1148 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1149 except GitError:
1150 raise ManifestInvalidRevisionError(
1151 'revision %s in %s not found' % (self.revisionExpr,
1152 self.name))
1153
David Pursehouse8a68ff92012-09-24 12:15:13 +09001154 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001155 if self.revisionId:
1156 return self.revisionId
1157
1158 rem = self.GetRemote(self.remote.name)
1159 rev = rem.ToLocal(self.revisionExpr)
1160
David Pursehouse8a68ff92012-09-24 12:15:13 +09001161 if all_refs is not None and rev in all_refs:
1162 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001163
1164 try:
1165 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1166 except GitError:
1167 raise ManifestInvalidRevisionError(
1168 'revision %s in %s not found' % (self.revisionExpr,
1169 self.name))
1170
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001171 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172 """Perform only the local IO portion of the sync process.
1173 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001174 """
David James8d201162013-10-11 17:03:19 -07001175 self._InitWorkTree()
David Pursehouse8a68ff92012-09-24 12:15:13 +09001176 all_refs = self.bare_ref.all
1177 self.CleanPublishedCache(all_refs)
1178 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001179
David Pursehouse1d947b32012-10-25 12:23:11 +09001180 def _doff():
1181 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001182 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001183
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001184 head = self.work_git.GetHead()
1185 if head.startswith(R_HEADS):
1186 branch = head[len(R_HEADS):]
1187 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001188 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001189 except KeyError:
1190 head = None
1191 else:
1192 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001194 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195 # Currently on a detached HEAD. The user is assumed to
1196 # not have any local modifications worth worrying about.
1197 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001198 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001199 syncbuf.fail(self, _PriorSyncFailedError())
1200 return
1201
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001202 if head == revid:
1203 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001204 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001205 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001206 if not syncbuf.detach_head:
1207 return
1208 else:
1209 lost = self._revlist(not_rev(revid), HEAD)
1210 if lost:
1211 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001212
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001214 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001215 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001216 syncbuf.fail(self, e)
1217 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001218 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001219 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001220
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001221 if head == revid:
1222 # No changes; don't do anything further.
1223 #
1224 return
1225
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001226 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001228 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001229 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001230 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001231 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001232 syncbuf.info(self,
1233 "leaving %s; does not track upstream",
1234 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001235 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001236 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001237 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001238 syncbuf.fail(self, e)
1239 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001240 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001241 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001243 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001244 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001245 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001246 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247 if not_merged:
1248 if upstream_gain:
1249 # The user has published this branch and some of those
1250 # commits are not yet merged upstream. We do not want
1251 # to rewrite the published commits so we punt.
1252 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001253 syncbuf.fail(self,
1254 "branch %s is published (but not merged) and is now %d commits behind"
1255 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001256 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001257 elif pub == head:
1258 # All published commits are merged, and thus we are a
1259 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001260 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001261 syncbuf.later1(self, _doff)
1262 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001264 # Examine the local commits not in the remote. Find the
1265 # last one attributed to this user, if any.
1266 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001267 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001268 last_mine = None
1269 cnt_mine = 0
1270 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301271 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001272 if committer_email == self.UserEmail:
1273 last_mine = commit_id
1274 cnt_mine += 1
1275
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001276 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001277 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001278
1279 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001280 syncbuf.fail(self, _DirtyError())
1281 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001282
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001283 # If the upstream switched on us, warn the user.
1284 #
1285 if branch.merge != self.revisionExpr:
1286 if branch.merge and self.revisionExpr:
1287 syncbuf.info(self,
1288 'manifest switched %s...%s',
1289 branch.merge,
1290 self.revisionExpr)
1291 elif branch.merge:
1292 syncbuf.info(self,
1293 'manifest no longer tracks %s',
1294 branch.merge)
1295
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001296 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001298 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001300 syncbuf.info(self,
1301 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001302 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001304 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001305 if not ID_RE.match(self.revisionExpr):
1306 # in case of manifest sync the revisionExpr might be a SHA1
1307 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308 branch.Save()
1309
Mike Pontillod3153822012-02-28 11:53:24 -08001310 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001311 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001312 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001313 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001314 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001315 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001317 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001318 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001319 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001320 syncbuf.fail(self, e)
1321 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001323 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001325 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326 # dest should already be an absolute path, but src is project relative
1327 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001328 abssrc = os.path.join(self.worktree, src)
1329 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001331 def AddLinkFile(self, src, dest, absdest):
1332 # dest should already be an absolute path, but src is project relative
1333 # make src an absolute path
1334 abssrc = os.path.join(self.worktree, src)
1335 self.linkfiles.append(_LinkFile(src, dest, abssrc, absdest))
1336
James W. Mills24c13082012-04-12 15:04:13 -05001337 def AddAnnotation(self, name, value, keep):
1338 self.annotations.append(_Annotation(name, value, keep))
1339
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001340 def DownloadPatchSet(self, change_id, patch_id):
1341 """Download a single patch set of a single change to FETCH_HEAD.
1342 """
1343 remote = self.GetRemote(self.remote.name)
1344
1345 cmd = ['fetch', remote.name]
1346 cmd.append('refs/changes/%2.2d/%d/%d' \
1347 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001348 if GitCommand(self, cmd, bare=True).Wait() != 0:
1349 return None
1350 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001351 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001352 change_id,
1353 patch_id,
1354 self.bare_git.rev_parse('FETCH_HEAD'))
1355
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001356
1357## Branch Management ##
1358
1359 def StartBranch(self, name):
1360 """Create a new branch off the manifest's revision.
1361 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001362 head = self.work_git.GetHead()
1363 if head == (R_HEADS + name):
1364 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365
David Pursehouse8a68ff92012-09-24 12:15:13 +09001366 all_refs = self.bare_ref.all
1367 if (R_HEADS + name) in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001368 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001369 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001370 capture_stdout = True,
1371 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001372
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001373 branch = self.GetBranch(name)
1374 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001375 branch.merge = self.revisionExpr
David Pursehouse8a68ff92012-09-24 12:15:13 +09001376 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001377
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001378 if head.startswith(R_HEADS):
1379 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001380 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001381 except KeyError:
1382 head = None
1383
1384 if revid and head and revid == head:
1385 ref = os.path.join(self.gitdir, R_HEADS + name)
1386 try:
1387 os.makedirs(os.path.dirname(ref))
1388 except OSError:
1389 pass
1390 _lwrite(ref, '%s\n' % revid)
1391 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1392 'ref: %s%s\n' % (R_HEADS, name))
1393 branch.Save()
1394 return True
1395
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001396 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001397 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -07001398 capture_stdout = True,
1399 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001400 branch.Save()
1401 return True
1402 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403
Wink Saville02d79452009-04-10 13:01:24 -07001404 def CheckoutBranch(self, name):
1405 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001406
1407 Args:
1408 name: The name of the branch to checkout.
1409
1410 Returns:
1411 True if the checkout succeeded; False if it didn't; None if the branch
1412 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001413 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001414 rev = R_HEADS + name
1415 head = self.work_git.GetHead()
1416 if head == rev:
1417 # Already on the branch
1418 #
1419 return True
Wink Saville02d79452009-04-10 13:01:24 -07001420
David Pursehouse8a68ff92012-09-24 12:15:13 +09001421 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001422 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001423 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001424 except KeyError:
1425 # Branch does not exist in this project
1426 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001427 return None
Wink Saville02d79452009-04-10 13:01:24 -07001428
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001429 if head.startswith(R_HEADS):
1430 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001431 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001432 except KeyError:
1433 head = None
1434
1435 if head == revid:
1436 # Same revision; just update HEAD to point to the new
1437 # target branch, but otherwise take no other action.
1438 #
1439 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1440 'ref: %s%s\n' % (R_HEADS, name))
1441 return True
1442
1443 return GitCommand(self,
1444 ['checkout', name, '--'],
1445 capture_stdout = True,
1446 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001447
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001448 def AbandonBranch(self, name):
1449 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001450
1451 Args:
1452 name: The name of the branch to abandon.
1453
1454 Returns:
1455 True if the abandon succeeded; False if it didn't; None if the branch
1456 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001457 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001458 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001459 all_refs = self.bare_ref.all
1460 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001461 # Doesn't exist
1462 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001463
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001464 head = self.work_git.GetHead()
1465 if head == rev:
1466 # We can't destroy the branch while we are sitting
1467 # on it. Switch to a detached HEAD.
1468 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001469 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001470
David Pursehouse8a68ff92012-09-24 12:15:13 +09001471 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001472 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001473 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1474 '%s\n' % revid)
1475 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001476 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001477
1478 return GitCommand(self,
1479 ['branch', '-D', name],
1480 capture_stdout = True,
1481 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001482
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001483 def PruneHeads(self):
1484 """Prune any topic branches already merged into upstream.
1485 """
1486 cb = self.CurrentBranch
1487 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001488 left = self._allrefs
1489 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001490 if name.startswith(R_HEADS):
1491 name = name[len(R_HEADS):]
1492 if cb is None or name != cb:
1493 kill.append(name)
1494
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001495 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001496 if cb is not None \
1497 and not self._revlist(HEAD + '...' + rev) \
1498 and not self.IsDirty(consider_untracked = False):
1499 self.work_git.DetachHead(HEAD)
1500 kill.append(cb)
1501
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001502 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001503 old = self.bare_git.GetHead()
1504 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001505 old = 'refs/heads/please_never_use_this_as_a_branch_name'
1506
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001507 try:
1508 self.bare_git.DetachHead(rev)
1509
1510 b = ['branch', '-d']
1511 b.extend(kill)
1512 b = GitCommand(self, b, bare=True,
1513 capture_stdout=True,
1514 capture_stderr=True)
1515 b.Wait()
1516 finally:
1517 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001518 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001519
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001520 for branch in kill:
1521 if (R_HEADS + branch) not in left:
1522 self.CleanPublishedCache()
1523 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001524
1525 if cb and cb not in kill:
1526 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001527 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001528
1529 kept = []
1530 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001531 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001532 branch = self.GetBranch(branch)
1533 base = branch.LocalMerge
1534 if not base:
1535 base = rev
1536 kept.append(ReviewableBranch(self, branch, base))
1537 return kept
1538
1539
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001540## Submodule Management ##
1541
1542 def GetRegisteredSubprojects(self):
1543 result = []
1544 def rec(subprojects):
1545 if not subprojects:
1546 return
1547 result.extend(subprojects)
1548 for p in subprojects:
1549 rec(p.subprojects)
1550 rec(self.subprojects)
1551 return result
1552
1553 def _GetSubmodules(self):
1554 # Unfortunately we cannot call `git submodule status --recursive` here
1555 # because the working tree might not exist yet, and it cannot be used
1556 # without a working tree in its current implementation.
1557
1558 def get_submodules(gitdir, rev):
1559 # Parse .gitmodules for submodule sub_paths and sub_urls
1560 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1561 if not sub_paths:
1562 return []
1563 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1564 # revision of submodule repository
1565 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1566 submodules = []
1567 for sub_path, sub_url in zip(sub_paths, sub_urls):
1568 try:
1569 sub_rev = sub_revs[sub_path]
1570 except KeyError:
1571 # Ignore non-exist submodules
1572 continue
1573 submodules.append((sub_rev, sub_path, sub_url))
1574 return submodules
1575
1576 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1577 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
1578 def parse_gitmodules(gitdir, rev):
1579 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1580 try:
1581 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1582 bare = True, gitdir = gitdir)
1583 except GitError:
1584 return [], []
1585 if p.Wait() != 0:
1586 return [], []
1587
1588 gitmodules_lines = []
1589 fd, temp_gitmodules_path = tempfile.mkstemp()
1590 try:
1591 os.write(fd, p.stdout)
1592 os.close(fd)
1593 cmd = ['config', '--file', temp_gitmodules_path, '--list']
1594 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1595 bare = True, gitdir = gitdir)
1596 if p.Wait() != 0:
1597 return [], []
1598 gitmodules_lines = p.stdout.split('\n')
1599 except GitError:
1600 return [], []
1601 finally:
1602 os.remove(temp_gitmodules_path)
1603
1604 names = set()
1605 paths = {}
1606 urls = {}
1607 for line in gitmodules_lines:
1608 if not line:
1609 continue
1610 m = re_path.match(line)
1611 if m:
1612 names.add(m.group(1))
1613 paths[m.group(1)] = m.group(2)
1614 continue
1615 m = re_url.match(line)
1616 if m:
1617 names.add(m.group(1))
1618 urls[m.group(1)] = m.group(2)
1619 continue
1620 names = sorted(names)
1621 return ([paths.get(name, '') for name in names],
1622 [urls.get(name, '') for name in names])
1623
1624 def git_ls_tree(gitdir, rev, paths):
1625 cmd = ['ls-tree', rev, '--']
1626 cmd.extend(paths)
1627 try:
1628 p = GitCommand(None, cmd, capture_stdout = True, capture_stderr = True,
1629 bare = True, gitdir = gitdir)
1630 except GitError:
1631 return []
1632 if p.Wait() != 0:
1633 return []
1634 objects = {}
1635 for line in p.stdout.split('\n'):
1636 if not line.strip():
1637 continue
1638 object_rev, object_path = line.split()[2:4]
1639 objects[object_path] = object_rev
1640 return objects
1641
1642 try:
1643 rev = self.GetRevisionId()
1644 except GitError:
1645 return []
1646 return get_submodules(self.gitdir, rev)
1647
1648 def GetDerivedSubprojects(self):
1649 result = []
1650 if not self.Exists:
1651 # If git repo does not exist yet, querying its submodules will
1652 # mess up its states; so return here.
1653 return result
1654 for rev, path, url in self._GetSubmodules():
1655 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001656 relpath, worktree, gitdir, objdir = \
1657 self.manifest.GetSubprojectPaths(self, name, path)
1658 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001659 if project:
1660 result.extend(project.GetDerivedSubprojects())
1661 continue
David James8d201162013-10-11 17:03:19 -07001662
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001663 remote = RemoteSpec(self.remote.name,
1664 url = url,
1665 review = self.remote.review)
1666 subproject = Project(manifest = self.manifest,
1667 name = name,
1668 remote = remote,
1669 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07001670 objdir = objdir,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001671 worktree = worktree,
1672 relpath = relpath,
1673 revisionExpr = self.revisionExpr,
1674 revisionId = rev,
1675 rebase = self.rebase,
1676 groups = self.groups,
1677 sync_c = self.sync_c,
1678 sync_s = self.sync_s,
1679 parent = self,
1680 is_derived = True)
1681 result.append(subproject)
1682 result.extend(subproject.GetDerivedSubprojects())
1683 return result
1684
1685
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001686## Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001687 def _CheckForSha1(self):
1688 try:
1689 # if revision (sha or tag) is not present then following function
1690 # throws an error.
1691 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1692 return True
1693 except GitError:
1694 # There is no such persistent revision. We have to fetch it.
1695 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001696
Julien Campergue335f5ef2013-10-16 11:02:35 +02001697 def _FetchArchive(self, tarpath, cwd=None):
1698 cmd = ['archive', '-v', '-o', tarpath]
1699 cmd.append('--remote=%s' % self.remote.url)
1700 cmd.append('--prefix=%s/' % self.relpath)
1701 cmd.append(self.revisionExpr)
1702
1703 command = GitCommand(self, cmd, cwd=cwd,
1704 capture_stdout=True,
1705 capture_stderr=True)
1706
1707 if command.Wait() != 0:
1708 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1709
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001710 def _RemoteFetch(self, name=None,
1711 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001712 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001713 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001714 alt_dir=None,
1715 no_tags=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001716
1717 is_sha1 = False
1718 tag_name = None
1719
Shawn Pearce69e04d82014-01-29 12:48:54 -08001720 if self.clone_depth:
1721 depth = self.clone_depth
1722 else:
1723 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1724 if depth:
1725 current_branch_only = True
1726
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001727 if current_branch_only:
1728 if ID_RE.match(self.revisionExpr) is not None:
1729 is_sha1 = True
1730 elif self.revisionExpr.startswith(R_TAGS):
1731 # this is a tag and its sha1 value should never change
1732 tag_name = self.revisionExpr[len(R_TAGS):]
1733
1734 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001735 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001736 return True
Brian Harring14a66742012-09-28 20:21:57 -07001737 if is_sha1 and (not self.upstream or ID_RE.match(self.upstream)):
1738 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001739
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001740 if not name:
1741 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001742
1743 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001744 remote = self.GetRemote(name)
1745 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001746 ssh_proxy = True
1747
Shawn O. Pearce88443382010-10-08 10:02:09 +02001748 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001749 if alt_dir and 'objects' == os.path.basename(alt_dir):
1750 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001751 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1752 remote = self.GetRemote(name)
1753
David Pursehouse8a68ff92012-09-24 12:15:13 +09001754 all_refs = self.bare_ref.all
1755 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001756 tmp = set()
1757
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301758 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001759 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001760 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001761 all_refs[r] = ref_id
1762 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001763 continue
1764
David Pursehouse8a68ff92012-09-24 12:15:13 +09001765 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001766 continue
1767
David Pursehouse8a68ff92012-09-24 12:15:13 +09001768 r = 'refs/_alt/%s' % ref_id
1769 all_refs[r] = ref_id
1770 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001771 tmp.add(r)
1772
Shawn O. Pearce88443382010-10-08 10:02:09 +02001773 tmp_packed = ''
1774 old_packed = ''
1775
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301776 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001777 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001778 tmp_packed += line
1779 if r not in tmp:
1780 old_packed += line
1781
1782 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001783 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001784 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001785
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001786 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001787
1788 # The --depth option only affects the initial fetch; after that we'll do
1789 # full fetches of changes.
Doug Anderson30d45292011-05-04 15:01:04 -07001790 if depth and initial:
1791 cmd.append('--depth=%s' % depth)
1792
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001793 if quiet:
1794 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001795 if not self.worktree:
1796 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001797 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001798
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001799 # If using depth then we should not get all the tags since they may
1800 # be outside of the depth.
1801 if no_tags or depth:
1802 cmd.append('--no-tags')
1803 else:
1804 cmd.append('--tags')
1805
Brian Harring14a66742012-09-28 20:21:57 -07001806 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001807 # Fetch whole repo
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301808 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001809 elif tag_name is not None:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001810 cmd.append('tag')
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001811 cmd.append(tag_name)
1812 else:
1813 branch = self.revisionExpr
Brian Harring14a66742012-09-28 20:21:57 -07001814 if is_sha1:
1815 branch = self.upstream
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001816 if branch.startswith(R_HEADS):
1817 branch = branch[len(R_HEADS):]
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301818 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001819
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001820 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001821 for _i in range(2):
Brian Harring14a66742012-09-28 20:21:57 -07001822 ret = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy).Wait()
1823 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001824 ok = True
1825 break
Brian Harring14a66742012-09-28 20:21:57 -07001826 elif current_branch_only and is_sha1 and ret == 128:
1827 # Exit code 128 means "couldn't find the ref you asked for"; if we're in sha1
1828 # mode, we just tried sync'ing from the upstream field; it doesn't exist, thus
1829 # abort the optimization attempt and do a full sync.
1830 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001831 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001832
1833 if initial:
Conley Owens56548052014-02-11 18:44:58 -08001834 # Ensure that some refs exist. Otherwise, we probably aren't looking
1835 # at a real git repository and may have a bad url.
1836 if not self.bare_ref.all:
David Pursehouse68425f42014-03-11 14:55:52 +09001837 ok = False
Conley Owens56548052014-02-11 18:44:58 -08001838
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001839 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001840 if old_packed != '':
1841 _lwrite(packed_refs, old_packed)
1842 else:
1843 os.remove(packed_refs)
1844 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001845
1846 if is_sha1 and current_branch_only and self.upstream:
1847 # We just synced the upstream given branch; verify we
1848 # got what we wanted, else trigger a second run of all
1849 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001850 if not self._CheckForSha1():
Brian Harring14a66742012-09-28 20:21:57 -07001851 return self._RemoteFetch(name=name, current_branch_only=False,
1852 initial=False, quiet=quiet, alt_dir=alt_dir)
1853
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001854 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02001855
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001856 def _ApplyCloneBundle(self, initial=False, quiet=False):
David Pursehouseede7f122012-11-27 22:25:30 +09001857 if initial and (self.manifest.manifestProject.config.GetString('repo.depth') or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001858 return False
1859
1860 remote = self.GetRemote(self.remote.name)
1861 bundle_url = remote.url + '/clone.bundle'
1862 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001863 if GetSchemeFromUrl(bundle_url) not in (
1864 'http', 'https', 'persistent-http', 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001865 return False
1866
1867 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
1868 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
1869
1870 exist_dst = os.path.exists(bundle_dst)
1871 exist_tmp = os.path.exists(bundle_tmp)
1872
1873 if not initial and not exist_dst and not exist_tmp:
1874 return False
1875
1876 if not exist_dst:
1877 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
1878 if not exist_dst:
1879 return False
1880
1881 cmd = ['fetch']
1882 if quiet:
1883 cmd.append('--quiet')
1884 if not self.worktree:
1885 cmd.append('--update-head-ok')
1886 cmd.append(bundle_dst)
1887 for f in remote.fetch:
1888 cmd.append(str(f))
1889 cmd.append('refs/tags/*:refs/tags/*')
1890
1891 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001892 if os.path.exists(bundle_dst):
1893 os.remove(bundle_dst)
1894 if os.path.exists(bundle_tmp):
1895 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001896 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001897
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001898 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001899 if os.path.exists(dstPath):
1900 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001901
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001902 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001903 if quiet:
1904 cmd += ['--silent']
1905 if os.path.exists(tmpPath):
1906 size = os.stat(tmpPath).st_size
1907 if size >= 1024:
1908 cmd += ['--continue-at', '%d' % (size,)]
1909 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001910 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001911 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
1912 cmd += ['--proxy', os.environ['http_proxy']]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001913 cookiefile = self._GetBundleCookieFile(srcUrl)
Torne (Richard Coles)ed68d0e2013-01-11 16:22:54 +00001914 if cookiefile:
1915 cmd += ['--cookie', cookiefile]
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001916 if srcUrl.startswith('persistent-'):
1917 srcUrl = srcUrl[len('persistent-'):]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001918 cmd += [srcUrl]
1919
1920 if IsTrace():
1921 Trace('%s', ' '.join(cmd))
1922 try:
1923 proc = subprocess.Popen(cmd)
1924 except OSError:
1925 return False
1926
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001927 curlret = proc.wait()
1928
1929 if curlret == 22:
1930 # From curl man page:
1931 # 22: HTTP page not retrieved. The requested url was not found or
1932 # returned another error with the HTTP error code being 400 or above.
1933 # This return code only appears if -f, --fail is used.
1934 if not quiet:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001935 print("Server does not provide clone.bundle; ignoring.",
1936 file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07001937 return False
1938
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001939 if os.path.exists(tmpPath):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001940 if curlret == 0 and self._IsValidBundle(tmpPath):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07001941 os.rename(tmpPath, dstPath)
1942 return True
1943 else:
1944 os.remove(tmpPath)
1945 return False
1946 else:
1947 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001948
Dave Borowitz91f3ba52013-06-03 12:15:23 -07001949 def _IsValidBundle(self, path):
1950 try:
1951 with open(path) as f:
1952 if f.read(16) == '# v2 git bundle\n':
1953 return True
1954 else:
1955 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
1956 return False
1957 except OSError:
1958 return False
1959
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001960 def _GetBundleCookieFile(self, url):
1961 if url.startswith('persistent-'):
1962 try:
1963 p = subprocess.Popen(
1964 ['git-remote-persistent-https', '-print_config', url],
1965 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1966 stderr=subprocess.PIPE)
Dave Borowitz0836a222013-09-25 17:46:01 -07001967 p.stdin.close() # Tell subprocess it's ok to close.
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001968 prefix = 'http.cookiefile='
Dave Borowitz0836a222013-09-25 17:46:01 -07001969 cookiefile = None
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001970 for line in p.stdout:
1971 line = line.strip()
1972 if line.startswith(prefix):
Dave Borowitz0836a222013-09-25 17:46:01 -07001973 cookiefile = line[len(prefix):]
1974 break
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001975 if p.wait():
Conley Owenscbc07982013-11-21 10:38:03 -08001976 err_msg = p.stderr.read()
1977 if ' -print_config' in err_msg:
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001978 pass # Persistent proxy doesn't support -print_config.
1979 else:
Conley Owenscbc07982013-11-21 10:38:03 -08001980 print(err_msg, file=sys.stderr)
Dave Borowitz0836a222013-09-25 17:46:01 -07001981 if cookiefile:
1982 return cookiefile
Dave Borowitz74c1f3d2013-06-03 15:05:07 -07001983 except OSError as e:
1984 if e.errno == errno.ENOENT:
1985 pass # No persistent proxy.
1986 raise
1987 return GitConfig.ForUser().GetString('http.cookiefile')
1988
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001989 def _Checkout(self, rev, quiet=False):
1990 cmd = ['checkout']
1991 if quiet:
1992 cmd.append('-q')
1993 cmd.append(rev)
1994 cmd.append('--')
1995 if GitCommand(self, cmd).Wait() != 0:
1996 if self._allrefs:
1997 raise GitError('%s checkout %s ' % (self.name, rev))
1998
Pierre Tardye5a21222011-03-24 16:28:18 +01001999 def _CherryPick(self, rev, quiet=False):
2000 cmd = ['cherry-pick']
2001 cmd.append(rev)
2002 cmd.append('--')
2003 if GitCommand(self, cmd).Wait() != 0:
2004 if self._allrefs:
2005 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2006
Erwan Mahea94f1622011-08-19 13:56:09 +02002007 def _Revert(self, rev, quiet=False):
2008 cmd = ['revert']
2009 cmd.append('--no-edit')
2010 cmd.append(rev)
2011 cmd.append('--')
2012 if GitCommand(self, cmd).Wait() != 0:
2013 if self._allrefs:
2014 raise GitError('%s revert %s ' % (self.name, rev))
2015
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002016 def _ResetHard(self, rev, quiet=True):
2017 cmd = ['reset', '--hard']
2018 if quiet:
2019 cmd.append('-q')
2020 cmd.append(rev)
2021 if GitCommand(self, cmd).Wait() != 0:
2022 raise GitError('%s reset --hard %s ' % (self.name, rev))
2023
2024 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002025 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002026 if onto is not None:
2027 cmd.extend(['--onto', onto])
2028 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002029 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002030 raise GitError('%s rebase %s ' % (self.name, upstream))
2031
Pierre Tardy3d125942012-05-04 12:18:12 +02002032 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002033 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002034 if ffonly:
2035 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002036 if GitCommand(self, cmd).Wait() != 0:
2037 raise GitError('%s merge %s ' % (self.name, head))
2038
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002039 def _InitGitDir(self, mirror_git=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002040 if not os.path.exists(self.gitdir):
David James8d201162013-10-11 17:03:19 -07002041
2042 # Initialize the bare repository, which contains all of the objects.
2043 if not os.path.exists(self.objdir):
2044 os.makedirs(self.objdir)
2045 self.bare_objdir.init()
2046
2047 # If we have a separate directory to hold refs, initialize it as well.
2048 if self.objdir != self.gitdir:
2049 os.makedirs(self.gitdir)
2050 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2051 copy_all=True)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002052
Shawn O. Pearce88443382010-10-08 10:02:09 +02002053 mp = self.manifest.manifestProject
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002054 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002055
Victor Boivie2b30e3a2012-10-05 12:37:58 +02002056 if ref_dir or mirror_git:
2057 if not mirror_git:
2058 mirror_git = os.path.join(ref_dir, self.name + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002059 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2060 self.relpath + '.git')
2061
2062 if os.path.exists(mirror_git):
2063 ref_dir = mirror_git
2064
2065 elif os.path.exists(repo_git):
2066 ref_dir = repo_git
2067
2068 else:
2069 ref_dir = None
2070
2071 if ref_dir:
2072 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2073 os.path.join(ref_dir, 'objects') + '\n')
2074
Jimmie Westera0444582012-10-24 13:44:42 +02002075 self._UpdateHooks()
2076
2077 m = self.manifest.manifestProject.config
2078 for key in ['user.name', 'user.email']:
2079 if m.Has(key, include_defaults = False):
2080 self.config.SetString(key, m.GetString(key))
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002081 if self.manifest.IsMirror:
2082 self.config.SetString('core.bare', 'true')
2083 else:
2084 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002085
Jimmie Westera0444582012-10-24 13:44:42 +02002086 def _UpdateHooks(self):
2087 if os.path.exists(self.gitdir):
2088 # Always recreate hooks since they can have been changed
2089 # since the latest update.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002090 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07002091 try:
2092 to_rm = os.listdir(hooks)
2093 except OSError:
2094 to_rm = []
2095 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002096 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002097 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002098
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002099 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002100 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002101 if not os.path.exists(hooks):
2102 os.makedirs(hooks)
Doug Anderson8ced8642011-01-10 14:16:30 -08002103 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002104 name = os.path.basename(stock_hook)
2105
Victor Boivie65e0f352011-04-18 11:23:29 +02002106 if name in ('commit-msg',) and not self.remote.review \
2107 and not self is self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002108 # Don't install a Gerrit Code Review hook if this
2109 # project does not appear to use it for reviews.
2110 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002111 # Since the manifest project is one of those, but also
2112 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002113 continue
2114
2115 dst = os.path.join(hooks, name)
2116 if os.path.islink(dst):
2117 continue
2118 if os.path.exists(dst):
2119 if filecmp.cmp(stock_hook, dst, shallow=False):
2120 os.remove(dst)
2121 else:
2122 _error("%s: Not replacing %s hook", self.relpath, name)
2123 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002124 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002125 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002126 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002127 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002128 raise GitError('filesystem must support symlinks')
2129 else:
2130 raise
2131
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002132 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002133 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002135 remote.url = self.remote.url
2136 remote.review = self.remote.review
2137 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002138
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002139 if self.worktree:
2140 remote.ResetFetch(mirror=False)
2141 else:
2142 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002143 remote.Save()
2144
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002145 def _InitMRef(self):
2146 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002147 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002148
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002149 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002150 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002151
2152 def _InitAnyMRef(self, ref):
2153 cur = self.bare_ref.symref(ref)
2154
2155 if self.revisionId:
2156 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2157 msg = 'manifest set to %s' % self.revisionId
2158 dst = self.revisionId + '^0'
2159 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
2160 else:
2161 remote = self.GetRemote(self.remote.name)
2162 dst = remote.ToLocal(self.revisionExpr)
2163 if cur != dst:
2164 msg = 'manifest set to %s' % self.revisionExpr
2165 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002166
David James8d201162013-10-11 17:03:19 -07002167 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2168 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2169
2170 Args:
2171 gitdir: The bare git repository. Must already be initialized.
2172 dotgit: The repository you would like to initialize.
2173 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2174 Only one work tree can store refs under a given |gitdir|.
2175 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2176 This saves you the effort of initializing |dotgit| yourself.
2177 """
2178 # These objects can be shared between several working trees.
2179 symlink_files = ['description', 'info']
2180 symlink_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
2181 if share_refs:
2182 # These objects can only be used by a single working tree.
2183 symlink_files += ['config', 'packed-refs']
2184 symlink_dirs += ['logs', 'refs']
2185 to_symlink = symlink_files + symlink_dirs
2186
2187 to_copy = []
2188 if copy_all:
2189 to_copy = os.listdir(gitdir)
2190
2191 for name in set(to_copy).union(to_symlink):
2192 try:
2193 src = os.path.realpath(os.path.join(gitdir, name))
2194 dst = os.path.realpath(os.path.join(dotgit, name))
2195
2196 if os.path.lexists(dst) and not os.path.islink(dst):
2197 raise GitError('cannot overwrite a local work tree')
2198
2199 # If the source dir doesn't exist, create an empty dir.
2200 if name in symlink_dirs and not os.path.lexists(src):
2201 os.makedirs(src)
2202
2203 if name in to_symlink:
2204 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2205 elif copy_all and not os.path.islink(dst):
2206 if os.path.isdir(src):
2207 shutil.copytree(src, dst)
2208 elif os.path.isfile(src):
2209 shutil.copy(src, dst)
2210 except OSError as e:
2211 if e.errno == errno.EPERM:
2212 raise GitError('filesystem must support symlinks')
2213 else:
2214 raise
2215
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002216 def _InitWorkTree(self):
2217 dotgit = os.path.join(self.worktree, '.git')
2218 if not os.path.exists(dotgit):
2219 os.makedirs(dotgit)
David James8d201162013-10-11 17:03:19 -07002220 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2221 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002222
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002223 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002224
2225 cmd = ['read-tree', '--reset', '-u']
2226 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002227 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002228 if GitCommand(self, cmd).Wait() != 0:
2229 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002230
Jeff Hamiltone0df2322014-04-21 17:10:59 -05002231 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002232
2233 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002234 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002235
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002236 def _revlist(self, *args, **kw):
2237 a = []
2238 a.extend(args)
2239 a.append('--')
2240 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002241
2242 @property
2243 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002244 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002245
Julien Camperguedd654222014-01-09 16:21:37 +01002246 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2247 """Get logs between two revisions of this project."""
2248 comp = '..'
2249 if rev1:
2250 revs = [rev1]
2251 if rev2:
2252 revs.extend([comp, rev2])
2253 cmd = ['log', ''.join(revs)]
2254 out = DiffColoring(self.config)
2255 if out.is_on and color:
2256 cmd.append('--color')
2257 if oneline:
2258 cmd.append('--oneline')
2259
2260 try:
2261 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2262 if log.Wait() == 0:
2263 return log.stdout
2264 except GitError:
2265 # worktree may not exist if groups changed for example. In that case,
2266 # try in gitdir instead.
2267 if not os.path.exists(self.worktree):
2268 return self.bare_git.log(*cmd[1:])
2269 else:
2270 raise
2271 return None
2272
2273 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2274 """Get the list of logs from this revision to given revisionId"""
2275 logs = {}
2276 selfId = self.GetRevisionId(self._allrefs)
2277 toId = toProject.GetRevisionId(toProject._allrefs)
2278
2279 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2280 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2281 return logs
2282
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002283 class _GitGetByExec(object):
David James8d201162013-10-11 17:03:19 -07002284 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002285 self._project = project
2286 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002287 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002289 def LsOthers(self):
2290 p = GitCommand(self._project,
2291 ['ls-files',
2292 '-z',
2293 '--others',
2294 '--exclude-standard'],
2295 bare = False,
David James8d201162013-10-11 17:03:19 -07002296 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002297 capture_stdout = True,
2298 capture_stderr = True)
2299 if p.Wait() == 0:
2300 out = p.stdout
2301 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002302 return out[:-1].split('\0') # pylint: disable=W1401
2303 # Backslash is not anomalous
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002304 return []
2305
2306 def DiffZ(self, name, *args):
2307 cmd = [name]
2308 cmd.append('-z')
2309 cmd.extend(args)
2310 p = GitCommand(self._project,
2311 cmd,
David James8d201162013-10-11 17:03:19 -07002312 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002313 bare = False,
2314 capture_stdout = True,
2315 capture_stderr = True)
2316 try:
2317 out = p.process.stdout.read()
2318 r = {}
2319 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002320 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002321 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002322 try:
2323 info = out.next()
2324 path = out.next()
2325 except StopIteration:
2326 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002327
2328 class _Info(object):
2329 def __init__(self, path, omode, nmode, oid, nid, state):
2330 self.path = path
2331 self.src_path = None
2332 self.old_mode = omode
2333 self.new_mode = nmode
2334 self.old_id = oid
2335 self.new_id = nid
2336
2337 if len(state) == 1:
2338 self.status = state
2339 self.level = None
2340 else:
2341 self.status = state[:1]
2342 self.level = state[1:]
2343 while self.level.startswith('0'):
2344 self.level = self.level[1:]
2345
2346 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002347 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002348 if info.status in ('R', 'C'):
2349 info.src_path = info.path
2350 info.path = out.next()
2351 r[info.path] = info
2352 return r
2353 finally:
2354 p.Wait()
2355
2356 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002357 if self._bare:
2358 path = os.path.join(self._project.gitdir, HEAD)
2359 else:
2360 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002361 try:
2362 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002363 except IOError as e:
2364 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002365 try:
2366 line = fd.read()
2367 finally:
2368 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302369 try:
2370 line = line.decode()
2371 except AttributeError:
2372 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002373 if line.startswith('ref: '):
2374 return line[5:-1]
2375 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002376
2377 def SetHead(self, ref, message=None):
2378 cmdv = []
2379 if message is not None:
2380 cmdv.extend(['-m', message])
2381 cmdv.append(HEAD)
2382 cmdv.append(ref)
2383 self.symbolic_ref(*cmdv)
2384
2385 def DetachHead(self, new, message=None):
2386 cmdv = ['--no-deref']
2387 if message is not None:
2388 cmdv.extend(['-m', message])
2389 cmdv.append(HEAD)
2390 cmdv.append(new)
2391 self.update_ref(*cmdv)
2392
2393 def UpdateRef(self, name, new, old=None,
2394 message=None,
2395 detach=False):
2396 cmdv = []
2397 if message is not None:
2398 cmdv.extend(['-m', message])
2399 if detach:
2400 cmdv.append('--no-deref')
2401 cmdv.append(name)
2402 cmdv.append(new)
2403 if old is not None:
2404 cmdv.append(old)
2405 self.update_ref(*cmdv)
2406
2407 def DeleteRef(self, name, old=None):
2408 if not old:
2409 old = self.rev_parse(name)
2410 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002411 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002412
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002413 def rev_list(self, *args, **kw):
2414 if 'format' in kw:
2415 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2416 else:
2417 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002418 cmdv.extend(args)
2419 p = GitCommand(self._project,
2420 cmdv,
2421 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002422 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002423 capture_stdout = True,
2424 capture_stderr = True)
2425 r = []
2426 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002427 if line[-1] == '\n':
2428 line = line[:-1]
2429 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002430 if p.Wait() != 0:
2431 raise GitError('%s rev-list %s: %s' % (
2432 self._project.name,
2433 str(args),
2434 p.stderr))
2435 return r
2436
2437 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002438 """Allow arbitrary git commands using pythonic syntax.
2439
2440 This allows you to do things like:
2441 git_obj.rev_parse('HEAD')
2442
2443 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2444 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002445 Any other positional arguments will be passed to the git command, and the
2446 following keyword arguments are supported:
2447 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002448
2449 Args:
2450 name: The name of the git command to call. Any '_' characters will
2451 be replaced with '-'.
2452
2453 Returns:
2454 A callable object that will try to call git with the named command.
2455 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002456 name = name.replace('_', '-')
Dave Borowitz091f8932012-10-23 17:01:04 -07002457 def runner(*args, **kwargs):
2458 cmdv = []
2459 config = kwargs.pop('config', None)
2460 for k in kwargs:
2461 raise TypeError('%s() got an unexpected keyword argument %r'
2462 % (name, k))
2463 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002464 if not git_require((1, 7, 2)):
2465 raise ValueError('cannot set config on command line for %s()'
2466 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302467 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002468 cmdv.append('-c')
2469 cmdv.append('%s=%s' % (k, v))
2470 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002471 cmdv.extend(args)
2472 p = GitCommand(self._project,
2473 cmdv,
2474 bare = self._bare,
David James8d201162013-10-11 17:03:19 -07002475 gitdir=self._gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002476 capture_stdout = True,
2477 capture_stderr = True)
2478 if p.Wait() != 0:
2479 raise GitError('%s %s: %s' % (
2480 self._project.name,
2481 name,
2482 p.stderr))
2483 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302484 try:
Conley Owensedd01512013-09-26 12:59:58 -07002485 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302486 except AttributeError:
2487 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002488 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2489 return r[:-1]
2490 return r
2491 return runner
2492
2493
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002494class _PriorSyncFailedError(Exception):
2495 def __str__(self):
2496 return 'prior sync failed; rebase still in progress'
2497
2498class _DirtyError(Exception):
2499 def __str__(self):
2500 return 'contains uncommitted changes'
2501
2502class _InfoMessage(object):
2503 def __init__(self, project, text):
2504 self.project = project
2505 self.text = text
2506
2507 def Print(self, syncbuf):
2508 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2509 syncbuf.out.nl()
2510
2511class _Failure(object):
2512 def __init__(self, project, why):
2513 self.project = project
2514 self.why = why
2515
2516 def Print(self, syncbuf):
2517 syncbuf.out.fail('error: %s/: %s',
2518 self.project.relpath,
2519 str(self.why))
2520 syncbuf.out.nl()
2521
2522class _Later(object):
2523 def __init__(self, project, action):
2524 self.project = project
2525 self.action = action
2526
2527 def Run(self, syncbuf):
2528 out = syncbuf.out
2529 out.project('project %s/', self.project.relpath)
2530 out.nl()
2531 try:
2532 self.action()
2533 out.nl()
2534 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002535 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002536 out.nl()
2537 return False
2538
2539class _SyncColoring(Coloring):
2540 def __init__(self, config):
2541 Coloring.__init__(self, config, 'reposync')
2542 self.project = self.printer('header', attr = 'bold')
2543 self.info = self.printer('info')
2544 self.fail = self.printer('fail', fg='red')
2545
2546class SyncBuffer(object):
2547 def __init__(self, config, detach_head=False):
2548 self._messages = []
2549 self._failures = []
2550 self._later_queue1 = []
2551 self._later_queue2 = []
2552
2553 self.out = _SyncColoring(config)
2554 self.out.redirect(sys.stderr)
2555
2556 self.detach_head = detach_head
2557 self.clean = True
2558
2559 def info(self, project, fmt, *args):
2560 self._messages.append(_InfoMessage(project, fmt % args))
2561
2562 def fail(self, project, err=None):
2563 self._failures.append(_Failure(project, err))
2564 self.clean = False
2565
2566 def later1(self, project, what):
2567 self._later_queue1.append(_Later(project, what))
2568
2569 def later2(self, project, what):
2570 self._later_queue2.append(_Later(project, what))
2571
2572 def Finish(self):
2573 self._PrintMessages()
2574 self._RunLater()
2575 self._PrintMessages()
2576 return self.clean
2577
2578 def _RunLater(self):
2579 for q in ['_later_queue1', '_later_queue2']:
2580 if not self._RunQueue(q):
2581 return
2582
2583 def _RunQueue(self, queue):
2584 for m in getattr(self, queue):
2585 if not m.Run(self):
2586 self.clean = False
2587 return False
2588 setattr(self, queue, [])
2589 return True
2590
2591 def _PrintMessages(self):
2592 for m in self._messages:
2593 m.Print(self)
2594 for m in self._failures:
2595 m.Print(self)
2596
2597 self._messages = []
2598 self._failures = []
2599
2600
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002601class MetaProject(Project):
2602 """A special project housed under .repo.
2603 """
2604 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002605 Project.__init__(self,
2606 manifest = manifest,
2607 name = name,
2608 gitdir = gitdir,
David James8d201162013-10-11 17:03:19 -07002609 objdir = gitdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002610 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002611 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002612 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002613 revisionExpr = 'refs/heads/master',
Colin Cross5acde752012-03-28 20:15:45 -07002614 revisionId = None,
2615 groups = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002616
2617 def PreSync(self):
2618 if self.Exists:
2619 cb = self.CurrentBranch
2620 if cb:
2621 base = self.GetBranch(cb).merge
2622 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002623 self.revisionExpr = base
2624 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002625
Florian Vallee5d016502012-06-07 17:19:26 +02002626 def MetaBranchSwitch(self, target):
2627 """ Prepare MetaProject for manifest branch switch
2628 """
2629
2630 # detach and delete manifest branch, allowing a new
2631 # branch to take over
2632 syncbuf = SyncBuffer(self.config, detach_head = True)
2633 self.Sync_LocalHalf(syncbuf)
2634 syncbuf.Finish()
2635
2636 return GitCommand(self,
Torne (Richard Coles)e8f75fa2012-07-20 15:32:19 +01002637 ['update-ref', '-d', 'refs/heads/default'],
Florian Vallee5d016502012-06-07 17:19:26 +02002638 capture_stdout = True,
2639 capture_stderr = True).Wait() == 0
2640
2641
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002642 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002643 def LastFetch(self):
2644 try:
2645 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2646 return os.path.getmtime(fh)
2647 except OSError:
2648 return 0
2649
2650 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002651 def HasChanges(self):
2652 """Has the remote received new commits not yet checked out?
2653 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002654 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002655 return False
2656
David Pursehouse8a68ff92012-09-24 12:15:13 +09002657 all_refs = self.bare_ref.all
2658 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002659 head = self.work_git.GetHead()
2660 if head.startswith(R_HEADS):
2661 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002662 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002663 except KeyError:
2664 head = None
2665
2666 if revid == head:
2667 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002668 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002669 return True
2670 return False