blob: 1e1a10aea61db869ed9be8b2bc9006770009e7df [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
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080016import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070018import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import 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
Dave Borowitz137d0132015-01-02 11:12:54 -080029import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070032from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
34 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070035from error import GitError, HookError, UploadError, DownloadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080036from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080037from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070038from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
Shawn O. Pearced237b692009-04-17 18:49:50 -070040from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
David Pursehouse59bbb582013-05-17 10:49:33 +090042from pyversion import is_python3
43if not is_python3():
44 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053045 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090046 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053047
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070048
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070049def _lwrite(path, content):
50 lock = '%s.lock' % path
51
Chirayu Desai303a82f2014-08-19 22:57:17 +053052 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070053 try:
54 fd.write(content)
55 finally:
56 fd.close()
57
58 try:
59 os.rename(lock, path)
60 except OSError:
61 os.remove(lock)
62 raise
63
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070064
Shawn O. Pearce48244782009-04-16 08:25:57 -070065def _error(fmt, *args):
66 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070067 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070068
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070069
David Pursehousef33929d2015-08-24 14:39:14 +090070def _warn(fmt, *args):
71 msg = fmt % args
72 print('warn: %s' % msg, file=sys.stderr)
73
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070074
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070075def not_rev(r):
76 return '^' + r
77
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070078
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080079def sq(r):
80 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080081
Jonathan Nieder93719792015-03-17 11:29:58 -070082_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070083
84
Jonathan Nieder93719792015-03-17 11:29:58 -070085def _ProjectHooks():
86 """List the hooks present in the 'hooks' directory.
87
88 These hooks are project hooks and are copied to the '.git/hooks' directory
89 of all subprojects.
90
91 This function caches the list of hooks (based on the contents of the
92 'repo/hooks' directory) on the first call.
93
94 Returns:
95 A list of absolute paths to all of the files in the hooks directory.
96 """
97 global _project_hook_list
98 if _project_hook_list is None:
99 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
100 d = os.path.join(d, 'hooks')
101 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
102 return _project_hook_list
103
104
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700105class DownloadedChange(object):
106 _commit_cache = None
107
108 def __init__(self, project, base, change_id, ps_id, commit):
109 self.project = project
110 self.base = base
111 self.change_id = change_id
112 self.ps_id = ps_id
113 self.commit = commit
114
115 @property
116 def commits(self):
117 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700118 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
119 '--abbrev-commit',
120 '--pretty=oneline',
121 '--reverse',
122 '--date-order',
123 not_rev(self.base),
124 self.commit,
125 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700126 return self._commit_cache
127
128
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700129class ReviewableBranch(object):
130 _commit_cache = None
131
132 def __init__(self, project, branch, base):
133 self.project = project
134 self.branch = branch
135 self.base = base
136
137 @property
138 def name(self):
139 return self.branch.name
140
141 @property
142 def commits(self):
143 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700144 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
145 '--abbrev-commit',
146 '--pretty=oneline',
147 '--reverse',
148 '--date-order',
149 not_rev(self.base),
150 R_HEADS + self.name,
151 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700152 return self._commit_cache
153
154 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800155 def unabbrev_commits(self):
156 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700157 for commit in self.project.bare_git.rev_list(not_rev(self.base),
158 R_HEADS + self.name,
159 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800160 r[commit[0:8]] = commit
161 return r
162
163 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700164 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700165 return self.project.bare_git.log('--pretty=format:%cd',
166 '-n', '1',
167 R_HEADS + self.name,
168 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700170 def UploadForReview(self, people,
171 auto_topic=False,
172 draft=False,
173 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800174 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700175 people,
Brian Harring435370c2012-07-28 15:37:04 -0700176 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400177 draft=draft,
178 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700179
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700180 def GetPublishedRefs(self):
181 refs = {}
182 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700183 self.branch.remote.SshReviewUrl(self.project.UserEmail),
184 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700185 for line in output.split('\n'):
186 try:
187 (sha, ref) = line.split()
188 refs[sha] = ref
189 except ValueError:
190 pass
191
192 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700193
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700194
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700196
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197 def __init__(self, config):
198 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100199 self.project = self.printer('header', attr='bold')
200 self.branch = self.printer('header', attr='bold')
201 self.nobranch = self.printer('nobranch', fg='red')
202 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203
Anthony King7bdac712014-07-16 12:56:40 +0100204 self.added = self.printer('added', fg='green')
205 self.changed = self.printer('changed', fg='red')
206 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207
208
209class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700210
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211 def __init__(self, config):
212 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100213 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700215
Anthony King7bdac712014-07-16 12:56:40 +0100216class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700217
James W. Mills24c13082012-04-12 15:04:13 -0500218 def __init__(self, name, value, keep):
219 self.name = name
220 self.value = value
221 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700223
Anthony King7bdac712014-07-16 12:56:40 +0100224class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700225
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800226 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227 self.src = src
228 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800229 self.abs_src = abssrc
230 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231
232 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800233 src = self.abs_src
234 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235 # copy file if it does not exist or is out of date
236 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
237 try:
238 # remove existing file first, since it might be read-only
239 if os.path.exists(dest):
240 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400241 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200242 dest_dir = os.path.dirname(dest)
243 if not os.path.isdir(dest_dir):
244 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245 shutil.copy(src, dest)
246 # make the file read-only
247 mode = os.stat(dest)[stat.ST_MODE]
248 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
249 os.chmod(dest, mode)
250 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700251 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700253
Anthony King7bdac712014-07-16 12:56:40 +0100254class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700255
Wink Saville4c426ef2015-06-03 08:05:17 -0700256 def __init__(self, git_worktree, src, dest, relsrc, absdest):
257 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500258 self.src = src
259 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700260 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500261 self.abs_dest = absdest
262
Wink Saville4c426ef2015-06-03 08:05:17 -0700263 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500264 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700265 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500266 try:
267 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800268 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700269 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500270 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700271 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500272 if not os.path.isdir(dest_dir):
273 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700274 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500275 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700276 _error('Cannot link file %s to %s', relSrc, absDest)
277
278 def _Link(self):
279 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
280 on the src linking all of the files in the source in to the destination
281 directory.
282 """
283 # We use the absSrc to handle the situation where the current directory
284 # is not the root of the repo
285 absSrc = os.path.join(self.git_worktree, self.src)
286 if os.path.exists(absSrc):
287 # Entity exists so just a simple one to one link operation
288 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
289 else:
290 # Entity doesn't exist assume there is a wild card
291 absDestDir = self.abs_dest
292 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
293 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700294 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700295 else:
296 absSrcFiles = glob.glob(absSrc)
297 for absSrcFile in absSrcFiles:
298 # Create a releative path from source dir to destination dir
299 absSrcDir = os.path.dirname(absSrcFile)
300 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
301
302 # Get the source file name
303 srcFile = os.path.basename(absSrcFile)
304
305 # Now form the final full paths to srcFile. They will be
306 # absolute for the desintaiton and relative for the srouce.
307 absDest = os.path.join(absDestDir, srcFile)
308 relSrc = os.path.join(relSrcDir, srcFile)
309 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500310
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700311
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700312class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700313
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700314 def __init__(self,
315 name,
Anthony King7bdac712014-07-16 12:56:40 +0100316 url=None,
317 review=None,
318 revision=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700319 self.name = name
320 self.url = url
321 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100322 self.revision = revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700323
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700324
Doug Anderson37282b42011-03-04 11:54:18 -0800325class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700326
Doug Anderson37282b42011-03-04 11:54:18 -0800327 """A RepoHook contains information about a script to run as a hook.
328
329 Hooks are used to run a python script before running an upload (for instance,
330 to run presubmit checks). Eventually, we may have hooks for other actions.
331
332 This shouldn't be confused with files in the 'repo/hooks' directory. Those
333 files are copied into each '.git/hooks' folder for each project. Repo-level
334 hooks are associated instead with repo actions.
335
336 Hooks are always python. When a hook is run, we will load the hook into the
337 interpreter and execute its main() function.
338 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700339
Doug Anderson37282b42011-03-04 11:54:18 -0800340 def __init__(self,
341 hook_type,
342 hooks_project,
343 topdir,
344 abort_if_user_denies=False):
345 """RepoHook constructor.
346
347 Params:
348 hook_type: A string representing the type of hook. This is also used
349 to figure out the name of the file containing the hook. For
350 example: 'pre-upload'.
351 hooks_project: The project containing the repo hooks. If you have a
352 manifest, this is manifest.repo_hooks_project. OK if this is None,
353 which will make the hook a no-op.
354 topdir: Repo's top directory (the one containing the .repo directory).
355 Scripts will run with CWD as this directory. If you have a manifest,
356 this is manifest.topdir
357 abort_if_user_denies: If True, we'll throw a HookError() if the user
358 doesn't allow us to run the hook.
359 """
360 self._hook_type = hook_type
361 self._hooks_project = hooks_project
362 self._topdir = topdir
363 self._abort_if_user_denies = abort_if_user_denies
364
365 # Store the full path to the script for convenience.
366 if self._hooks_project:
367 self._script_fullpath = os.path.join(self._hooks_project.worktree,
368 self._hook_type + '.py')
369 else:
370 self._script_fullpath = None
371
372 def _GetHash(self):
373 """Return a hash of the contents of the hooks directory.
374
375 We'll just use git to do this. This hash has the property that if anything
376 changes in the directory we will return a different has.
377
378 SECURITY CONSIDERATION:
379 This hash only represents the contents of files in the hook directory, not
380 any other files imported or called by hooks. Changes to imported files
381 can change the script behavior without affecting the hash.
382
383 Returns:
384 A string representing the hash. This will always be ASCII so that it can
385 be printed to the user easily.
386 """
387 assert self._hooks_project, "Must have hooks to calculate their hash."
388
389 # We will use the work_git object rather than just calling GetRevisionId().
390 # That gives us a hash of the latest checked in version of the files that
391 # the user will actually be executing. Specifically, GetRevisionId()
392 # doesn't appear to change even if a user checks out a different version
393 # of the hooks repo (via git checkout) nor if a user commits their own revs.
394 #
395 # NOTE: Local (non-committed) changes will not be factored into this hash.
396 # I think this is OK, since we're really only worried about warning the user
397 # about upstream changes.
398 return self._hooks_project.work_git.rev_parse('HEAD')
399
400 def _GetMustVerb(self):
401 """Return 'must' if the hook is required; 'should' if not."""
402 if self._abort_if_user_denies:
403 return 'must'
404 else:
405 return 'should'
406
407 def _CheckForHookApproval(self):
408 """Check to see whether this hook has been approved.
409
410 We'll look at the hash of all of the hooks. If this matches the hash that
411 the user last approved, we're done. If it doesn't, we'll ask the user
412 about approval.
413
414 Note that we ask permission for each individual hook even though we use
415 the hash of all hooks when detecting changes. We'd like the user to be
416 able to approve / deny each hook individually. We only use the hash of all
417 hooks because there is no other easy way to detect changes to local imports.
418
419 Returns:
420 True if this hook is approved to run; False otherwise.
421
422 Raises:
423 HookError: Raised if the user doesn't approve and abort_if_user_denies
424 was passed to the consturctor.
425 """
Doug Anderson37282b42011-03-04 11:54:18 -0800426 hooks_config = self._hooks_project.config
427 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type
428
429 # Get the last hash that the user approved for this hook; may be None.
430 old_hash = hooks_config.GetString(git_approval_key)
431
432 # Get the current hash so we can tell if scripts changed since approval.
433 new_hash = self._GetHash()
434
435 if old_hash is not None:
436 # User previously approved hook and asked not to be prompted again.
437 if new_hash == old_hash:
438 # Approval matched. We're done.
439 return True
440 else:
441 # Give the user a reason why we're prompting, since they last told
442 # us to "never ask again".
443 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % (
444 self._hook_type)
445 else:
446 prompt = ''
447
448 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
449 if sys.stdout.isatty():
450 prompt += ('Repo %s run the script:\n'
451 ' %s\n'
452 '\n'
453 'Do you want to allow this script to run '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700454 '(yes/yes-never-ask-again/NO)? ') % (self._GetMustVerb(),
455 self._script_fullpath)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530456 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900457 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800458
459 # User is doing a one-time approval.
460 if response in ('y', 'yes'):
461 return True
462 elif response == 'yes-never-ask-again':
463 hooks_config.SetString(git_approval_key, new_hash)
464 return True
465
466 # For anything else, we'll assume no approval.
467 if self._abort_if_user_denies:
468 raise HookError('You must allow the %s hook or use --no-verify.' %
469 self._hook_type)
470
471 return False
472
473 def _ExecuteHook(self, **kwargs):
474 """Actually execute the given hook.
475
476 This will run the hook's 'main' function in our python interpreter.
477
478 Args:
479 kwargs: Keyword arguments to pass to the hook. These are often specific
480 to the hook type. For instance, pre-upload hooks will contain
481 a project_list.
482 """
483 # Keep sys.path and CWD stashed away so that we can always restore them
484 # upon function exit.
485 orig_path = os.getcwd()
486 orig_syspath = sys.path
487
488 try:
489 # Always run hooks with CWD as topdir.
490 os.chdir(self._topdir)
491
492 # Put the hook dir as the first item of sys.path so hooks can do
493 # relative imports. We want to replace the repo dir as [0] so
494 # hooks can't import repo files.
495 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
496
497 # Exec, storing global context in the context dict. We catch exceptions
498 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500499 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800500 try:
Anthony King70f68902014-05-05 21:15:34 +0100501 exec(compile(open(self._script_fullpath).read(),
502 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800503 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700504 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
505 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800506
507 # Running the script should have defined a main() function.
508 if 'main' not in context:
509 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
510
Doug Anderson37282b42011-03-04 11:54:18 -0800511 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
512 # We don't actually want hooks to define their main with this argument--
513 # it's there to remind them that their hook should always take **kwargs.
514 # For instance, a pre-upload hook should be defined like:
515 # def main(project_list, **kwargs):
516 #
517 # This allows us to later expand the API without breaking old hooks.
518 kwargs = kwargs.copy()
519 kwargs['hook_should_take_kwargs'] = True
520
521 # Call the main function in the hook. If the hook should cause the
522 # build to fail, it will raise an Exception. We'll catch that convert
523 # to a HookError w/ just the failing traceback.
524 try:
525 context['main'](**kwargs)
526 except Exception:
527 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700528 'above.' % (traceback.format_exc(),
529 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800530 finally:
531 # Restore sys.path and CWD.
532 sys.path = orig_syspath
533 os.chdir(orig_path)
534
535 def Run(self, user_allows_all_hooks, **kwargs):
536 """Run the hook.
537
538 If the hook doesn't exist (because there is no hooks project or because
539 this particular hook is not enabled), this is a no-op.
540
541 Args:
542 user_allows_all_hooks: If True, we will never prompt about running the
543 hook--we'll just assume it's OK to run it.
544 kwargs: Keyword arguments to pass to the hook. These are often specific
545 to the hook type. For instance, pre-upload hooks will contain
546 a project_list.
547
548 Raises:
549 HookError: If there was a problem finding the hook or the user declined
550 to run a required hook (from _CheckForHookApproval).
551 """
552 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700553 if ((not self._hooks_project) or (self._hook_type not in
554 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800555 return
556
557 # Bail with a nice error if we can't find the hook.
558 if not os.path.isfile(self._script_fullpath):
559 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
560
561 # Make sure the user is OK with running the hook.
562 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
563 return
564
565 # Run the hook with the same version of python we're using.
566 self._ExecuteHook(**kwargs)
567
568
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700569class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600570 # These objects can be shared between several working trees.
571 shareable_files = ['description', 'info']
572 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
573 # These objects can only be used by a single working tree.
574 working_tree_files = ['config', 'packed-refs', 'shallow']
575 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700576
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700577 def __init__(self,
578 manifest,
579 name,
580 remote,
581 gitdir,
David James8d201162013-10-11 17:03:19 -0700582 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583 worktree,
584 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700585 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800586 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100587 rebase=True,
588 groups=None,
589 sync_c=False,
590 sync_s=False,
591 clone_depth=None,
592 upstream=None,
593 parent=None,
594 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900595 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700596 optimized_fetch=False,
597 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800598 """Init a Project object.
599
600 Args:
601 manifest: The XmlManifest object.
602 name: The `name` attribute of manifest.xml's project element.
603 remote: RemoteSpec object specifying its remote's properties.
604 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700605 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800606 worktree: Absolute path of git working tree.
607 relpath: Relative path of git working tree to repo's top directory.
608 revisionExpr: The `revision` attribute of manifest.xml's project element.
609 revisionId: git commit id for checking out.
610 rebase: The `rebase` attribute of manifest.xml's project element.
611 groups: The `groups` attribute of manifest.xml's project element.
612 sync_c: The `sync-c` attribute of manifest.xml's project element.
613 sync_s: The `sync-s` attribute of manifest.xml's project element.
614 upstream: The `upstream` attribute of manifest.xml's project element.
615 parent: The parent Project object.
616 is_derived: False if the project was explicitly defined in the manifest;
617 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400618 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900619 optimized_fetch: If True, when a project is set to a sha1 revision, only
620 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700621 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800622 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700623 self.manifest = manifest
624 self.name = name
625 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800626 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700627 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800628 if worktree:
629 self.worktree = worktree.replace('\\', '/')
630 else:
631 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700632 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700633 self.revisionExpr = revisionExpr
634
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700635 if revisionId is None \
636 and revisionExpr \
637 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700638 self.revisionId = revisionExpr
639 else:
640 self.revisionId = revisionId
641
Mike Pontillod3153822012-02-28 11:53:24 -0800642 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700643 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700644 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800645 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900646 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700647 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800648 self.parent = parent
649 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900650 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800651 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800652
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500655 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500656 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700657 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
658 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700659
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800660 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700661 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800662 else:
663 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700664 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700665 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700666 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400667 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700668 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669
Doug Anderson37282b42011-03-04 11:54:18 -0800670 # This will be filled in if a project is later identified to be the
671 # project containing repo hooks.
672 self.enabled_repo_hooks = []
673
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700674 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800675 def Derived(self):
676 return self.is_derived
677
678 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600680 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700681
682 @property
683 def CurrentBranch(self):
684 """Obtain the name of the currently checked out branch.
685 The branch name omits the 'refs/heads/' prefix.
686 None is returned if the project is on a detached HEAD.
687 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700688 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700689 if b.startswith(R_HEADS):
690 return b[len(R_HEADS):]
691 return None
692
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700693 def IsRebaseInProgress(self):
694 w = self.worktree
695 g = os.path.join(w, '.git')
696 return os.path.exists(os.path.join(g, 'rebase-apply')) \
697 or os.path.exists(os.path.join(g, 'rebase-merge')) \
698 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200699
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700700 def IsDirty(self, consider_untracked=True):
701 """Is the working directory modified in some way?
702 """
703 self.work_git.update_index('-q',
704 '--unmerged',
705 '--ignore-missing',
706 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900707 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708 return True
709 if self.work_git.DiffZ('diff-files'):
710 return True
711 if consider_untracked and self.work_git.LsOthers():
712 return True
713 return False
714
715 _userident_name = None
716 _userident_email = None
717
718 @property
719 def UserName(self):
720 """Obtain the user's personal name.
721 """
722 if self._userident_name is None:
723 self._LoadUserIdentity()
724 return self._userident_name
725
726 @property
727 def UserEmail(self):
728 """Obtain the user's email address. This is very likely
729 to be their Gerrit login.
730 """
731 if self._userident_email is None:
732 self._LoadUserIdentity()
733 return self._userident_email
734
735 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900736 u = self.bare_git.var('GIT_COMMITTER_IDENT')
737 m = re.compile("^(.*) <([^>]*)> ").match(u)
738 if m:
739 self._userident_name = m.group(1)
740 self._userident_email = m.group(2)
741 else:
742 self._userident_name = ''
743 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744
745 def GetRemote(self, name):
746 """Get the configuration for a single remote.
747 """
748 return self.config.GetRemote(name)
749
750 def GetBranch(self, name):
751 """Get the configuration for a single branch.
752 """
753 return self.config.GetBranch(name)
754
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700755 def GetBranches(self):
756 """Get all existing local branches.
757 """
758 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900759 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700760 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700761
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530762 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700763 if name.startswith(R_HEADS):
764 name = name[len(R_HEADS):]
765 b = self.GetBranch(name)
766 b.current = name == current
767 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900768 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700769 heads[name] = b
770
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530771 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700772 if name.startswith(R_PUB):
773 name = name[len(R_PUB):]
774 b = heads.get(name)
775 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900776 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700777
778 return heads
779
Colin Cross5acde752012-03-28 20:15:45 -0700780 def MatchesGroups(self, manifest_groups):
781 """Returns true if the manifest groups specified at init should cause
782 this project to be synced.
783 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700784 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700785
786 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700787 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700788 manifest_groups: "-group1,group2"
789 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500790
791 The special manifest group "default" will match any project that
792 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700793 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500794 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700795 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700796 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500797 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700798
Conley Owens971de8e2012-04-16 10:36:08 -0700799 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700800 for group in expanded_manifest_groups:
801 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700802 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700803 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700804 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700805
Conley Owens971de8e2012-04-16 10:36:08 -0700806 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700808# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700809 def UncommitedFiles(self, get_all=True):
810 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700812 Args:
813 get_all: a boolean, if True - get information about all different
814 uncommitted files. If False - return as soon as any kind of
815 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500816 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700817 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500818 self.work_git.update_index('-q',
819 '--unmerged',
820 '--ignore-missing',
821 '--refresh')
822 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700823 details.append("rebase in progress")
824 if not get_all:
825 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500826
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700827 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
828 if changes:
829 details.extend(changes)
830 if not get_all:
831 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500832
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700833 changes = self.work_git.DiffZ('diff-files').keys()
834 if changes:
835 details.extend(changes)
836 if not get_all:
837 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500838
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700839 changes = self.work_git.LsOthers()
840 if changes:
841 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500842
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700843 return details
844
845 def HasChanges(self):
846 """Returns true if there are uncommitted changes.
847 """
848 if self.UncommitedFiles(get_all=False):
849 return True
850 else:
851 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500852
Terence Haddock4655e812011-03-31 12:33:34 +0200853 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200855
856 Args:
857 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858 """
859 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700860 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200861 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700862 print(file=output_redir)
863 print('project %s/' % self.relpath, file=output_redir)
864 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865 return
866
867 self.work_git.update_index('-q',
868 '--unmerged',
869 '--ignore-missing',
870 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700871 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
873 df = self.work_git.DiffZ('diff-files')
874 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100875 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700876 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700877
878 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700879 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200880 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700881 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700882
883 branch = self.CurrentBranch
884 if branch is None:
885 out.nobranch('(*** NO BRANCH ***)')
886 else:
887 out.branch('branch %s', branch)
888 out.nl()
889
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700890 if rb:
891 out.important('prior sync failed; rebase still in progress')
892 out.nl()
893
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894 paths = list()
895 paths.extend(di.keys())
896 paths.extend(df.keys())
897 paths.extend(do)
898
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530899 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900900 try:
901 i = di[p]
902 except KeyError:
903 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900905 try:
906 f = df[p]
907 except KeyError:
908 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200909
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900910 if i:
911 i_status = i.status.upper()
912 else:
913 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900915 if f:
916 f_status = f.status.lower()
917 else:
918 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919
920 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800921 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700922 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923 else:
924 line = ' %s%s\t%s' % (i_status, f_status, p)
925
926 if i and not f:
927 out.added('%s', line)
928 elif (i and f) or (not i and f):
929 out.changed('%s', line)
930 elif not i and not f:
931 out.untracked('%s', line)
932 else:
933 out.write('%s', line)
934 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200935
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700936 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937
pelyad67872d2012-03-28 14:49:58 +0300938 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 """Prints the status of the repository to stdout.
940 """
941 out = DiffColoring(self.config)
942 cmd = ['diff']
943 if out.is_on:
944 cmd.append('--color')
945 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300946 if absolute_paths:
947 cmd.append('--src-prefix=a/%s/' % self.relpath)
948 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949 cmd.append('--')
950 p = GitCommand(self,
951 cmd,
Anthony King7bdac712014-07-16 12:56:40 +0100952 capture_stdout=True,
953 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954 has_diff = False
955 for line in p.process.stdout:
956 if not has_diff:
957 out.nl()
958 out.project('project %s/' % self.relpath)
959 out.nl()
960 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700961 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 p.Wait()
963
964
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700965# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966
David Pursehouse8a68ff92012-09-24 12:15:13 +0900967 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968 """Was the branch published (uploaded) for code review?
969 If so, returns the SHA-1 hash of the last published
970 state for the branch.
971 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700972 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900973 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700974 try:
975 return self.bare_git.rev_parse(key)
976 except GitError:
977 return None
978 else:
979 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900980 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700981 except KeyError:
982 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983
David Pursehouse8a68ff92012-09-24 12:15:13 +0900984 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700985 """Prunes any stale published refs.
986 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900987 if all_refs is None:
988 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 heads = set()
990 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530991 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700992 if name.startswith(R_HEADS):
993 heads.add(name)
994 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900995 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700996
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530997 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998 n = name[len(R_PUB):]
999 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001000 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001002 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003 """List any branches which can be uploaded for review.
1004 """
1005 heads = {}
1006 pubed = {}
1007
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301008 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001009 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001010 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001012 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013
1014 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301015 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001016 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001018 if selected_branch and branch != selected_branch:
1019 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001020
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001021 rb = self.GetUploadableBranch(branch)
1022 if rb:
1023 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001024 return ready
1025
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001026 def GetUploadableBranch(self, branch_name):
1027 """Get a single uploadable branch, or None.
1028 """
1029 branch = self.GetBranch(branch_name)
1030 base = branch.LocalMerge
1031 if branch.LocalMerge:
1032 rb = ReviewableBranch(self, branch, base)
1033 if rb.commits:
1034 return rb
1035 return None
1036
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001037 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001038 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001039 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001040 draft=False,
1041 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 """Uploads the named branch for code review.
1043 """
1044 if branch is None:
1045 branch = self.CurrentBranch
1046 if branch is None:
1047 raise GitError('not currently on a branch')
1048
1049 branch = self.GetBranch(branch)
1050 if not branch.LocalMerge:
1051 raise GitError('branch %s does not track a remote' % branch.name)
1052 if not branch.remote.review:
1053 raise GitError('remote %s has no review url' % branch.remote.name)
1054
Bryan Jacobsf609f912013-05-06 13:36:24 -04001055 if dest_branch is None:
1056 dest_branch = self.dest_branch
1057 if dest_branch is None:
1058 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059 if not dest_branch.startswith(R_HEADS):
1060 dest_branch = R_HEADS + dest_branch
1061
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001062 if not branch.remote.projectname:
1063 branch.remote.projectname = self.name
1064 branch.remote.Save()
1065
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001066 url = branch.remote.ReviewUrl(self.UserEmail)
1067 if url is None:
1068 raise UploadError('review not configured')
1069 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001070
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001071 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001072 rp = ['gerrit receive-pack']
1073 for e in people[0]:
1074 rp.append('--reviewer=%s' % sq(e))
1075 for e in people[1]:
1076 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001077 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001078
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001079 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001080
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001081 if dest_branch.startswith(R_HEADS):
1082 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001083
1084 upload_type = 'for'
1085 if draft:
1086 upload_type = 'drafts'
1087
1088 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1089 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001090 if auto_topic:
1091 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001092 if not url.startswith('ssh://'):
1093 rp = ['r=%s' % p for p in people[0]] + \
1094 ['cc=%s' % p for p in people[1]]
1095 if rp:
1096 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001097 cmd.append(ref_spec)
1098
Anthony King7bdac712014-07-16 12:56:40 +01001099 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001100 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101
1102 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1103 self.bare_git.UpdateRef(R_PUB + branch.name,
1104 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001105 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106
1107
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001108# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001109
Julien Campergue335f5ef2013-10-16 11:02:35 +02001110 def _ExtractArchive(self, tarpath, path=None):
1111 """Extract the given tar on its current location
1112
1113 Args:
1114 - tarpath: The path to the actual tar file
1115
1116 """
1117 try:
1118 with tarfile.open(tarpath, 'r') as tar:
1119 tar.extractall(path=path)
1120 return True
1121 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001122 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001123 return False
1124
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001125 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001126 quiet=False,
1127 is_new=None,
1128 current_branch_only=False,
1129 force_sync=False,
1130 clone_bundle=True,
1131 no_tags=False,
1132 archive=False,
1133 optimized_fetch=False,
1134 prune=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135 """Perform only the network IO portion of the sync process.
1136 Local working directory/branch state is not affected.
1137 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001138 if archive and not isinstance(self, MetaProject):
1139 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001140 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001141 return False
1142
1143 name = self.relpath.replace('\\', '/')
1144 name = name.replace('/', '_')
1145 tarpath = '%s.tar' % name
1146 topdir = self.manifest.topdir
1147
1148 try:
1149 self._FetchArchive(tarpath, cwd=topdir)
1150 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001151 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001152 return False
1153
1154 # From now on, we only need absolute tarpath
1155 tarpath = os.path.join(topdir, tarpath)
1156
1157 if not self._ExtractArchive(tarpath, path=topdir):
1158 return False
1159 try:
1160 os.remove(tarpath)
1161 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001162 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001163 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001164 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001165 if is_new is None:
1166 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001167 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001168 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001169 else:
1170 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001171 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001172
1173 if is_new:
1174 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1175 try:
1176 fd = open(alt, 'rb')
1177 try:
1178 alt_dir = fd.readline().rstrip()
1179 finally:
1180 fd.close()
1181 except IOError:
1182 alt_dir = None
1183 else:
1184 alt_dir = None
1185
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001186 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001187 and alt_dir is None \
1188 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001189 is_new = False
1190
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001191 if not current_branch_only:
1192 if self.sync_c:
1193 current_branch_only = True
1194 elif not self.manifest._loaded:
1195 # Manifest cannot check defaults until it syncs.
1196 current_branch_only = False
1197 elif self.manifest.default.sync_c:
1198 current_branch_only = True
1199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001200 need_to_fetch = not (optimized_fetch and
1201 (ID_RE.match(self.revisionExpr) and
1202 self._CheckForSha1()))
1203 if (need_to_fetch and
1204 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1205 current_branch_only=current_branch_only,
1206 no_tags=no_tags, prune=prune)):
Anthony King7bdac712014-07-16 12:56:40 +01001207 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001208
1209 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001210 self._InitMRef()
1211 else:
1212 self._InitMirrorHead()
1213 try:
1214 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1215 except OSError:
1216 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001217 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001218
1219 def PostRepoUpgrade(self):
1220 self._InitHooks()
1221
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001222 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001223 if self.manifest.isGitcClient:
1224 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001225 for copyfile in self.copyfiles:
1226 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001227 for linkfile in self.linkfiles:
1228 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001229
Julien Camperguedd654222014-01-09 16:21:37 +01001230 def GetCommitRevisionId(self):
1231 """Get revisionId of a commit.
1232
1233 Use this method instead of GetRevisionId to get the id of the commit rather
1234 than the id of the current git object (for example, a tag)
1235
1236 """
1237 if not self.revisionExpr.startswith(R_TAGS):
1238 return self.GetRevisionId(self._allrefs)
1239
1240 try:
1241 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1242 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001243 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1244 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001245
David Pursehouse8a68ff92012-09-24 12:15:13 +09001246 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001247 if self.revisionId:
1248 return self.revisionId
1249
1250 rem = self.GetRemote(self.remote.name)
1251 rev = rem.ToLocal(self.revisionExpr)
1252
David Pursehouse8a68ff92012-09-24 12:15:13 +09001253 if all_refs is not None and rev in all_refs:
1254 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001255
1256 try:
1257 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1258 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001259 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1260 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001261
Kevin Degiabaa7f32014-11-12 11:27:45 -07001262 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263 """Perform only the local IO portion of the sync process.
1264 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001265 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001266 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001267 all_refs = self.bare_ref.all
1268 self.CleanPublishedCache(all_refs)
1269 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001270
David Pursehouse1d947b32012-10-25 12:23:11 +09001271 def _doff():
1272 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001273 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001274
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001275 head = self.work_git.GetHead()
1276 if head.startswith(R_HEADS):
1277 branch = head[len(R_HEADS):]
1278 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001279 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001280 except KeyError:
1281 head = None
1282 else:
1283 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001284
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001285 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286 # Currently on a detached HEAD. The user is assumed to
1287 # not have any local modifications worth worrying about.
1288 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001289 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001290 syncbuf.fail(self, _PriorSyncFailedError())
1291 return
1292
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001293 if head == revid:
1294 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001295 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001296 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001297 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001298 # The copy/linkfile config may have changed.
1299 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001300 return
1301 else:
1302 lost = self._revlist(not_rev(revid), HEAD)
1303 if lost:
1304 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001305
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001307 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001308 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001309 syncbuf.fail(self, e)
1310 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001311 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001312 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001313
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001314 if head == revid:
1315 # No changes; don't do anything further.
1316 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001317 # The copy/linkfile config may have changed.
1318 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001319 return
1320
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001323 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001325 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001327 syncbuf.info(self,
1328 "leaving %s; does not track upstream",
1329 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001331 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001332 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001333 syncbuf.fail(self, e)
1334 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001335 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001336 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001338 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001339 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001340 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001341 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342 if not_merged:
1343 if upstream_gain:
1344 # The user has published this branch and some of those
1345 # commits are not yet merged upstream. We do not want
1346 # to rewrite the published commits so we punt.
1347 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001348 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001349 "branch %s is published (but not merged) and is now "
1350 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001351 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001352 elif pub == head:
1353 # All published commits are merged, and thus we are a
1354 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001355 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001356 syncbuf.later1(self, _doff)
1357 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001359 # Examine the local commits not in the remote. Find the
1360 # last one attributed to this user, if any.
1361 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001362 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001363 last_mine = None
1364 cnt_mine = 0
1365 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301366 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001367 if committer_email == self.UserEmail:
1368 last_mine = commit_id
1369 cnt_mine += 1
1370
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001371 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001372 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373
1374 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001375 syncbuf.fail(self, _DirtyError())
1376 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001377
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001378 # If the upstream switched on us, warn the user.
1379 #
1380 if branch.merge != self.revisionExpr:
1381 if branch.merge and self.revisionExpr:
1382 syncbuf.info(self,
1383 'manifest switched %s...%s',
1384 branch.merge,
1385 self.revisionExpr)
1386 elif branch.merge:
1387 syncbuf.info(self,
1388 'manifest no longer tracks %s',
1389 branch.merge)
1390
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001391 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001392 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001393 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001395 syncbuf.info(self,
1396 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001397 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001399 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001400 if not ID_RE.match(self.revisionExpr):
1401 # in case of manifest sync the revisionExpr might be a SHA1
1402 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001403 if not branch.merge.startswith('refs/'):
1404 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001405 branch.Save()
1406
Mike Pontillod3153822012-02-28 11:53:24 -08001407 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001408 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001409 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001410 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001411 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001412 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001413 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001414 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001415 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001416 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001417 syncbuf.fail(self, e)
1418 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001419 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001420 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001421
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001422 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001423 # dest should already be an absolute path, but src is project relative
1424 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001425 abssrc = os.path.join(self.worktree, src)
1426 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001427
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001428 def AddLinkFile(self, src, dest, absdest):
1429 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001430 # make src relative path to dest
1431 absdestdir = os.path.dirname(absdest)
1432 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001433 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001434
James W. Mills24c13082012-04-12 15:04:13 -05001435 def AddAnnotation(self, name, value, keep):
1436 self.annotations.append(_Annotation(name, value, keep))
1437
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001438 def DownloadPatchSet(self, change_id, patch_id):
1439 """Download a single patch set of a single change to FETCH_HEAD.
1440 """
1441 remote = self.GetRemote(self.remote.name)
1442
1443 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001444 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001445 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001446 if GitCommand(self, cmd, bare=True).Wait() != 0:
1447 return None
1448 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001449 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001450 change_id,
1451 patch_id,
1452 self.bare_git.rev_parse('FETCH_HEAD'))
1453
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001454
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001455# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001456
Simran Basib9a1b732015-08-20 12:19:28 -07001457 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001458 """Create a new branch off the manifest's revision.
1459 """
Simran Basib9a1b732015-08-20 12:19:28 -07001460 if not branch_merge:
1461 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001462 head = self.work_git.GetHead()
1463 if head == (R_HEADS + name):
1464 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001465
David Pursehouse8a68ff92012-09-24 12:15:13 +09001466 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001467 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001468 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001469 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001470 capture_stdout=True,
1471 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001472
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001473 branch = self.GetBranch(name)
1474 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001475 branch.merge = branch_merge
1476 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1477 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001478 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001479
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001480 if head.startswith(R_HEADS):
1481 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001482 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001483 except KeyError:
1484 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001485 if revid and head and revid == head:
1486 ref = os.path.join(self.gitdir, R_HEADS + name)
1487 try:
1488 os.makedirs(os.path.dirname(ref))
1489 except OSError:
1490 pass
1491 _lwrite(ref, '%s\n' % revid)
1492 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1493 'ref: %s%s\n' % (R_HEADS, name))
1494 branch.Save()
1495 return True
1496
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001497 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001498 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001499 capture_stdout=True,
1500 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001501 branch.Save()
1502 return True
1503 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001504
Wink Saville02d79452009-04-10 13:01:24 -07001505 def CheckoutBranch(self, name):
1506 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001507
1508 Args:
1509 name: The name of the branch to checkout.
1510
1511 Returns:
1512 True if the checkout succeeded; False if it didn't; None if the branch
1513 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001514 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001515 rev = R_HEADS + name
1516 head = self.work_git.GetHead()
1517 if head == rev:
1518 # Already on the branch
1519 #
1520 return True
Wink Saville02d79452009-04-10 13:01:24 -07001521
David Pursehouse8a68ff92012-09-24 12:15:13 +09001522 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001523 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001524 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001525 except KeyError:
1526 # Branch does not exist in this project
1527 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001528 return None
Wink Saville02d79452009-04-10 13:01:24 -07001529
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001530 if head.startswith(R_HEADS):
1531 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001532 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001533 except KeyError:
1534 head = None
1535
1536 if head == revid:
1537 # Same revision; just update HEAD to point to the new
1538 # target branch, but otherwise take no other action.
1539 #
1540 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1541 'ref: %s%s\n' % (R_HEADS, name))
1542 return True
1543
1544 return GitCommand(self,
1545 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001546 capture_stdout=True,
1547 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001548
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001549 def AbandonBranch(self, name):
1550 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001551
1552 Args:
1553 name: The name of the branch to abandon.
1554
1555 Returns:
1556 True if the abandon succeeded; False if it didn't; None if the branch
1557 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001558 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001559 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001560 all_refs = self.bare_ref.all
1561 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001562 # Doesn't exist
1563 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001564
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001565 head = self.work_git.GetHead()
1566 if head == rev:
1567 # We can't destroy the branch while we are sitting
1568 # on it. Switch to a detached HEAD.
1569 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001570 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001571
David Pursehouse8a68ff92012-09-24 12:15:13 +09001572 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001573 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001574 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1575 '%s\n' % revid)
1576 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001577 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001578
1579 return GitCommand(self,
1580 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001581 capture_stdout=True,
1582 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001583
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001584 def PruneHeads(self):
1585 """Prune any topic branches already merged into upstream.
1586 """
1587 cb = self.CurrentBranch
1588 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001589 left = self._allrefs
1590 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001591 if name.startswith(R_HEADS):
1592 name = name[len(R_HEADS):]
1593 if cb is None or name != cb:
1594 kill.append(name)
1595
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001596 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001597 if cb is not None \
1598 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001599 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001600 self.work_git.DetachHead(HEAD)
1601 kill.append(cb)
1602
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001603 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001604 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001605
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001606 try:
1607 self.bare_git.DetachHead(rev)
1608
1609 b = ['branch', '-d']
1610 b.extend(kill)
1611 b = GitCommand(self, b, bare=True,
1612 capture_stdout=True,
1613 capture_stderr=True)
1614 b.Wait()
1615 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001616 if ID_RE.match(old):
1617 self.bare_git.DetachHead(old)
1618 else:
1619 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001620 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001621
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001622 for branch in kill:
1623 if (R_HEADS + branch) not in left:
1624 self.CleanPublishedCache()
1625 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001626
1627 if cb and cb not in kill:
1628 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001629 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001630
1631 kept = []
1632 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001633 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001634 branch = self.GetBranch(branch)
1635 base = branch.LocalMerge
1636 if not base:
1637 base = rev
1638 kept.append(ReviewableBranch(self, branch, base))
1639 return kept
1640
1641
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001642# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001643
1644 def GetRegisteredSubprojects(self):
1645 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001646
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001647 def rec(subprojects):
1648 if not subprojects:
1649 return
1650 result.extend(subprojects)
1651 for p in subprojects:
1652 rec(p.subprojects)
1653 rec(self.subprojects)
1654 return result
1655
1656 def _GetSubmodules(self):
1657 # Unfortunately we cannot call `git submodule status --recursive` here
1658 # because the working tree might not exist yet, and it cannot be used
1659 # without a working tree in its current implementation.
1660
1661 def get_submodules(gitdir, rev):
1662 # Parse .gitmodules for submodule sub_paths and sub_urls
1663 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1664 if not sub_paths:
1665 return []
1666 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1667 # revision of submodule repository
1668 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1669 submodules = []
1670 for sub_path, sub_url in zip(sub_paths, sub_urls):
1671 try:
1672 sub_rev = sub_revs[sub_path]
1673 except KeyError:
1674 # Ignore non-exist submodules
1675 continue
1676 submodules.append((sub_rev, sub_path, sub_url))
1677 return submodules
1678
1679 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1680 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001681
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001682 def parse_gitmodules(gitdir, rev):
1683 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1684 try:
Anthony King7bdac712014-07-16 12:56:40 +01001685 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1686 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001687 except GitError:
1688 return [], []
1689 if p.Wait() != 0:
1690 return [], []
1691
1692 gitmodules_lines = []
1693 fd, temp_gitmodules_path = tempfile.mkstemp()
1694 try:
1695 os.write(fd, p.stdout)
1696 os.close(fd)
1697 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001698 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1699 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001700 if p.Wait() != 0:
1701 return [], []
1702 gitmodules_lines = p.stdout.split('\n')
1703 except GitError:
1704 return [], []
1705 finally:
1706 os.remove(temp_gitmodules_path)
1707
1708 names = set()
1709 paths = {}
1710 urls = {}
1711 for line in gitmodules_lines:
1712 if not line:
1713 continue
1714 m = re_path.match(line)
1715 if m:
1716 names.add(m.group(1))
1717 paths[m.group(1)] = m.group(2)
1718 continue
1719 m = re_url.match(line)
1720 if m:
1721 names.add(m.group(1))
1722 urls[m.group(1)] = m.group(2)
1723 continue
1724 names = sorted(names)
1725 return ([paths.get(name, '') for name in names],
1726 [urls.get(name, '') for name in names])
1727
1728 def git_ls_tree(gitdir, rev, paths):
1729 cmd = ['ls-tree', rev, '--']
1730 cmd.extend(paths)
1731 try:
Anthony King7bdac712014-07-16 12:56:40 +01001732 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1733 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001734 except GitError:
1735 return []
1736 if p.Wait() != 0:
1737 return []
1738 objects = {}
1739 for line in p.stdout.split('\n'):
1740 if not line.strip():
1741 continue
1742 object_rev, object_path = line.split()[2:4]
1743 objects[object_path] = object_rev
1744 return objects
1745
1746 try:
1747 rev = self.GetRevisionId()
1748 except GitError:
1749 return []
1750 return get_submodules(self.gitdir, rev)
1751
1752 def GetDerivedSubprojects(self):
1753 result = []
1754 if not self.Exists:
1755 # If git repo does not exist yet, querying its submodules will
1756 # mess up its states; so return here.
1757 return result
1758 for rev, path, url in self._GetSubmodules():
1759 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001760 relpath, worktree, gitdir, objdir = \
1761 self.manifest.GetSubprojectPaths(self, name, path)
1762 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001763 if project:
1764 result.extend(project.GetDerivedSubprojects())
1765 continue
David James8d201162013-10-11 17:03:19 -07001766
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001767 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001768 url=url,
1769 review=self.remote.review,
1770 revision=self.remote.revision)
1771 subproject = Project(manifest=self.manifest,
1772 name=name,
1773 remote=remote,
1774 gitdir=gitdir,
1775 objdir=objdir,
1776 worktree=worktree,
1777 relpath=relpath,
1778 revisionExpr=self.revisionExpr,
1779 revisionId=rev,
1780 rebase=self.rebase,
1781 groups=self.groups,
1782 sync_c=self.sync_c,
1783 sync_s=self.sync_s,
1784 parent=self,
1785 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001786 result.append(subproject)
1787 result.extend(subproject.GetDerivedSubprojects())
1788 return result
1789
1790
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001791# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001792 def _CheckForSha1(self):
1793 try:
1794 # if revision (sha or tag) is not present then following function
1795 # throws an error.
1796 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1797 return True
1798 except GitError:
1799 # There is no such persistent revision. We have to fetch it.
1800 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001801
Julien Campergue335f5ef2013-10-16 11:02:35 +02001802 def _FetchArchive(self, tarpath, cwd=None):
1803 cmd = ['archive', '-v', '-o', tarpath]
1804 cmd.append('--remote=%s' % self.remote.url)
1805 cmd.append('--prefix=%s/' % self.relpath)
1806 cmd.append(self.revisionExpr)
1807
1808 command = GitCommand(self, cmd, cwd=cwd,
1809 capture_stdout=True,
1810 capture_stderr=True)
1811
1812 if command.Wait() != 0:
1813 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1814
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001815 def _RemoteFetch(self, name=None,
1816 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001817 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001818 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001819 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001820 no_tags=False,
1821 prune=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001822
1823 is_sha1 = False
1824 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001825 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001826
David Pursehouse9bc422f2014-04-15 10:28:56 +09001827 # The depth should not be used when fetching to a mirror because
1828 # it will result in a shallow repository that cannot be cloned or
1829 # fetched from.
1830 if not self.manifest.IsMirror:
1831 if self.clone_depth:
1832 depth = self.clone_depth
1833 else:
1834 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001835 # The repo project should never be synced with partial depth
1836 if self.relpath == '.repo/repo':
1837 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001838
Shawn Pearce69e04d82014-01-29 12:48:54 -08001839 if depth:
1840 current_branch_only = True
1841
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001842 if ID_RE.match(self.revisionExpr) is not None:
1843 is_sha1 = True
1844
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001845 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001846 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001847 # this is a tag and its sha1 value should never change
1848 tag_name = self.revisionExpr[len(R_TAGS):]
1849
1850 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001851 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001852 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001853 if is_sha1 and not depth:
1854 # When syncing a specific commit and --depth is not set:
1855 # * if upstream is explicitly specified and is not a sha1, fetch only
1856 # upstream as users expect only upstream to be fetch.
1857 # Note: The commit might not be in upstream in which case the sync
1858 # will fail.
1859 # * otherwise, fetch all branches to make sure we end up with the
1860 # specific commit.
1861 current_branch_only = self.upstream and not ID_RE.match(self.upstream)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001862
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001863 if not name:
1864 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001865
1866 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001867 remote = self.GetRemote(name)
1868 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001869 ssh_proxy = True
1870
Shawn O. Pearce88443382010-10-08 10:02:09 +02001871 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001872 if alt_dir and 'objects' == os.path.basename(alt_dir):
1873 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001874 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1875 remote = self.GetRemote(name)
1876
David Pursehouse8a68ff92012-09-24 12:15:13 +09001877 all_refs = self.bare_ref.all
1878 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02001879 tmp = set()
1880
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301881 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001882 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001883 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001884 all_refs[r] = ref_id
1885 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001886 continue
1887
David Pursehouse8a68ff92012-09-24 12:15:13 +09001888 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001889 continue
1890
David Pursehouse8a68ff92012-09-24 12:15:13 +09001891 r = 'refs/_alt/%s' % ref_id
1892 all_refs[r] = ref_id
1893 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001894 tmp.add(r)
1895
Shawn O. Pearce88443382010-10-08 10:02:09 +02001896 tmp_packed = ''
1897 old_packed = ''
1898
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301899 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001900 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001901 tmp_packed += line
1902 if r not in tmp:
1903 old_packed += line
1904
1905 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001906 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001907 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02001908
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001909 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07001910
Conley Owensf97e8382015-01-21 11:12:46 -08001911 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07001912 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07001913 else:
1914 # If this repo has shallow objects, then we don't know which refs have
1915 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
1916 # do this with projects that don't have shallow objects, since it is less
1917 # efficient.
1918 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
1919 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07001920
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001921 if quiet:
1922 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001923 if not self.worktree:
1924 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001925 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001926
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07001927 # If using depth then we should not get all the tags since they may
1928 # be outside of the depth.
1929 if no_tags or depth:
1930 cmd.append('--no-tags')
1931 else:
1932 cmd.append('--tags')
1933
David Pursehouse74cfd272015-10-14 10:50:15 +09001934 if prune:
1935 cmd.append('--prune')
1936
Conley Owens80b87fe2014-05-09 17:13:44 -07001937 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07001938 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001939 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07001940 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001941 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07001942 spec.append('tag')
1943 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06001944
David Pursehouse403b64e2015-04-27 10:41:33 +09001945 if not self.manifest.IsMirror:
1946 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06001947 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09001948 # Shallow checkout of a specific commit, fetch from that commit and not
1949 # the heads only as the commit might be deeper in the history.
1950 spec.append(branch)
1951 else:
1952 if is_sha1:
1953 branch = self.upstream
1954 if branch is not None and branch.strip():
1955 if not branch.startswith('refs/'):
1956 branch = R_HEADS + branch
1957 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07001958 cmd.extend(spec)
1959
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001960 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09001961 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07001962 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08001963 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07001964 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001965 ok = True
1966 break
John L. Villalovos126e2982015-01-29 21:58:12 -08001967 # If needed, run the 'git remote prune' the first time through the loop
1968 elif (not _i and
1969 "error:" in gitcmd.stderr and
1970 "git remote prune" in gitcmd.stderr):
1971 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07001972 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08001973 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08001974 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08001975 break
1976 continue
Brian Harring14a66742012-09-28 20:21:57 -07001977 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001978 # Exit code 128 means "couldn't find the ref you asked for"; if we're
1979 # in sha1 mode, we just tried sync'ing from the upstream field; it
1980 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07001981 break
Colin Crossc4b301f2015-05-13 00:10:02 -07001982 elif ret < 0:
1983 # Git died with a signal, exit immediately
1984 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001985 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02001986
1987 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001988 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02001989 if old_packed != '':
1990 _lwrite(packed_refs, old_packed)
1991 else:
1992 os.remove(packed_refs)
1993 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07001994
1995 if is_sha1 and current_branch_only and self.upstream:
1996 # We just synced the upstream given branch; verify we
1997 # got what we wanted, else trigger a second run of all
1998 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05001999 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06002000 if not depth:
2001 # Avoid infinite recursion when depth is True (since depth implies
2002 # current_branch_only)
2003 return self._RemoteFetch(name=name, current_branch_only=False,
2004 initial=False, quiet=quiet, alt_dir=alt_dir)
2005 if self.clone_depth:
2006 self.clone_depth = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002007 return self._RemoteFetch(name=name,
2008 current_branch_only=current_branch_only,
Kevin Degi679bac42015-06-22 15:31:26 -06002009 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07002010
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002011 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002012
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002013 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002014 if initial and \
2015 (self.manifest.manifestProject.config.GetString('repo.depth') or
2016 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002017 return False
2018
2019 remote = self.GetRemote(self.remote.name)
2020 bundle_url = remote.url + '/clone.bundle'
2021 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002022 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2023 'persistent-http',
2024 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002025 return False
2026
2027 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2028 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2029
2030 exist_dst = os.path.exists(bundle_dst)
2031 exist_tmp = os.path.exists(bundle_tmp)
2032
2033 if not initial and not exist_dst and not exist_tmp:
2034 return False
2035
2036 if not exist_dst:
2037 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2038 if not exist_dst:
2039 return False
2040
2041 cmd = ['fetch']
2042 if quiet:
2043 cmd.append('--quiet')
2044 if not self.worktree:
2045 cmd.append('--update-head-ok')
2046 cmd.append(bundle_dst)
2047 for f in remote.fetch:
2048 cmd.append(str(f))
2049 cmd.append('refs/tags/*:refs/tags/*')
2050
2051 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002052 if os.path.exists(bundle_dst):
2053 os.remove(bundle_dst)
2054 if os.path.exists(bundle_tmp):
2055 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002056 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002057
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002058 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002059 if os.path.exists(dstPath):
2060 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002061
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002062 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002063 if quiet:
2064 cmd += ['--silent']
2065 if os.path.exists(tmpPath):
2066 size = os.stat(tmpPath).st_size
2067 if size >= 1024:
2068 cmd += ['--continue-at', '%d' % (size,)]
2069 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002070 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002071 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2072 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002073 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002074 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002075 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002076 if srcUrl.startswith('persistent-'):
2077 srcUrl = srcUrl[len('persistent-'):]
2078 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002079
Dave Borowitz137d0132015-01-02 11:12:54 -08002080 if IsTrace():
2081 Trace('%s', ' '.join(cmd))
2082 try:
2083 proc = subprocess.Popen(cmd)
2084 except OSError:
2085 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002086
Dave Borowitz137d0132015-01-02 11:12:54 -08002087 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002088
Dave Borowitz137d0132015-01-02 11:12:54 -08002089 if curlret == 22:
2090 # From curl man page:
2091 # 22: HTTP page not retrieved. The requested url was not found or
2092 # returned another error with the HTTP error code being 400 or above.
2093 # This return code only appears if -f, --fail is used.
2094 if not quiet:
2095 print("Server does not provide clone.bundle; ignoring.",
2096 file=sys.stderr)
2097 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002098
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002099 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002100 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002101 os.rename(tmpPath, dstPath)
2102 return True
2103 else:
2104 os.remove(tmpPath)
2105 return False
2106 else:
2107 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002108
Kris Giesingc8d882a2014-12-23 13:02:32 -08002109 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002110 try:
2111 with open(path) as f:
2112 if f.read(16) == '# v2 git bundle\n':
2113 return True
2114 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002115 if not quiet:
2116 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002117 return False
2118 except OSError:
2119 return False
2120
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002121 def _Checkout(self, rev, quiet=False):
2122 cmd = ['checkout']
2123 if quiet:
2124 cmd.append('-q')
2125 cmd.append(rev)
2126 cmd.append('--')
2127 if GitCommand(self, cmd).Wait() != 0:
2128 if self._allrefs:
2129 raise GitError('%s checkout %s ' % (self.name, rev))
2130
Anthony King7bdac712014-07-16 12:56:40 +01002131 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002132 cmd = ['cherry-pick']
2133 cmd.append(rev)
2134 cmd.append('--')
2135 if GitCommand(self, cmd).Wait() != 0:
2136 if self._allrefs:
2137 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2138
Anthony King7bdac712014-07-16 12:56:40 +01002139 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002140 cmd = ['revert']
2141 cmd.append('--no-edit')
2142 cmd.append(rev)
2143 cmd.append('--')
2144 if GitCommand(self, cmd).Wait() != 0:
2145 if self._allrefs:
2146 raise GitError('%s revert %s ' % (self.name, rev))
2147
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002148 def _ResetHard(self, rev, quiet=True):
2149 cmd = ['reset', '--hard']
2150 if quiet:
2151 cmd.append('-q')
2152 cmd.append(rev)
2153 if GitCommand(self, cmd).Wait() != 0:
2154 raise GitError('%s reset --hard %s ' % (self.name, rev))
2155
Anthony King7bdac712014-07-16 12:56:40 +01002156 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002157 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002158 if onto is not None:
2159 cmd.extend(['--onto', onto])
2160 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002161 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002162 raise GitError('%s rebase %s ' % (self.name, upstream))
2163
Pierre Tardy3d125942012-05-04 12:18:12 +02002164 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002165 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002166 if ffonly:
2167 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002168 if GitCommand(self, cmd).Wait() != 0:
2169 raise GitError('%s merge %s ' % (self.name, head))
2170
Kevin Degiabaa7f32014-11-12 11:27:45 -07002171 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002172 init_git_dir = not os.path.exists(self.gitdir)
2173 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002174 try:
2175 # Initialize the bare repository, which contains all of the objects.
2176 if init_obj_dir:
2177 os.makedirs(self.objdir)
2178 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002179
Kevin Degib1a07b82015-07-27 13:33:43 -06002180 # If we have a separate directory to hold refs, initialize it as well.
2181 if self.objdir != self.gitdir:
2182 if init_git_dir:
2183 os.makedirs(self.gitdir)
2184
2185 if init_obj_dir or init_git_dir:
2186 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2187 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002188 try:
2189 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2190 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002191 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002192 print("Retrying clone after deleting %s" %
2193 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002194 try:
2195 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002196 if self.worktree and os.path.exists(os.path.realpath
2197 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002198 shutil.rmtree(os.path.realpath(self.worktree))
2199 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2200 except:
2201 raise e
2202 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002203
Kevin Degi384b3c52014-10-16 16:02:58 -06002204 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002205 mp = self.manifest.manifestProject
2206 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002207
Kevin Degib1a07b82015-07-27 13:33:43 -06002208 if ref_dir or mirror_git:
2209 if not mirror_git:
2210 mirror_git = os.path.join(ref_dir, self.name + '.git')
2211 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2212 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002213
Kevin Degib1a07b82015-07-27 13:33:43 -06002214 if os.path.exists(mirror_git):
2215 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002216
Kevin Degib1a07b82015-07-27 13:33:43 -06002217 elif os.path.exists(repo_git):
2218 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002219
Kevin Degib1a07b82015-07-27 13:33:43 -06002220 else:
2221 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002222
Kevin Degib1a07b82015-07-27 13:33:43 -06002223 if ref_dir:
2224 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2225 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002226
Kevin Degib1a07b82015-07-27 13:33:43 -06002227 self._UpdateHooks()
2228
2229 m = self.manifest.manifestProject.config
2230 for key in ['user.name', 'user.email']:
2231 if m.Has(key, include_defaults=False):
2232 self.config.SetString(key, m.GetString(key))
2233 if self.manifest.IsMirror:
2234 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002235 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002236 self.config.SetString('core.bare', None)
2237 except Exception:
2238 if init_obj_dir and os.path.exists(self.objdir):
2239 shutil.rmtree(self.objdir)
2240 if init_git_dir and os.path.exists(self.gitdir):
2241 shutil.rmtree(self.gitdir)
2242 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002243
Jimmie Westera0444582012-10-24 13:44:42 +02002244 def _UpdateHooks(self):
2245 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002246 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002247
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002248 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002249 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002250 if not os.path.exists(hooks):
2251 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002252 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002253 name = os.path.basename(stock_hook)
2254
Victor Boivie65e0f352011-04-18 11:23:29 +02002255 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002256 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002257 # Don't install a Gerrit Code Review hook if this
2258 # project does not appear to use it for reviews.
2259 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002260 # Since the manifest project is one of those, but also
2261 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002262 continue
2263
2264 dst = os.path.join(hooks, name)
2265 if os.path.islink(dst):
2266 continue
2267 if os.path.exists(dst):
2268 if filecmp.cmp(stock_hook, dst, shallow=False):
2269 os.remove(dst)
2270 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002271 _warn("%s: Not replacing locally modified %s hook",
2272 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002273 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002274 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002275 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002276 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002277 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002278 raise GitError('filesystem must support symlinks')
2279 else:
2280 raise
2281
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002282 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002283 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002284 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002285 remote.url = self.remote.url
2286 remote.review = self.remote.review
2287 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002288
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002289 if self.worktree:
2290 remote.ResetFetch(mirror=False)
2291 else:
2292 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002293 remote.Save()
2294
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002295 def _InitMRef(self):
2296 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002297 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002298
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002299 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002300 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002301
2302 def _InitAnyMRef(self, ref):
2303 cur = self.bare_ref.symref(ref)
2304
2305 if self.revisionId:
2306 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2307 msg = 'manifest set to %s' % self.revisionId
2308 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002309 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002310 else:
2311 remote = self.GetRemote(self.remote.name)
2312 dst = remote.ToLocal(self.revisionExpr)
2313 if cur != dst:
2314 msg = 'manifest set to %s' % self.revisionExpr
2315 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002316
Kevin Degi384b3c52014-10-16 16:02:58 -06002317 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002318 symlink_files = self.shareable_files[:]
2319 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002320 if share_refs:
2321 symlink_files += self.working_tree_files
2322 symlink_dirs += self.working_tree_dirs
2323 to_symlink = symlink_files + symlink_dirs
2324 for name in set(to_symlink):
2325 dst = os.path.realpath(os.path.join(destdir, name))
2326 if os.path.lexists(dst):
2327 src = os.path.realpath(os.path.join(srcdir, name))
2328 # Fail if the links are pointing to the wrong place
2329 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002330 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002331 'work tree. If you\'re comfortable with the '
2332 'possibility of losing the work tree\'s git metadata,'
2333 ' use `repo sync --force-sync {0}` to '
2334 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002335
David James8d201162013-10-11 17:03:19 -07002336 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2337 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2338
2339 Args:
2340 gitdir: The bare git repository. Must already be initialized.
2341 dotgit: The repository you would like to initialize.
2342 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2343 Only one work tree can store refs under a given |gitdir|.
2344 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2345 This saves you the effort of initializing |dotgit| yourself.
2346 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002347 symlink_files = self.shareable_files[:]
2348 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002349 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002350 symlink_files += self.working_tree_files
2351 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002352 to_symlink = symlink_files + symlink_dirs
2353
2354 to_copy = []
2355 if copy_all:
2356 to_copy = os.listdir(gitdir)
2357
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002358 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002359 for name in set(to_copy).union(to_symlink):
2360 try:
2361 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002362 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002363
Kevin Degi384b3c52014-10-16 16:02:58 -06002364 if os.path.lexists(dst):
2365 continue
David James8d201162013-10-11 17:03:19 -07002366
2367 # If the source dir doesn't exist, create an empty dir.
2368 if name in symlink_dirs and not os.path.lexists(src):
2369 os.makedirs(src)
2370
Conley Owens80b87fe2014-05-09 17:13:44 -07002371 # If the source file doesn't exist, ensure the destination
2372 # file doesn't either.
2373 if name in symlink_files and not os.path.lexists(src):
2374 try:
2375 os.remove(dst)
2376 except OSError:
2377 pass
2378
David James8d201162013-10-11 17:03:19 -07002379 if name in to_symlink:
2380 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2381 elif copy_all and not os.path.islink(dst):
2382 if os.path.isdir(src):
2383 shutil.copytree(src, dst)
2384 elif os.path.isfile(src):
2385 shutil.copy(src, dst)
2386 except OSError as e:
2387 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002388 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002389 else:
2390 raise
2391
Kevin Degiabaa7f32014-11-12 11:27:45 -07002392 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002393 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002394 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002395 try:
2396 if init_dotgit:
2397 os.makedirs(dotgit)
2398 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2399 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002400
Kevin Degiabaa7f32014-11-12 11:27:45 -07002401 try:
2402 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2403 except GitError as e:
2404 if force_sync:
2405 try:
2406 shutil.rmtree(dotgit)
2407 return self._InitWorkTree(force_sync=False)
2408 except:
2409 raise e
2410 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002411
Kevin Degib1a07b82015-07-27 13:33:43 -06002412 if init_dotgit:
2413 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002414
Kevin Degib1a07b82015-07-27 13:33:43 -06002415 cmd = ['read-tree', '--reset', '-u']
2416 cmd.append('-v')
2417 cmd.append(HEAD)
2418 if GitCommand(self, cmd).Wait() != 0:
2419 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002420
Kevin Degib1a07b82015-07-27 13:33:43 -06002421 self._CopyAndLinkFiles()
2422 except Exception:
2423 if init_dotgit:
2424 shutil.rmtree(dotgit)
2425 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002426
2427 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002428 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002429
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002430 def _revlist(self, *args, **kw):
2431 a = []
2432 a.extend(args)
2433 a.append('--')
2434 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002435
2436 @property
2437 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002438 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002439
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002440 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002441 """Get logs between two revisions of this project."""
2442 comp = '..'
2443 if rev1:
2444 revs = [rev1]
2445 if rev2:
2446 revs.extend([comp, rev2])
2447 cmd = ['log', ''.join(revs)]
2448 out = DiffColoring(self.config)
2449 if out.is_on and color:
2450 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002451 if pretty_format is not None:
2452 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002453 if oneline:
2454 cmd.append('--oneline')
2455
2456 try:
2457 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2458 if log.Wait() == 0:
2459 return log.stdout
2460 except GitError:
2461 # worktree may not exist if groups changed for example. In that case,
2462 # try in gitdir instead.
2463 if not os.path.exists(self.worktree):
2464 return self.bare_git.log(*cmd[1:])
2465 else:
2466 raise
2467 return None
2468
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002469 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2470 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002471 """Get the list of logs from this revision to given revisionId"""
2472 logs = {}
2473 selfId = self.GetRevisionId(self._allrefs)
2474 toId = toProject.GetRevisionId(toProject._allrefs)
2475
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002476 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2477 pretty_format=pretty_format)
2478 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2479 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002480 return logs
2481
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002482 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002483
David James8d201162013-10-11 17:03:19 -07002484 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002485 self._project = project
2486 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002487 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002488
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002489 def LsOthers(self):
2490 p = GitCommand(self._project,
2491 ['ls-files',
2492 '-z',
2493 '--others',
2494 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002495 bare=False,
David James8d201162013-10-11 17:03:19 -07002496 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002497 capture_stdout=True,
2498 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002499 if p.Wait() == 0:
2500 out = p.stdout
2501 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002502 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002503 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002504 return []
2505
2506 def DiffZ(self, name, *args):
2507 cmd = [name]
2508 cmd.append('-z')
2509 cmd.extend(args)
2510 p = GitCommand(self._project,
2511 cmd,
David James8d201162013-10-11 17:03:19 -07002512 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002513 bare=False,
2514 capture_stdout=True,
2515 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002516 try:
2517 out = p.process.stdout.read()
2518 r = {}
2519 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002520 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002521 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002522 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002523 info = next(out)
2524 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002525 except StopIteration:
2526 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002527
2528 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002529
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002530 def __init__(self, path, omode, nmode, oid, nid, state):
2531 self.path = path
2532 self.src_path = None
2533 self.old_mode = omode
2534 self.new_mode = nmode
2535 self.old_id = oid
2536 self.new_id = nid
2537
2538 if len(state) == 1:
2539 self.status = state
2540 self.level = None
2541 else:
2542 self.status = state[:1]
2543 self.level = state[1:]
2544 while self.level.startswith('0'):
2545 self.level = self.level[1:]
2546
2547 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002548 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002549 if info.status in ('R', 'C'):
2550 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002551 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002552 r[info.path] = info
2553 return r
2554 finally:
2555 p.Wait()
2556
2557 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002558 if self._bare:
2559 path = os.path.join(self._project.gitdir, HEAD)
2560 else:
2561 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002562 try:
2563 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002564 except IOError as e:
2565 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002566 try:
2567 line = fd.read()
2568 finally:
2569 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302570 try:
2571 line = line.decode()
2572 except AttributeError:
2573 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002574 if line.startswith('ref: '):
2575 return line[5:-1]
2576 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002577
2578 def SetHead(self, ref, message=None):
2579 cmdv = []
2580 if message is not None:
2581 cmdv.extend(['-m', message])
2582 cmdv.append(HEAD)
2583 cmdv.append(ref)
2584 self.symbolic_ref(*cmdv)
2585
2586 def DetachHead(self, new, message=None):
2587 cmdv = ['--no-deref']
2588 if message is not None:
2589 cmdv.extend(['-m', message])
2590 cmdv.append(HEAD)
2591 cmdv.append(new)
2592 self.update_ref(*cmdv)
2593
2594 def UpdateRef(self, name, new, old=None,
2595 message=None,
2596 detach=False):
2597 cmdv = []
2598 if message is not None:
2599 cmdv.extend(['-m', message])
2600 if detach:
2601 cmdv.append('--no-deref')
2602 cmdv.append(name)
2603 cmdv.append(new)
2604 if old is not None:
2605 cmdv.append(old)
2606 self.update_ref(*cmdv)
2607
2608 def DeleteRef(self, name, old=None):
2609 if not old:
2610 old = self.rev_parse(name)
2611 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002612 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002613
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002614 def rev_list(self, *args, **kw):
2615 if 'format' in kw:
2616 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2617 else:
2618 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002619 cmdv.extend(args)
2620 p = GitCommand(self._project,
2621 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002622 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002623 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002624 capture_stdout=True,
2625 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002626 r = []
2627 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002628 if line[-1] == '\n':
2629 line = line[:-1]
2630 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002631 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002632 raise GitError('%s rev-list %s: %s' %
2633 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002634 return r
2635
2636 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002637 """Allow arbitrary git commands using pythonic syntax.
2638
2639 This allows you to do things like:
2640 git_obj.rev_parse('HEAD')
2641
2642 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2643 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002644 Any other positional arguments will be passed to the git command, and the
2645 following keyword arguments are supported:
2646 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002647
2648 Args:
2649 name: The name of the git command to call. Any '_' characters will
2650 be replaced with '-'.
2651
2652 Returns:
2653 A callable object that will try to call git with the named command.
2654 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002655 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002656
Dave Borowitz091f8932012-10-23 17:01:04 -07002657 def runner(*args, **kwargs):
2658 cmdv = []
2659 config = kwargs.pop('config', None)
2660 for k in kwargs:
2661 raise TypeError('%s() got an unexpected keyword argument %r'
2662 % (name, k))
2663 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002664 if not git_require((1, 7, 2)):
2665 raise ValueError('cannot set config on command line for %s()'
2666 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302667 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002668 cmdv.append('-c')
2669 cmdv.append('%s=%s' % (k, v))
2670 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002671 cmdv.extend(args)
2672 p = GitCommand(self._project,
2673 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002674 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002675 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002676 capture_stdout=True,
2677 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002678 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002679 raise GitError('%s %s: %s' %
2680 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002681 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302682 try:
Conley Owensedd01512013-09-26 12:59:58 -07002683 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302684 except AttributeError:
2685 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002686 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2687 return r[:-1]
2688 return r
2689 return runner
2690
2691
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002692class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002693
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002694 def __str__(self):
2695 return 'prior sync failed; rebase still in progress'
2696
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002697
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002698class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002699
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002700 def __str__(self):
2701 return 'contains uncommitted changes'
2702
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002703
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002704class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002705
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002706 def __init__(self, project, text):
2707 self.project = project
2708 self.text = text
2709
2710 def Print(self, syncbuf):
2711 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2712 syncbuf.out.nl()
2713
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002714
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002715class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002716
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002717 def __init__(self, project, why):
2718 self.project = project
2719 self.why = why
2720
2721 def Print(self, syncbuf):
2722 syncbuf.out.fail('error: %s/: %s',
2723 self.project.relpath,
2724 str(self.why))
2725 syncbuf.out.nl()
2726
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002727
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002728class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002729
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002730 def __init__(self, project, action):
2731 self.project = project
2732 self.action = action
2733
2734 def Run(self, syncbuf):
2735 out = syncbuf.out
2736 out.project('project %s/', self.project.relpath)
2737 out.nl()
2738 try:
2739 self.action()
2740 out.nl()
2741 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002742 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002743 out.nl()
2744 return False
2745
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002746
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002747class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002748
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002749 def __init__(self, config):
2750 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002751 self.project = self.printer('header', attr='bold')
2752 self.info = self.printer('info')
2753 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002754
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002755
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002756class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002757
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002758 def __init__(self, config, detach_head=False):
2759 self._messages = []
2760 self._failures = []
2761 self._later_queue1 = []
2762 self._later_queue2 = []
2763
2764 self.out = _SyncColoring(config)
2765 self.out.redirect(sys.stderr)
2766
2767 self.detach_head = detach_head
2768 self.clean = True
2769
2770 def info(self, project, fmt, *args):
2771 self._messages.append(_InfoMessage(project, fmt % args))
2772
2773 def fail(self, project, err=None):
2774 self._failures.append(_Failure(project, err))
2775 self.clean = False
2776
2777 def later1(self, project, what):
2778 self._later_queue1.append(_Later(project, what))
2779
2780 def later2(self, project, what):
2781 self._later_queue2.append(_Later(project, what))
2782
2783 def Finish(self):
2784 self._PrintMessages()
2785 self._RunLater()
2786 self._PrintMessages()
2787 return self.clean
2788
2789 def _RunLater(self):
2790 for q in ['_later_queue1', '_later_queue2']:
2791 if not self._RunQueue(q):
2792 return
2793
2794 def _RunQueue(self, queue):
2795 for m in getattr(self, queue):
2796 if not m.Run(self):
2797 self.clean = False
2798 return False
2799 setattr(self, queue, [])
2800 return True
2801
2802 def _PrintMessages(self):
2803 for m in self._messages:
2804 m.Print(self)
2805 for m in self._failures:
2806 m.Print(self)
2807
2808 self._messages = []
2809 self._failures = []
2810
2811
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002812class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002813
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002814 """A special project housed under .repo.
2815 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002816
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002817 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002818 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002819 manifest=manifest,
2820 name=name,
2821 gitdir=gitdir,
2822 objdir=gitdir,
2823 worktree=worktree,
2824 remote=RemoteSpec('origin'),
2825 relpath='.repo/%s' % name,
2826 revisionExpr='refs/heads/master',
2827 revisionId=None,
2828 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002829
2830 def PreSync(self):
2831 if self.Exists:
2832 cb = self.CurrentBranch
2833 if cb:
2834 base = self.GetBranch(cb).merge
2835 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002836 self.revisionExpr = base
2837 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002838
Anthony King7bdac712014-07-16 12:56:40 +01002839 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002840 """ Prepare MetaProject for manifest branch switch
2841 """
2842
2843 # detach and delete manifest branch, allowing a new
2844 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002845 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002846 self.Sync_LocalHalf(syncbuf)
2847 syncbuf.Finish()
2848
2849 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002850 ['update-ref', '-d', 'refs/heads/default'],
2851 capture_stdout=True,
2852 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002853
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002854 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07002855 def LastFetch(self):
2856 try:
2857 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
2858 return os.path.getmtime(fh)
2859 except OSError:
2860 return 0
2861
2862 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002863 def HasChanges(self):
2864 """Has the remote received new commits not yet checked out?
2865 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002866 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002867 return False
2868
David Pursehouse8a68ff92012-09-24 12:15:13 +09002869 all_refs = self.bare_ref.all
2870 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002871 head = self.work_git.GetHead()
2872 if head.startswith(R_HEADS):
2873 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002874 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07002875 except KeyError:
2876 head = None
2877
2878 if revid == head:
2879 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002880 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002881 return True
2882 return False