blob: c654b7d3e93e10d66f6403d1e2367ecc2dd3f770 [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, \
Ningning Xiac2fbc782016-08-22 14:24:39 -070034 ID_RE, RefSpec
Kevin Degiabaa7f32014-11-12 11:27:45 -070035from error import GitError, HookError, UploadError, DownloadError
Ningning Xiac2fbc782016-08-22 14:24:39 -070036from error import CacheApplyError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080037from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080038from error import NoManifestException
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070039from trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
Shawn O. Pearced237b692009-04-17 18:49:50 -070041from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042
David Pursehouse59bbb582013-05-17 10:49:33 +090043from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040044if is_python3():
45 import urllib.parse
46else:
47 import imp
48 import urlparse
49 urllib = imp.new_module('urllib')
50 urllib.parse = urlparse
David Pursehouse59bbb582013-05-17 10:49:33 +090051 # pylint:disable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053052 input = raw_input
David Pursehouse59bbb582013-05-17 10:49:33 +090053 # pylint:enable=W0622
Chirayu Desai217ea7d2013-03-01 19:14:38 +053054
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070055
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070056def _lwrite(path, content):
57 lock = '%s.lock' % path
58
Chirayu Desai303a82f2014-08-19 22:57:17 +053059 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070060 try:
61 fd.write(content)
62 finally:
63 fd.close()
64
65 try:
66 os.rename(lock, path)
67 except OSError:
68 os.remove(lock)
69 raise
70
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070071
Shawn O. Pearce48244782009-04-16 08:25:57 -070072def _error(fmt, *args):
73 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070074 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070075
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070076
David Pursehousef33929d2015-08-24 14:39:14 +090077def _warn(fmt, *args):
78 msg = fmt % args
79 print('warn: %s' % msg, file=sys.stderr)
80
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070081
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082def not_rev(r):
83 return '^' + r
84
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070085
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080086def sq(r):
87 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
106 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
107 d = os.path.join(d, 'hooks')
108 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
138
139 def __init__(self, project, branch, base):
140 self.project = project
141 self.branch = branch
142 self.base = base
143
144 @property
145 def name(self):
146 return self.branch.name
147
148 @property
149 def commits(self):
150 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700151 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
152 '--abbrev-commit',
153 '--pretty=oneline',
154 '--reverse',
155 '--date-order',
156 not_rev(self.base),
157 R_HEADS + self.name,
158 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159 return self._commit_cache
160
161 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 def unabbrev_commits(self):
163 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700164 for commit in self.project.bare_git.rev_list(not_rev(self.base),
165 R_HEADS + self.name,
166 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800167 r[commit[0:8]] = commit
168 return r
169
170 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700172 return self.project.bare_git.log('--pretty=format:%cd',
173 '-n', '1',
174 R_HEADS + self.name,
175 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700177 def UploadForReview(self, people,
178 auto_topic=False,
179 draft=False,
180 dest_branch=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800181 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700182 people,
Brian Harring435370c2012-07-28 15:37:04 -0700183 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400184 draft=draft,
185 dest_branch=dest_branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700187 def GetPublishedRefs(self):
188 refs = {}
189 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700190 self.branch.remote.SshReviewUrl(self.project.UserEmail),
191 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700192 for line in output.split('\n'):
193 try:
194 (sha, ref) = line.split()
195 refs[sha] = ref
196 except ValueError:
197 pass
198
199 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700200
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700201
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700202class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700203
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204 def __init__(self, config):
205 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100206 self.project = self.printer('header', attr='bold')
207 self.branch = self.printer('header', attr='bold')
208 self.nobranch = self.printer('nobranch', fg='red')
209 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210
Anthony King7bdac712014-07-16 12:56:40 +0100211 self.added = self.printer('added', fg='green')
212 self.changed = self.printer('changed', fg='red')
213 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214
215
216class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700217
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218 def __init__(self, config):
219 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100220 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700222
Anthony King7bdac712014-07-16 12:56:40 +0100223class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700224
James W. Mills24c13082012-04-12 15:04:13 -0500225 def __init__(self, name, value, keep):
226 self.name = name
227 self.value = value
228 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700229
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700230
Anthony King7bdac712014-07-16 12:56:40 +0100231class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700232
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800233 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234 self.src = src
235 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800236 self.abs_src = abssrc
237 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238
239 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800240 src = self.abs_src
241 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242 # copy file if it does not exist or is out of date
243 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
244 try:
245 # remove existing file first, since it might be read-only
246 if os.path.exists(dest):
247 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400248 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200249 dest_dir = os.path.dirname(dest)
250 if not os.path.isdir(dest_dir):
251 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252 shutil.copy(src, dest)
253 # make the file read-only
254 mode = os.stat(dest)[stat.ST_MODE]
255 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
256 os.chmod(dest, mode)
257 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700258 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700260
Anthony King7bdac712014-07-16 12:56:40 +0100261class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
Wink Saville4c426ef2015-06-03 08:05:17 -0700263 def __init__(self, git_worktree, src, dest, relsrc, absdest):
264 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500265 self.src = src
266 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700267 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500268 self.abs_dest = absdest
269
Wink Saville4c426ef2015-06-03 08:05:17 -0700270 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500271 # link file if it does not exist or is out of date
Wink Saville4c426ef2015-06-03 08:05:17 -0700272 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500273 try:
274 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800275 if os.path.lexists(absDest):
Wink Saville4c426ef2015-06-03 08:05:17 -0700276 os.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500277 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700278 dest_dir = os.path.dirname(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500279 if not os.path.isdir(dest_dir):
280 os.makedirs(dest_dir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700281 os.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500282 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700283 _error('Cannot link file %s to %s', relSrc, absDest)
284
285 def _Link(self):
286 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
287 on the src linking all of the files in the source in to the destination
288 directory.
289 """
290 # We use the absSrc to handle the situation where the current directory
291 # is not the root of the repo
292 absSrc = os.path.join(self.git_worktree, self.src)
293 if os.path.exists(absSrc):
294 # Entity exists so just a simple one to one link operation
295 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
296 else:
297 # Entity doesn't exist assume there is a wild card
298 absDestDir = self.abs_dest
299 if os.path.exists(absDestDir) and not os.path.isdir(absDestDir):
300 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700301 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700302 else:
303 absSrcFiles = glob.glob(absSrc)
304 for absSrcFile in absSrcFiles:
305 # Create a releative path from source dir to destination dir
306 absSrcDir = os.path.dirname(absSrcFile)
307 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
308
309 # Get the source file name
310 srcFile = os.path.basename(absSrcFile)
311
312 # Now form the final full paths to srcFile. They will be
313 # absolute for the desintaiton and relative for the srouce.
314 absDest = os.path.join(absDestDir, srcFile)
315 relSrc = os.path.join(relSrcDir, srcFile)
316 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500317
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700318
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700319class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700320
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700321 def __init__(self,
322 name,
Anthony King7bdac712014-07-16 12:56:40 +0100323 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700324 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100325 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700326 revision=None,
327 orig_name=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700328 self.name = name
329 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700330 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700331 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100332 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700333 self.orig_name = orig_name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700334
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700335
Doug Anderson37282b42011-03-04 11:54:18 -0800336class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700337
Doug Anderson37282b42011-03-04 11:54:18 -0800338 """A RepoHook contains information about a script to run as a hook.
339
340 Hooks are used to run a python script before running an upload (for instance,
341 to run presubmit checks). Eventually, we may have hooks for other actions.
342
343 This shouldn't be confused with files in the 'repo/hooks' directory. Those
344 files are copied into each '.git/hooks' folder for each project. Repo-level
345 hooks are associated instead with repo actions.
346
347 Hooks are always python. When a hook is run, we will load the hook into the
348 interpreter and execute its main() function.
349 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700350
Doug Anderson37282b42011-03-04 11:54:18 -0800351 def __init__(self,
352 hook_type,
353 hooks_project,
354 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400355 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800356 abort_if_user_denies=False):
357 """RepoHook constructor.
358
359 Params:
360 hook_type: A string representing the type of hook. This is also used
361 to figure out the name of the file containing the hook. For
362 example: 'pre-upload'.
363 hooks_project: The project containing the repo hooks. If you have a
364 manifest, this is manifest.repo_hooks_project. OK if this is None,
365 which will make the hook a no-op.
366 topdir: Repo's top directory (the one containing the .repo directory).
367 Scripts will run with CWD as this directory. If you have a manifest,
368 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400369 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800370 abort_if_user_denies: If True, we'll throw a HookError() if the user
371 doesn't allow us to run the hook.
372 """
373 self._hook_type = hook_type
374 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400375 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800376 self._topdir = topdir
377 self._abort_if_user_denies = abort_if_user_denies
378
379 # Store the full path to the script for convenience.
380 if self._hooks_project:
381 self._script_fullpath = os.path.join(self._hooks_project.worktree,
382 self._hook_type + '.py')
383 else:
384 self._script_fullpath = None
385
386 def _GetHash(self):
387 """Return a hash of the contents of the hooks directory.
388
389 We'll just use git to do this. This hash has the property that if anything
390 changes in the directory we will return a different has.
391
392 SECURITY CONSIDERATION:
393 This hash only represents the contents of files in the hook directory, not
394 any other files imported or called by hooks. Changes to imported files
395 can change the script behavior without affecting the hash.
396
397 Returns:
398 A string representing the hash. This will always be ASCII so that it can
399 be printed to the user easily.
400 """
401 assert self._hooks_project, "Must have hooks to calculate their hash."
402
403 # We will use the work_git object rather than just calling GetRevisionId().
404 # That gives us a hash of the latest checked in version of the files that
405 # the user will actually be executing. Specifically, GetRevisionId()
406 # doesn't appear to change even if a user checks out a different version
407 # of the hooks repo (via git checkout) nor if a user commits their own revs.
408 #
409 # NOTE: Local (non-committed) changes will not be factored into this hash.
410 # I think this is OK, since we're really only worried about warning the user
411 # about upstream changes.
412 return self._hooks_project.work_git.rev_parse('HEAD')
413
414 def _GetMustVerb(self):
415 """Return 'must' if the hook is required; 'should' if not."""
416 if self._abort_if_user_denies:
417 return 'must'
418 else:
419 return 'should'
420
421 def _CheckForHookApproval(self):
422 """Check to see whether this hook has been approved.
423
Mike Frysinger40252c22016-08-15 21:23:44 -0400424 We'll accept approval of manifest URLs if they're using secure transports.
425 This way the user can say they trust the manifest hoster. For insecure
426 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800427
428 Note that we ask permission for each individual hook even though we use
429 the hash of all hooks when detecting changes. We'd like the user to be
430 able to approve / deny each hook individually. We only use the hash of all
431 hooks because there is no other easy way to detect changes to local imports.
432
433 Returns:
434 True if this hook is approved to run; False otherwise.
435
436 Raises:
437 HookError: Raised if the user doesn't approve and abort_if_user_denies
438 was passed to the consturctor.
439 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400440 if self._ManifestUrlHasSecureScheme():
441 return self._CheckForHookApprovalManifest()
442 else:
443 return self._CheckForHookApprovalHash()
444
445 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
446 changed_prompt):
447 """Check for approval for a particular attribute and hook.
448
449 Args:
450 subkey: The git config key under [repo.hooks.<hook_type>] to store the
451 last approved string.
452 new_val: The new value to compare against the last approved one.
453 main_prompt: Message to display to the user to ask for approval.
454 changed_prompt: Message explaining why we're re-asking for approval.
455
456 Returns:
457 True if this hook is approved to run; False otherwise.
458
459 Raises:
460 HookError: Raised if the user doesn't approve and abort_if_user_denies
461 was passed to the consturctor.
462 """
Doug Anderson37282b42011-03-04 11:54:18 -0800463 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400464 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800465
Mike Frysinger40252c22016-08-15 21:23:44 -0400466 # Get the last value that the user approved for this hook; may be None.
467 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800468
Mike Frysinger40252c22016-08-15 21:23:44 -0400469 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800470 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400471 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800472 # Approval matched. We're done.
473 return True
474 else:
475 # Give the user a reason why we're prompting, since they last told
476 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400477 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800478 else:
479 prompt = ''
480
481 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
482 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400483 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530484 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900485 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800486
487 # User is doing a one-time approval.
488 if response in ('y', 'yes'):
489 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400490 elif response == 'always':
491 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800492 return True
493
494 # For anything else, we'll assume no approval.
495 if self._abort_if_user_denies:
496 raise HookError('You must allow the %s hook or use --no-verify.' %
497 self._hook_type)
498
499 return False
500
Mike Frysinger40252c22016-08-15 21:23:44 -0400501 def _ManifestUrlHasSecureScheme(self):
502 """Check if the URI for the manifest is a secure transport."""
503 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
504 parse_results = urllib.parse.urlparse(self._manifest_url)
505 return parse_results.scheme in secure_schemes
506
507 def _CheckForHookApprovalManifest(self):
508 """Check whether the user has approved this manifest host.
509
510 Returns:
511 True if this hook is approved to run; False otherwise.
512 """
513 return self._CheckForHookApprovalHelper(
514 'approvedmanifest',
515 self._manifest_url,
516 'Run hook scripts from %s' % (self._manifest_url,),
517 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
518
519 def _CheckForHookApprovalHash(self):
520 """Check whether the user has approved the hooks repo.
521
522 Returns:
523 True if this hook is approved to run; False otherwise.
524 """
525 prompt = ('Repo %s run the script:\n'
526 ' %s\n'
527 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700528 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400529 return self._CheckForHookApprovalHelper(
530 'approvedhash',
531 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700532 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400533 'Scripts have changed since %s was allowed.' % (self._hook_type,))
534
Doug Anderson37282b42011-03-04 11:54:18 -0800535 def _ExecuteHook(self, **kwargs):
536 """Actually execute the given hook.
537
538 This will run the hook's 'main' function in our python interpreter.
539
540 Args:
541 kwargs: Keyword arguments to pass to the hook. These are often specific
542 to the hook type. For instance, pre-upload hooks will contain
543 a project_list.
544 """
545 # Keep sys.path and CWD stashed away so that we can always restore them
546 # upon function exit.
547 orig_path = os.getcwd()
548 orig_syspath = sys.path
549
550 try:
551 # Always run hooks with CWD as topdir.
552 os.chdir(self._topdir)
553
554 # Put the hook dir as the first item of sys.path so hooks can do
555 # relative imports. We want to replace the repo dir as [0] so
556 # hooks can't import repo files.
557 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
558
559 # Exec, storing global context in the context dict. We catch exceptions
560 # and convert to a HookError w/ just the failing traceback.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500561 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800562 try:
Anthony King70f68902014-05-05 21:15:34 +0100563 exec(compile(open(self._script_fullpath).read(),
564 self._script_fullpath, 'exec'), context)
Doug Anderson37282b42011-03-04 11:54:18 -0800565 except Exception:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700566 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
567 (traceback.format_exc(), self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800568
569 # Running the script should have defined a main() function.
570 if 'main' not in context:
571 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
572
Doug Anderson37282b42011-03-04 11:54:18 -0800573 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
574 # We don't actually want hooks to define their main with this argument--
575 # it's there to remind them that their hook should always take **kwargs.
576 # For instance, a pre-upload hook should be defined like:
577 # def main(project_list, **kwargs):
578 #
579 # This allows us to later expand the API without breaking old hooks.
580 kwargs = kwargs.copy()
581 kwargs['hook_should_take_kwargs'] = True
582
583 # Call the main function in the hook. If the hook should cause the
584 # build to fail, it will raise an Exception. We'll catch that convert
585 # to a HookError w/ just the failing traceback.
586 try:
587 context['main'](**kwargs)
588 except Exception:
589 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700590 'above.' % (traceback.format_exc(),
591 self._hook_type))
Doug Anderson37282b42011-03-04 11:54:18 -0800592 finally:
593 # Restore sys.path and CWD.
594 sys.path = orig_syspath
595 os.chdir(orig_path)
596
597 def Run(self, user_allows_all_hooks, **kwargs):
598 """Run the hook.
599
600 If the hook doesn't exist (because there is no hooks project or because
601 this particular hook is not enabled), this is a no-op.
602
603 Args:
604 user_allows_all_hooks: If True, we will never prompt about running the
605 hook--we'll just assume it's OK to run it.
606 kwargs: Keyword arguments to pass to the hook. These are often specific
607 to the hook type. For instance, pre-upload hooks will contain
608 a project_list.
609
610 Raises:
611 HookError: If there was a problem finding the hook or the user declined
612 to run a required hook (from _CheckForHookApproval).
613 """
614 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700615 if ((not self._hooks_project) or (self._hook_type not in
616 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800617 return
618
619 # Bail with a nice error if we can't find the hook.
620 if not os.path.isfile(self._script_fullpath):
621 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
622
623 # Make sure the user is OK with running the hook.
624 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
625 return
626
627 # Run the hook with the same version of python we're using.
628 self._ExecuteHook(**kwargs)
629
630
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700631class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600632 # These objects can be shared between several working trees.
633 shareable_files = ['description', 'info']
634 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
635 # These objects can only be used by a single working tree.
636 working_tree_files = ['config', 'packed-refs', 'shallow']
637 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700638
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700639 def __init__(self,
640 manifest,
641 name,
642 remote,
643 gitdir,
David James8d201162013-10-11 17:03:19 -0700644 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645 worktree,
646 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700647 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800648 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100649 rebase=True,
650 groups=None,
651 sync_c=False,
652 sync_s=False,
653 clone_depth=None,
654 upstream=None,
655 parent=None,
656 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900657 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700658 optimized_fetch=False,
659 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800660 """Init a Project object.
661
662 Args:
663 manifest: The XmlManifest object.
664 name: The `name` attribute of manifest.xml's project element.
665 remote: RemoteSpec object specifying its remote's properties.
666 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700667 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800668 worktree: Absolute path of git working tree.
669 relpath: Relative path of git working tree to repo's top directory.
670 revisionExpr: The `revision` attribute of manifest.xml's project element.
671 revisionId: git commit id for checking out.
672 rebase: The `rebase` attribute of manifest.xml's project element.
673 groups: The `groups` attribute of manifest.xml's project element.
674 sync_c: The `sync-c` attribute of manifest.xml's project element.
675 sync_s: The `sync-s` attribute of manifest.xml's project element.
676 upstream: The `upstream` attribute of manifest.xml's project element.
677 parent: The parent Project object.
678 is_derived: False if the project was explicitly defined in the manifest;
679 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400680 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900681 optimized_fetch: If True, when a project is set to a sha1 revision, only
682 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700683 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800684 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700685 self.manifest = manifest
686 self.name = name
687 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800688 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700689 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800690 if worktree:
Mark E. Hamiltonf9fe3e12016-02-23 18:10:42 -0700691 self.worktree = os.path.normpath(worktree.replace('\\', '/'))
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800692 else:
693 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700694 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700695 self.revisionExpr = revisionExpr
696
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700697 if revisionId is None \
698 and revisionExpr \
699 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700700 self.revisionId = revisionExpr
701 else:
702 self.revisionId = revisionId
703
Mike Pontillod3153822012-02-28 11:53:24 -0800704 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700705 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700706 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800707 self.sync_s = sync_s
David Pursehouseede7f122012-11-27 22:25:30 +0900708 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700709 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800710 self.parent = parent
711 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900712 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800713 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800714
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700715 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700716 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500717 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500718 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700719 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
720 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700721
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800722 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700723 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800724 else:
725 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700726 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700727 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700728 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400729 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700730 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700731
Doug Anderson37282b42011-03-04 11:54:18 -0800732 # This will be filled in if a project is later identified to be the
733 # project containing repo hooks.
734 self.enabled_repo_hooks = []
735
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800737 def Derived(self):
738 return self.is_derived
739
740 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741 def Exists(self):
Kevin Degi384b3c52014-10-16 16:02:58 -0600742 return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743
744 @property
745 def CurrentBranch(self):
746 """Obtain the name of the currently checked out branch.
747 The branch name omits the 'refs/heads/' prefix.
748 None is returned if the project is on a detached HEAD.
749 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700750 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751 if b.startswith(R_HEADS):
752 return b[len(R_HEADS):]
753 return None
754
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700755 def IsRebaseInProgress(self):
756 w = self.worktree
757 g = os.path.join(w, '.git')
758 return os.path.exists(os.path.join(g, 'rebase-apply')) \
759 or os.path.exists(os.path.join(g, 'rebase-merge')) \
760 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200761
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700762 def IsDirty(self, consider_untracked=True):
763 """Is the working directory modified in some way?
764 """
765 self.work_git.update_index('-q',
766 '--unmerged',
767 '--ignore-missing',
768 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900769 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770 return True
771 if self.work_git.DiffZ('diff-files'):
772 return True
773 if consider_untracked and self.work_git.LsOthers():
774 return True
775 return False
776
777 _userident_name = None
778 _userident_email = None
779
780 @property
781 def UserName(self):
782 """Obtain the user's personal name.
783 """
784 if self._userident_name is None:
785 self._LoadUserIdentity()
786 return self._userident_name
787
788 @property
789 def UserEmail(self):
790 """Obtain the user's email address. This is very likely
791 to be their Gerrit login.
792 """
793 if self._userident_email is None:
794 self._LoadUserIdentity()
795 return self._userident_email
796
797 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900798 u = self.bare_git.var('GIT_COMMITTER_IDENT')
799 m = re.compile("^(.*) <([^>]*)> ").match(u)
800 if m:
801 self._userident_name = m.group(1)
802 self._userident_email = m.group(2)
803 else:
804 self._userident_name = ''
805 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700806
807 def GetRemote(self, name):
808 """Get the configuration for a single remote.
809 """
810 return self.config.GetRemote(name)
811
812 def GetBranch(self, name):
813 """Get the configuration for a single branch.
814 """
815 return self.config.GetBranch(name)
816
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700817 def GetBranches(self):
818 """Get all existing local branches.
819 """
820 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900821 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700822 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700823
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530824 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700825 if name.startswith(R_HEADS):
826 name = name[len(R_HEADS):]
827 b = self.GetBranch(name)
828 b.current = name == current
829 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900830 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700831 heads[name] = b
832
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530833 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700834 if name.startswith(R_PUB):
835 name = name[len(R_PUB):]
836 b = heads.get(name)
837 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900838 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700839
840 return heads
841
Colin Cross5acde752012-03-28 20:15:45 -0700842 def MatchesGroups(self, manifest_groups):
843 """Returns true if the manifest groups specified at init should cause
844 this project to be synced.
845 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700846 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700847
848 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700849 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700850 manifest_groups: "-group1,group2"
851 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500852
853 The special manifest group "default" will match any project that
854 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700855 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500856 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700857 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700858 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500859 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700860
Conley Owens971de8e2012-04-16 10:36:08 -0700861 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700862 for group in expanded_manifest_groups:
863 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700864 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700865 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700866 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700867
Conley Owens971de8e2012-04-16 10:36:08 -0700868 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700870# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700871 def UncommitedFiles(self, get_all=True):
872 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700873
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700874 Args:
875 get_all: a boolean, if True - get information about all different
876 uncommitted files. If False - return as soon as any kind of
877 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500878 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700879 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500880 self.work_git.update_index('-q',
881 '--unmerged',
882 '--ignore-missing',
883 '--refresh')
884 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700885 details.append("rebase in progress")
886 if not get_all:
887 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500888
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700889 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
890 if changes:
891 details.extend(changes)
892 if not get_all:
893 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500894
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700895 changes = self.work_git.DiffZ('diff-files').keys()
896 if changes:
897 details.extend(changes)
898 if not get_all:
899 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500900
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700901 changes = self.work_git.LsOthers()
902 if changes:
903 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500904
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700905 return details
906
907 def HasChanges(self):
908 """Returns true if there are uncommitted changes.
909 """
910 if self.UncommitedFiles(get_all=False):
911 return True
912 else:
913 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500914
Terence Haddock4655e812011-03-31 12:33:34 +0200915 def PrintWorkTreeStatus(self, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200917
918 Args:
919 output: If specified, redirect the output to this object.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920 """
921 if not os.path.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700922 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200923 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700924 print(file=output_redir)
925 print('project %s/' % self.relpath, file=output_redir)
926 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 return
928
929 self.work_git.update_index('-q',
930 '--unmerged',
931 '--ignore-missing',
932 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700933 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
935 df = self.work_git.DiffZ('diff-files')
936 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100937 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700938 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939
940 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700941 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200942 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700943 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944
945 branch = self.CurrentBranch
946 if branch is None:
947 out.nobranch('(*** NO BRANCH ***)')
948 else:
949 out.branch('branch %s', branch)
950 out.nl()
951
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700952 if rb:
953 out.important('prior sync failed; rebase still in progress')
954 out.nl()
955
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700956 paths = list()
957 paths.extend(di.keys())
958 paths.extend(df.keys())
959 paths.extend(do)
960
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530961 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900962 try:
963 i = di[p]
964 except KeyError:
965 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900967 try:
968 f = df[p]
969 except KeyError:
970 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200971
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900972 if i:
973 i_status = i.status.upper()
974 else:
975 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700976
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900977 if f:
978 f_status = f.status.lower()
979 else:
980 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981
982 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800983 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700984 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700985 else:
986 line = ' %s%s\t%s' % (i_status, f_status, p)
987
988 if i and not f:
989 out.added('%s', line)
990 elif (i and f) or (not i and f):
991 out.changed('%s', line)
992 elif not i and not f:
993 out.untracked('%s', line)
994 else:
995 out.write('%s', line)
996 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200997
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700998 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700999
pelyad67872d2012-03-28 14:49:58 +03001000 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001 """Prints the status of the repository to stdout.
1002 """
1003 out = DiffColoring(self.config)
1004 cmd = ['diff']
1005 if out.is_on:
1006 cmd.append('--color')
1007 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001008 if absolute_paths:
1009 cmd.append('--src-prefix=a/%s/' % self.relpath)
1010 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011 cmd.append('--')
1012 p = GitCommand(self,
1013 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001014 capture_stdout=True,
1015 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016 has_diff = False
1017 for line in p.process.stdout:
1018 if not has_diff:
1019 out.nl()
1020 out.project('project %s/' % self.relpath)
1021 out.nl()
1022 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001023 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001024 p.Wait()
1025
1026
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001027# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028
David Pursehouse8a68ff92012-09-24 12:15:13 +09001029 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001030 """Was the branch published (uploaded) for code review?
1031 If so, returns the SHA-1 hash of the last published
1032 state for the branch.
1033 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001034 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001035 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001036 try:
1037 return self.bare_git.rev_parse(key)
1038 except GitError:
1039 return None
1040 else:
1041 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001042 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001043 except KeyError:
1044 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001045
David Pursehouse8a68ff92012-09-24 12:15:13 +09001046 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001047 """Prunes any stale published refs.
1048 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001049 if all_refs is None:
1050 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 heads = set()
1052 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301053 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054 if name.startswith(R_HEADS):
1055 heads.add(name)
1056 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001057 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301059 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001060 n = name[len(R_PUB):]
1061 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001062 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001064 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065 """List any branches which can be uploaded for review.
1066 """
1067 heads = {}
1068 pubed = {}
1069
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301070 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001072 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001073 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001074 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075
1076 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301077 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001078 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001080 if selected_branch and branch != selected_branch:
1081 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001083 rb = self.GetUploadableBranch(branch)
1084 if rb:
1085 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086 return ready
1087
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001088 def GetUploadableBranch(self, branch_name):
1089 """Get a single uploadable branch, or None.
1090 """
1091 branch = self.GetBranch(branch_name)
1092 base = branch.LocalMerge
1093 if branch.LocalMerge:
1094 rb = ReviewableBranch(self, branch, base)
1095 if rb.commits:
1096 return rb
1097 return None
1098
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001099 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001100 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001101 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001102 draft=False,
1103 dest_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001104 """Uploads the named branch for code review.
1105 """
1106 if branch is None:
1107 branch = self.CurrentBranch
1108 if branch is None:
1109 raise GitError('not currently on a branch')
1110
1111 branch = self.GetBranch(branch)
1112 if not branch.LocalMerge:
1113 raise GitError('branch %s does not track a remote' % branch.name)
1114 if not branch.remote.review:
1115 raise GitError('remote %s has no review url' % branch.remote.name)
1116
Bryan Jacobsf609f912013-05-06 13:36:24 -04001117 if dest_branch is None:
1118 dest_branch = self.dest_branch
1119 if dest_branch is None:
1120 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121 if not dest_branch.startswith(R_HEADS):
1122 dest_branch = R_HEADS + dest_branch
1123
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001124 if not branch.remote.projectname:
1125 branch.remote.projectname = self.name
1126 branch.remote.Save()
1127
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001128 url = branch.remote.ReviewUrl(self.UserEmail)
1129 if url is None:
1130 raise UploadError('review not configured')
1131 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001132
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001133 if url.startswith('ssh://'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001134 rp = ['gerrit receive-pack']
1135 for e in people[0]:
1136 rp.append('--reviewer=%s' % sq(e))
1137 for e in people[1]:
1138 rp.append('--cc=%s' % sq(e))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001139 cmd.append('--receive-pack=%s' % " ".join(rp))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001140
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001141 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001142
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001143 if dest_branch.startswith(R_HEADS):
1144 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001145
1146 upload_type = 'for'
1147 if draft:
1148 upload_type = 'drafts'
1149
1150 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1151 dest_branch)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001152 if auto_topic:
1153 ref_spec = ref_spec + '/' + branch.name
Shawn Pearce45d21682013-02-28 00:35:51 -08001154 if not url.startswith('ssh://'):
1155 rp = ['r=%s' % p for p in people[0]] + \
1156 ['cc=%s' % p for p in people[1]]
1157 if rp:
1158 ref_spec = ref_spec + '%' + ','.join(rp)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001159 cmd.append(ref_spec)
1160
Anthony King7bdac712014-07-16 12:56:40 +01001161 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001162 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163
1164 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1165 self.bare_git.UpdateRef(R_PUB + branch.name,
1166 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001167 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001168
1169
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001170# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001171
Julien Campergue335f5ef2013-10-16 11:02:35 +02001172 def _ExtractArchive(self, tarpath, path=None):
1173 """Extract the given tar on its current location
1174
1175 Args:
1176 - tarpath: The path to the actual tar file
1177
1178 """
1179 try:
1180 with tarfile.open(tarpath, 'r') as tar:
1181 tar.extractall(path=path)
1182 return True
1183 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001184 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001185 return False
1186
Ningning Xiac2fbc782016-08-22 14:24:39 -07001187 def CachePopulate(self, cache_dir, url):
1188 """Populate cache in the cache_dir.
1189
1190 Args:
1191 cache_dir: Directory to cache git files from Google Storage.
1192 url: Git url of current repository.
1193
1194 Raises:
1195 CacheApplyError if it fails to populate the git cache.
1196 """
1197 cmd = ['cache', 'populate', '--ignore_locks', '-v',
1198 '--cache-dir', cache_dir, url]
1199
1200 if GitCommand(self, cmd, cwd=cache_dir).Wait() != 0:
1201 raise CacheApplyError('Failed to populate cache. cache_dir: %s '
1202 'url: %s' % (cache_dir, url))
1203
1204 def CacheExists(self, cache_dir, url):
1205 """Check the existence of the cache files.
1206
1207 Args:
1208 cache_dir: Directory to cache git files.
1209 url: Git url of current repository.
1210
1211 Raises:
1212 CacheApplyError if the cache files do not exist.
1213 """
1214 cmd = ['cache', 'exists', '--quiet', '--cache-dir', cache_dir, url]
1215
1216 exist = GitCommand(self, cmd, cwd=self.gitdir, capture_stdout=True)
1217 if exist.Wait() != 0:
1218 raise CacheApplyError('Failed to execute git cache exists cmd. '
1219 'cache_dir: %s url: %s' % (cache_dir, url))
1220
1221 if not exist.stdout or not exist.stdout.strip():
1222 raise CacheApplyError('Failed to find cache. cache_dir: %s '
1223 'url: %s' % (cache_dir, url))
1224 return exist.stdout.strip()
1225
1226 def CacheApply(self, cache_dir):
1227 """Apply git cache files populated from Google Storage buckets.
1228
1229 Args:
1230 cache_dir: Directory to cache git files.
1231
1232 Raises:
1233 CacheApplyError if it fails to apply git caches.
1234 """
1235 remote = self.GetRemote(self.remote.name)
1236
1237 self.CachePopulate(cache_dir, remote.url)
1238
1239 mirror_dir = self.CacheExists(cache_dir, remote.url)
1240
1241 refspec = RefSpec(True, 'refs/heads/*',
1242 'refs/remotes/%s/*' % remote.name)
1243
1244 fetch_cache_cmd = ['fetch', mirror_dir, str(refspec)]
1245 if GitCommand(self, fetch_cache_cmd, self.gitdir).Wait() != 0:
1246 raise CacheApplyError('Failed to fetch refs %s from %s' %
1247 (mirror_dir, str(refspec)))
1248
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001249 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001250 quiet=False,
1251 is_new=None,
1252 current_branch_only=False,
1253 force_sync=False,
1254 clone_bundle=True,
1255 no_tags=False,
1256 archive=False,
1257 optimized_fetch=False,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001258 prune=False,
1259 cache_dir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001260 """Perform only the network IO portion of the sync process.
1261 Local working directory/branch state is not affected.
1262 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001263 if archive and not isinstance(self, MetaProject):
1264 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001265 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001266 return False
1267
1268 name = self.relpath.replace('\\', '/')
1269 name = name.replace('/', '_')
1270 tarpath = '%s.tar' % name
1271 topdir = self.manifest.topdir
1272
1273 try:
1274 self._FetchArchive(tarpath, cwd=topdir)
1275 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001276 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001277 return False
1278
1279 # From now on, we only need absolute tarpath
1280 tarpath = os.path.join(topdir, tarpath)
1281
1282 if not self._ExtractArchive(tarpath, path=topdir):
1283 return False
1284 try:
1285 os.remove(tarpath)
1286 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001287 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001288 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001289 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001290 if is_new is None:
1291 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001292 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001293 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001294 else:
1295 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001297
1298 if is_new:
1299 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1300 try:
1301 fd = open(alt, 'rb')
1302 try:
1303 alt_dir = fd.readline().rstrip()
1304 finally:
1305 fd.close()
1306 except IOError:
1307 alt_dir = None
1308 else:
1309 alt_dir = None
1310
Ningning Xiac2fbc782016-08-22 14:24:39 -07001311 applied_cache = False
1312 # If cache_dir is provided, and it's a new repository without
1313 # alternative_dir, bootstrap this project repo with the git
1314 # cache files.
1315 if cache_dir is not None and is_new and alt_dir is None:
1316 try:
1317 self.CacheApply(cache_dir)
1318 applied_cache = True
1319 is_new = False
1320 except CacheApplyError as e:
1321 _error('Could not apply git cache: %s', e)
1322 _error('Please check if you have the right GS credentials.')
1323 _error('Please check if the cache files exist in GS.')
1324
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001325 if clone_bundle \
Ningning Xiac2fbc782016-08-22 14:24:39 -07001326 and not applied_cache \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001327 and alt_dir is None \
1328 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001329 is_new = False
1330
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001331 if not current_branch_only:
1332 if self.sync_c:
1333 current_branch_only = True
1334 elif not self.manifest._loaded:
1335 # Manifest cannot check defaults until it syncs.
1336 current_branch_only = False
1337 elif self.manifest.default.sync_c:
1338 current_branch_only = True
1339
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001340 need_to_fetch = not (optimized_fetch and
1341 (ID_RE.match(self.revisionExpr) and
1342 self._CheckForSha1()))
1343 if (need_to_fetch and
1344 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1345 current_branch_only=current_branch_only,
1346 no_tags=no_tags, prune=prune)):
Anthony King7bdac712014-07-16 12:56:40 +01001347 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001348
1349 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001350 self._InitMRef()
1351 else:
1352 self._InitMirrorHead()
1353 try:
1354 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
1355 except OSError:
1356 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001358
1359 def PostRepoUpgrade(self):
1360 self._InitHooks()
1361
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001362 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001363 if self.manifest.isGitcClient:
1364 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001365 for copyfile in self.copyfiles:
1366 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001367 for linkfile in self.linkfiles:
1368 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001369
Julien Camperguedd654222014-01-09 16:21:37 +01001370 def GetCommitRevisionId(self):
1371 """Get revisionId of a commit.
1372
1373 Use this method instead of GetRevisionId to get the id of the commit rather
1374 than the id of the current git object (for example, a tag)
1375
1376 """
1377 if not self.revisionExpr.startswith(R_TAGS):
1378 return self.GetRevisionId(self._allrefs)
1379
1380 try:
1381 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1382 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001383 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1384 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001385
David Pursehouse8a68ff92012-09-24 12:15:13 +09001386 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001387 if self.revisionId:
1388 return self.revisionId
1389
1390 rem = self.GetRemote(self.remote.name)
1391 rev = rem.ToLocal(self.revisionExpr)
1392
David Pursehouse8a68ff92012-09-24 12:15:13 +09001393 if all_refs is not None and rev in all_refs:
1394 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001395
1396 try:
1397 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1398 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001399 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1400 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001401
Kevin Degiabaa7f32014-11-12 11:27:45 -07001402 def Sync_LocalHalf(self, syncbuf, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403 """Perform only the local IO portion of the sync process.
1404 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001405 """
Kevin Degiabaa7f32014-11-12 11:27:45 -07001406 self._InitWorkTree(force_sync=force_sync)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001407 all_refs = self.bare_ref.all
1408 self.CleanPublishedCache(all_refs)
1409 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001410
David Pursehouse1d947b32012-10-25 12:23:11 +09001411 def _doff():
1412 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001413 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001414
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001415 head = self.work_git.GetHead()
1416 if head.startswith(R_HEADS):
1417 branch = head[len(R_HEADS):]
1418 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001419 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001420 except KeyError:
1421 head = None
1422 else:
1423 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001424
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001425 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001426 # Currently on a detached HEAD. The user is assumed to
1427 # not have any local modifications worth worrying about.
1428 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001429 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001430 syncbuf.fail(self, _PriorSyncFailedError())
1431 return
1432
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001433 if head == revid:
1434 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001435 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001436 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001437 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001438 # The copy/linkfile config may have changed.
1439 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001440 return
1441 else:
1442 lost = self._revlist(not_rev(revid), HEAD)
1443 if lost:
1444 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001445
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001446 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001447 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001448 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001449 syncbuf.fail(self, e)
1450 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001451 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001452 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001453
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001454 if head == revid:
1455 # No changes; don't do anything further.
1456 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001457 # The copy/linkfile config may have changed.
1458 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001459 return
1460
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001461 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001462
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001463 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001464 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001465 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001466 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001467 syncbuf.info(self,
1468 "leaving %s; does not track upstream",
1469 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001470 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001471 self._Checkout(revid, quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001472 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001473 syncbuf.fail(self, e)
1474 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001475 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001476 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001477
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001478 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001479 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001480 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001481 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001482 if not_merged:
1483 if upstream_gain:
1484 # The user has published this branch and some of those
1485 # commits are not yet merged upstream. We do not want
1486 # to rewrite the published commits so we punt.
1487 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001488 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001489 "branch %s is published (but not merged) and is now "
1490 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001491 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001492 elif pub == head:
1493 # All published commits are merged, and thus we are a
1494 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001495 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001496 syncbuf.later1(self, _doff)
1497 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001498
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001499 # Examine the local commits not in the remote. Find the
1500 # last one attributed to this user, if any.
1501 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001502 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001503 last_mine = None
1504 cnt_mine = 0
1505 for commit in local_changes:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +05301506 commit_id, committer_email = commit.decode('utf-8').split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001507 if committer_email == self.UserEmail:
1508 last_mine = commit_id
1509 cnt_mine += 1
1510
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001511 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001512 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001513
1514 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001515 syncbuf.fail(self, _DirtyError())
1516 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001517
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001518 # If the upstream switched on us, warn the user.
1519 #
1520 if branch.merge != self.revisionExpr:
1521 if branch.merge and self.revisionExpr:
1522 syncbuf.info(self,
1523 'manifest switched %s...%s',
1524 branch.merge,
1525 self.revisionExpr)
1526 elif branch.merge:
1527 syncbuf.info(self,
1528 'manifest no longer tracks %s',
1529 branch.merge)
1530
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001531 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001532 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001533 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001534 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001535 syncbuf.info(self,
1536 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001537 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001538
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001539 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001540 if not ID_RE.match(self.revisionExpr):
1541 # in case of manifest sync the revisionExpr might be a SHA1
1542 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001543 if not branch.merge.startswith('refs/'):
1544 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001545 branch.Save()
1546
Mike Pontillod3153822012-02-28 11:53:24 -08001547 if cnt_mine > 0 and self.rebase:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001548 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001549 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001550 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001551 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001552 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001553 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001554 self._ResetHard(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001555 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001556 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001557 syncbuf.fail(self, e)
1558 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001559 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001560 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001561
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001562 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001563 # dest should already be an absolute path, but src is project relative
1564 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001565 abssrc = os.path.join(self.worktree, src)
1566 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001567
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001568 def AddLinkFile(self, src, dest, absdest):
1569 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001570 # make src relative path to dest
1571 absdestdir = os.path.dirname(absdest)
1572 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001573 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001574
James W. Mills24c13082012-04-12 15:04:13 -05001575 def AddAnnotation(self, name, value, keep):
1576 self.annotations.append(_Annotation(name, value, keep))
1577
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001578 def DownloadPatchSet(self, change_id, patch_id):
1579 """Download a single patch set of a single change to FETCH_HEAD.
1580 """
1581 remote = self.GetRemote(self.remote.name)
1582
1583 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001584 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001585 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001586 if GitCommand(self, cmd, bare=True).Wait() != 0:
1587 return None
1588 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001589 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001590 change_id,
1591 patch_id,
1592 self.bare_git.rev_parse('FETCH_HEAD'))
1593
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001594
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001595# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001596
Simran Basib9a1b732015-08-20 12:19:28 -07001597 def StartBranch(self, name, branch_merge=''):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001598 """Create a new branch off the manifest's revision.
1599 """
Simran Basib9a1b732015-08-20 12:19:28 -07001600 if not branch_merge:
1601 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001602 head = self.work_git.GetHead()
1603 if head == (R_HEADS + name):
1604 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001605
David Pursehouse8a68ff92012-09-24 12:15:13 +09001606 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001607 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001608 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001609 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001610 capture_stdout=True,
1611 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001612
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001613 branch = self.GetBranch(name)
1614 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001615 branch.merge = branch_merge
1616 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1617 branch.merge = R_HEADS + branch_merge
David Pursehouse8a68ff92012-09-24 12:15:13 +09001618 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001619
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001620 if head.startswith(R_HEADS):
1621 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001622 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001623 except KeyError:
1624 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001625 if revid and head and revid == head:
1626 ref = os.path.join(self.gitdir, R_HEADS + name)
1627 try:
1628 os.makedirs(os.path.dirname(ref))
1629 except OSError:
1630 pass
1631 _lwrite(ref, '%s\n' % revid)
1632 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1633 'ref: %s%s\n' % (R_HEADS, name))
1634 branch.Save()
1635 return True
1636
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001637 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001638 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001639 capture_stdout=True,
1640 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001641 branch.Save()
1642 return True
1643 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001644
Wink Saville02d79452009-04-10 13:01:24 -07001645 def CheckoutBranch(self, name):
1646 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001647
1648 Args:
1649 name: The name of the branch to checkout.
1650
1651 Returns:
1652 True if the checkout succeeded; False if it didn't; None if the branch
1653 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001654 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001655 rev = R_HEADS + name
1656 head = self.work_git.GetHead()
1657 if head == rev:
1658 # Already on the branch
1659 #
1660 return True
Wink Saville02d79452009-04-10 13:01:24 -07001661
David Pursehouse8a68ff92012-09-24 12:15:13 +09001662 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001663 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001664 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001665 except KeyError:
1666 # Branch does not exist in this project
1667 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001668 return None
Wink Saville02d79452009-04-10 13:01:24 -07001669
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001670 if head.startswith(R_HEADS):
1671 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001672 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001673 except KeyError:
1674 head = None
1675
1676 if head == revid:
1677 # Same revision; just update HEAD to point to the new
1678 # target branch, but otherwise take no other action.
1679 #
1680 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1681 'ref: %s%s\n' % (R_HEADS, name))
1682 return True
1683
1684 return GitCommand(self,
1685 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001686 capture_stdout=True,
1687 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001688
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001689 def AbandonBranch(self, name):
1690 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001691
1692 Args:
1693 name: The name of the branch to abandon.
1694
1695 Returns:
1696 True if the abandon succeeded; False if it didn't; None if the branch
1697 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001698 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001699 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001700 all_refs = self.bare_ref.all
1701 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001702 # Doesn't exist
1703 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001704
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001705 head = self.work_git.GetHead()
1706 if head == rev:
1707 # We can't destroy the branch while we are sitting
1708 # on it. Switch to a detached HEAD.
1709 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001710 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001711
David Pursehouse8a68ff92012-09-24 12:15:13 +09001712 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001713 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001714 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1715 '%s\n' % revid)
1716 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001717 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001718
1719 return GitCommand(self,
1720 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001721 capture_stdout=True,
1722 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001723
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001724 def PruneHeads(self):
1725 """Prune any topic branches already merged into upstream.
1726 """
1727 cb = self.CurrentBranch
1728 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001729 left = self._allrefs
1730 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001731 if name.startswith(R_HEADS):
1732 name = name[len(R_HEADS):]
1733 if cb is None or name != cb:
1734 kill.append(name)
1735
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001736 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001737 if cb is not None \
1738 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001739 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001740 self.work_git.DetachHead(HEAD)
1741 kill.append(cb)
1742
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001743 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001744 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001745
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001746 try:
1747 self.bare_git.DetachHead(rev)
1748
1749 b = ['branch', '-d']
1750 b.extend(kill)
1751 b = GitCommand(self, b, bare=True,
1752 capture_stdout=True,
1753 capture_stderr=True)
1754 b.Wait()
1755 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001756 if ID_RE.match(old):
1757 self.bare_git.DetachHead(old)
1758 else:
1759 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001760 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001761
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001762 for branch in kill:
1763 if (R_HEADS + branch) not in left:
1764 self.CleanPublishedCache()
1765 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766
1767 if cb and cb not in kill:
1768 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001769 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001770
1771 kept = []
1772 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001773 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001774 branch = self.GetBranch(branch)
1775 base = branch.LocalMerge
1776 if not base:
1777 base = rev
1778 kept.append(ReviewableBranch(self, branch, base))
1779 return kept
1780
1781
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001782# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001783
1784 def GetRegisteredSubprojects(self):
1785 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001786
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001787 def rec(subprojects):
1788 if not subprojects:
1789 return
1790 result.extend(subprojects)
1791 for p in subprojects:
1792 rec(p.subprojects)
1793 rec(self.subprojects)
1794 return result
1795
1796 def _GetSubmodules(self):
1797 # Unfortunately we cannot call `git submodule status --recursive` here
1798 # because the working tree might not exist yet, and it cannot be used
1799 # without a working tree in its current implementation.
1800
1801 def get_submodules(gitdir, rev):
1802 # Parse .gitmodules for submodule sub_paths and sub_urls
1803 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1804 if not sub_paths:
1805 return []
1806 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1807 # revision of submodule repository
1808 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1809 submodules = []
1810 for sub_path, sub_url in zip(sub_paths, sub_urls):
1811 try:
1812 sub_rev = sub_revs[sub_path]
1813 except KeyError:
1814 # Ignore non-exist submodules
1815 continue
1816 submodules.append((sub_rev, sub_path, sub_url))
1817 return submodules
1818
1819 re_path = re.compile(r'^submodule\.([^.]+)\.path=(.*)$')
1820 re_url = re.compile(r'^submodule\.([^.]+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001821
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001822 def parse_gitmodules(gitdir, rev):
1823 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1824 try:
Anthony King7bdac712014-07-16 12:56:40 +01001825 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1826 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001827 except GitError:
1828 return [], []
1829 if p.Wait() != 0:
1830 return [], []
1831
1832 gitmodules_lines = []
1833 fd, temp_gitmodules_path = tempfile.mkstemp()
1834 try:
1835 os.write(fd, p.stdout)
1836 os.close(fd)
1837 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001838 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1839 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001840 if p.Wait() != 0:
1841 return [], []
1842 gitmodules_lines = p.stdout.split('\n')
1843 except GitError:
1844 return [], []
1845 finally:
1846 os.remove(temp_gitmodules_path)
1847
1848 names = set()
1849 paths = {}
1850 urls = {}
1851 for line in gitmodules_lines:
1852 if not line:
1853 continue
1854 m = re_path.match(line)
1855 if m:
1856 names.add(m.group(1))
1857 paths[m.group(1)] = m.group(2)
1858 continue
1859 m = re_url.match(line)
1860 if m:
1861 names.add(m.group(1))
1862 urls[m.group(1)] = m.group(2)
1863 continue
1864 names = sorted(names)
1865 return ([paths.get(name, '') for name in names],
1866 [urls.get(name, '') for name in names])
1867
1868 def git_ls_tree(gitdir, rev, paths):
1869 cmd = ['ls-tree', rev, '--']
1870 cmd.extend(paths)
1871 try:
Anthony King7bdac712014-07-16 12:56:40 +01001872 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1873 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001874 except GitError:
1875 return []
1876 if p.Wait() != 0:
1877 return []
1878 objects = {}
1879 for line in p.stdout.split('\n'):
1880 if not line.strip():
1881 continue
1882 object_rev, object_path = line.split()[2:4]
1883 objects[object_path] = object_rev
1884 return objects
1885
1886 try:
1887 rev = self.GetRevisionId()
1888 except GitError:
1889 return []
1890 return get_submodules(self.gitdir, rev)
1891
1892 def GetDerivedSubprojects(self):
1893 result = []
1894 if not self.Exists:
1895 # If git repo does not exist yet, querying its submodules will
1896 # mess up its states; so return here.
1897 return result
1898 for rev, path, url in self._GetSubmodules():
1899 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001900 relpath, worktree, gitdir, objdir = \
1901 self.manifest.GetSubprojectPaths(self, name, path)
1902 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001903 if project:
1904 result.extend(project.GetDerivedSubprojects())
1905 continue
David James8d201162013-10-11 17:03:19 -07001906
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001907 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001908 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001909 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001910 review=self.remote.review,
1911 revision=self.remote.revision)
1912 subproject = Project(manifest=self.manifest,
1913 name=name,
1914 remote=remote,
1915 gitdir=gitdir,
1916 objdir=objdir,
1917 worktree=worktree,
1918 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001919 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001920 revisionId=rev,
1921 rebase=self.rebase,
1922 groups=self.groups,
1923 sync_c=self.sync_c,
1924 sync_s=self.sync_s,
1925 parent=self,
1926 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001927 result.append(subproject)
1928 result.extend(subproject.GetDerivedSubprojects())
1929 return result
1930
1931
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001932# Direct Git Commands ##
Chris AtLee2fb64662014-01-16 21:32:33 -05001933 def _CheckForSha1(self):
1934 try:
1935 # if revision (sha or tag) is not present then following function
1936 # throws an error.
1937 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1938 return True
1939 except GitError:
1940 # There is no such persistent revision. We have to fetch it.
1941 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001942
Julien Campergue335f5ef2013-10-16 11:02:35 +02001943 def _FetchArchive(self, tarpath, cwd=None):
1944 cmd = ['archive', '-v', '-o', tarpath]
1945 cmd.append('--remote=%s' % self.remote.url)
1946 cmd.append('--prefix=%s/' % self.relpath)
1947 cmd.append(self.revisionExpr)
1948
1949 command = GitCommand(self, cmd, cwd=cwd,
1950 capture_stdout=True,
1951 capture_stderr=True)
1952
1953 if command.Wait() != 0:
1954 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1955
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001956 def _RemoteFetch(self, name=None,
1957 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001958 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001959 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001960 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09001961 no_tags=False,
1962 prune=False):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001963
1964 is_sha1 = False
1965 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001966 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001967
David Pursehouse9bc422f2014-04-15 10:28:56 +09001968 # The depth should not be used when fetching to a mirror because
1969 # it will result in a shallow repository that cannot be cloned or
1970 # fetched from.
1971 if not self.manifest.IsMirror:
1972 if self.clone_depth:
1973 depth = self.clone_depth
1974 else:
1975 depth = self.manifest.manifestProject.config.GetString('repo.depth')
Conley Owense4978cf2015-02-03 18:06:16 -08001976 # The repo project should never be synced with partial depth
1977 if self.relpath == '.repo/repo':
1978 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001979
Shawn Pearce69e04d82014-01-29 12:48:54 -08001980 if depth:
1981 current_branch_only = True
1982
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001983 if ID_RE.match(self.revisionExpr) is not None:
1984 is_sha1 = True
1985
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001986 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001987 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001988 # this is a tag and its sha1 value should never change
1989 tag_name = self.revisionExpr[len(R_TAGS):]
1990
1991 if is_sha1 or tag_name is not None:
Chris AtLee2fb64662014-01-16 21:32:33 -05001992 if self._CheckForSha1():
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001993 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001994 if is_sha1 and not depth:
1995 # When syncing a specific commit and --depth is not set:
1996 # * if upstream is explicitly specified and is not a sha1, fetch only
1997 # upstream as users expect only upstream to be fetch.
1998 # Note: The commit might not be in upstream in which case the sync
1999 # will fail.
2000 # * otherwise, fetch all branches to make sure we end up with the
2001 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002002 if self.upstream:
2003 current_branch_only = not ID_RE.match(self.upstream)
2004 else:
2005 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002006
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002007 if not name:
2008 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002009
2010 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002011 remote = self.GetRemote(name)
2012 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002013 ssh_proxy = True
2014
Shawn O. Pearce88443382010-10-08 10:02:09 +02002015 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002016 if alt_dir and 'objects' == os.path.basename(alt_dir):
2017 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002018 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2019 remote = self.GetRemote(name)
2020
David Pursehouse8a68ff92012-09-24 12:15:13 +09002021 all_refs = self.bare_ref.all
2022 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002023 tmp = set()
2024
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302025 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002026 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002027 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002028 all_refs[r] = ref_id
2029 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002030 continue
2031
David Pursehouse8a68ff92012-09-24 12:15:13 +09002032 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002033 continue
2034
David Pursehouse8a68ff92012-09-24 12:15:13 +09002035 r = 'refs/_alt/%s' % ref_id
2036 all_refs[r] = ref_id
2037 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002038 tmp.add(r)
2039
Shawn O. Pearce88443382010-10-08 10:02:09 +02002040 tmp_packed = ''
2041 old_packed = ''
2042
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302043 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002044 line = '%s %s\n' % (all_refs[r], r)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002045 tmp_packed += line
2046 if r not in tmp:
2047 old_packed += line
2048
2049 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002050 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002051 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002052
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002053 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002054
Conley Owensf97e8382015-01-21 11:12:46 -08002055 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002056 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002057 else:
2058 # If this repo has shallow objects, then we don't know which refs have
2059 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2060 # do this with projects that don't have shallow objects, since it is less
2061 # efficient.
2062 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2063 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002064
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002065 if quiet:
2066 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002067 if not self.worktree:
2068 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002069 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002070
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002071 # If using depth then we should not get all the tags since they may
2072 # be outside of the depth.
2073 if no_tags or depth:
2074 cmd.append('--no-tags')
2075 else:
2076 cmd.append('--tags')
2077
David Pursehouse74cfd272015-10-14 10:50:15 +09002078 if prune:
2079 cmd.append('--prune')
2080
Conley Owens80b87fe2014-05-09 17:13:44 -07002081 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002082 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002083 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002084 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002085 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002086 spec.append('tag')
2087 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002088
David Pursehouse403b64e2015-04-27 10:41:33 +09002089 if not self.manifest.IsMirror:
2090 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002091 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002092 # Shallow checkout of a specific commit, fetch from that commit and not
2093 # the heads only as the commit might be deeper in the history.
2094 spec.append(branch)
2095 else:
2096 if is_sha1:
2097 branch = self.upstream
2098 if branch is not None and branch.strip():
2099 if not branch.startswith('refs/'):
2100 branch = R_HEADS + branch
2101 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002102 cmd.extend(spec)
2103
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002104 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002105 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002106 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002107 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002108 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002109 ok = True
2110 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002111 # If needed, run the 'git remote prune' the first time through the loop
2112 elif (not _i and
2113 "error:" in gitcmd.stderr and
2114 "git remote prune" in gitcmd.stderr):
2115 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002116 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002117 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002118 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002119 break
2120 continue
Brian Harring14a66742012-09-28 20:21:57 -07002121 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002122 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2123 # in sha1 mode, we just tried sync'ing from the upstream field; it
2124 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002125 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002126 elif ret < 0:
2127 # Git died with a signal, exit immediately
2128 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002129 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002130
2131 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002132 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002133 if old_packed != '':
2134 _lwrite(packed_refs, old_packed)
2135 else:
2136 os.remove(packed_refs)
2137 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002138
2139 if is_sha1 and current_branch_only and self.upstream:
2140 # We just synced the upstream given branch; verify we
2141 # got what we wanted, else trigger a second run of all
2142 # refs.
Chris AtLee2fb64662014-01-16 21:32:33 -05002143 if not self._CheckForSha1():
Kevin Degi679bac42015-06-22 15:31:26 -06002144 if not depth:
2145 # Avoid infinite recursion when depth is True (since depth implies
2146 # current_branch_only)
2147 return self._RemoteFetch(name=name, current_branch_only=False,
2148 initial=False, quiet=quiet, alt_dir=alt_dir)
2149 if self.clone_depth:
2150 self.clone_depth = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002151 return self._RemoteFetch(name=name,
2152 current_branch_only=current_branch_only,
Kevin Degi679bac42015-06-22 15:31:26 -06002153 initial=False, quiet=quiet, alt_dir=alt_dir)
Brian Harring14a66742012-09-28 20:21:57 -07002154
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002155 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002156
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002157 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002158 if initial and \
2159 (self.manifest.manifestProject.config.GetString('repo.depth') or
2160 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002161 return False
2162
2163 remote = self.GetRemote(self.remote.name)
2164 bundle_url = remote.url + '/clone.bundle'
2165 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002166 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2167 'persistent-http',
2168 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002169 return False
2170
2171 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2172 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2173
2174 exist_dst = os.path.exists(bundle_dst)
2175 exist_tmp = os.path.exists(bundle_tmp)
2176
2177 if not initial and not exist_dst and not exist_tmp:
2178 return False
2179
2180 if not exist_dst:
2181 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2182 if not exist_dst:
2183 return False
2184
2185 cmd = ['fetch']
2186 if quiet:
2187 cmd.append('--quiet')
2188 if not self.worktree:
2189 cmd.append('--update-head-ok')
2190 cmd.append(bundle_dst)
2191 for f in remote.fetch:
2192 cmd.append(str(f))
2193 cmd.append('refs/tags/*:refs/tags/*')
2194
2195 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002196 if os.path.exists(bundle_dst):
2197 os.remove(bundle_dst)
2198 if os.path.exists(bundle_tmp):
2199 os.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002200 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002201
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002202 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002203 if os.path.exists(dstPath):
2204 os.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002205
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002206 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002207 if quiet:
2208 cmd += ['--silent']
2209 if os.path.exists(tmpPath):
2210 size = os.stat(tmpPath).st_size
2211 if size >= 1024:
2212 cmd += ['--continue-at', '%d' % (size,)]
2213 else:
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002214 os.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002215 if 'http_proxy' in os.environ and 'darwin' == sys.platform:
2216 cmd += ['--proxy', os.environ['http_proxy']]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002217 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, _proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002218 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002219 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Dave Borowitz137d0132015-01-02 11:12:54 -08002220 if srcUrl.startswith('persistent-'):
2221 srcUrl = srcUrl[len('persistent-'):]
2222 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002223
Dave Borowitz137d0132015-01-02 11:12:54 -08002224 if IsTrace():
2225 Trace('%s', ' '.join(cmd))
2226 try:
2227 proc = subprocess.Popen(cmd)
2228 except OSError:
2229 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002230
Dave Borowitz137d0132015-01-02 11:12:54 -08002231 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002232
Dave Borowitz137d0132015-01-02 11:12:54 -08002233 if curlret == 22:
2234 # From curl man page:
2235 # 22: HTTP page not retrieved. The requested url was not found or
2236 # returned another error with the HTTP error code being 400 or above.
2237 # This return code only appears if -f, --fail is used.
2238 if not quiet:
2239 print("Server does not provide clone.bundle; ignoring.",
2240 file=sys.stderr)
2241 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002242
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002243 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002244 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002245 os.rename(tmpPath, dstPath)
2246 return True
2247 else:
2248 os.remove(tmpPath)
2249 return False
2250 else:
2251 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002252
Kris Giesingc8d882a2014-12-23 13:02:32 -08002253 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002254 try:
2255 with open(path) as f:
2256 if f.read(16) == '# v2 git bundle\n':
2257 return True
2258 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002259 if not quiet:
2260 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002261 return False
2262 except OSError:
2263 return False
2264
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002265 def _Checkout(self, rev, quiet=False):
2266 cmd = ['checkout']
2267 if quiet:
2268 cmd.append('-q')
2269 cmd.append(rev)
2270 cmd.append('--')
2271 if GitCommand(self, cmd).Wait() != 0:
2272 if self._allrefs:
2273 raise GitError('%s checkout %s ' % (self.name, rev))
2274
Anthony King7bdac712014-07-16 12:56:40 +01002275 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002276 cmd = ['cherry-pick']
2277 cmd.append(rev)
2278 cmd.append('--')
2279 if GitCommand(self, cmd).Wait() != 0:
2280 if self._allrefs:
2281 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2282
Anthony King7bdac712014-07-16 12:56:40 +01002283 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002284 cmd = ['revert']
2285 cmd.append('--no-edit')
2286 cmd.append(rev)
2287 cmd.append('--')
2288 if GitCommand(self, cmd).Wait() != 0:
2289 if self._allrefs:
2290 raise GitError('%s revert %s ' % (self.name, rev))
2291
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002292 def _ResetHard(self, rev, quiet=True):
2293 cmd = ['reset', '--hard']
2294 if quiet:
2295 cmd.append('-q')
2296 cmd.append(rev)
2297 if GitCommand(self, cmd).Wait() != 0:
2298 raise GitError('%s reset --hard %s ' % (self.name, rev))
2299
Anthony King7bdac712014-07-16 12:56:40 +01002300 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002301 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002302 if onto is not None:
2303 cmd.extend(['--onto', onto])
2304 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002305 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002306 raise GitError('%s rebase %s ' % (self.name, upstream))
2307
Pierre Tardy3d125942012-05-04 12:18:12 +02002308 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002309 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002310 if ffonly:
2311 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002312 if GitCommand(self, cmd).Wait() != 0:
2313 raise GitError('%s merge %s ' % (self.name, head))
2314
Kevin Degiabaa7f32014-11-12 11:27:45 -07002315 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002316 init_git_dir = not os.path.exists(self.gitdir)
2317 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002318 try:
2319 # Initialize the bare repository, which contains all of the objects.
2320 if init_obj_dir:
2321 os.makedirs(self.objdir)
2322 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002323
Kevin Degib1a07b82015-07-27 13:33:43 -06002324 # If we have a separate directory to hold refs, initialize it as well.
2325 if self.objdir != self.gitdir:
2326 if init_git_dir:
2327 os.makedirs(self.gitdir)
2328
2329 if init_obj_dir or init_git_dir:
2330 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2331 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002332 try:
2333 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2334 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002335 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002336 print("Retrying clone after deleting %s" %
2337 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002338 try:
2339 shutil.rmtree(os.path.realpath(self.gitdir))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002340 if self.worktree and os.path.exists(os.path.realpath
2341 (self.worktree)):
Kevin Degiabaa7f32014-11-12 11:27:45 -07002342 shutil.rmtree(os.path.realpath(self.worktree))
2343 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2344 except:
2345 raise e
2346 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002347
Kevin Degi384b3c52014-10-16 16:02:58 -06002348 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002349 mp = self.manifest.manifestProject
2350 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002351
Kevin Degib1a07b82015-07-27 13:33:43 -06002352 if ref_dir or mirror_git:
2353 if not mirror_git:
2354 mirror_git = os.path.join(ref_dir, self.name + '.git')
2355 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2356 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002357
Kevin Degib1a07b82015-07-27 13:33:43 -06002358 if os.path.exists(mirror_git):
2359 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002360
Kevin Degib1a07b82015-07-27 13:33:43 -06002361 elif os.path.exists(repo_git):
2362 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002363
Kevin Degib1a07b82015-07-27 13:33:43 -06002364 else:
2365 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002366
Kevin Degib1a07b82015-07-27 13:33:43 -06002367 if ref_dir:
2368 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2369 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002370
Kevin Degib1a07b82015-07-27 13:33:43 -06002371 self._UpdateHooks()
2372
2373 m = self.manifest.manifestProject.config
2374 for key in ['user.name', 'user.email']:
2375 if m.Has(key, include_defaults=False):
2376 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002377 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Kevin Degib1a07b82015-07-27 13:33:43 -06002378 if self.manifest.IsMirror:
2379 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002380 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002381 self.config.SetString('core.bare', None)
2382 except Exception:
2383 if init_obj_dir and os.path.exists(self.objdir):
2384 shutil.rmtree(self.objdir)
2385 if init_git_dir and os.path.exists(self.gitdir):
2386 shutil.rmtree(self.gitdir)
2387 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002388
Jimmie Westera0444582012-10-24 13:44:42 +02002389 def _UpdateHooks(self):
2390 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002391 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002392
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002393 def _InitHooks(self):
Jesse Hall672cc492013-11-27 11:17:13 -08002394 hooks = os.path.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002395 if not os.path.exists(hooks):
2396 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002397 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002398 name = os.path.basename(stock_hook)
2399
Victor Boivie65e0f352011-04-18 11:23:29 +02002400 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002401 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002402 # Don't install a Gerrit Code Review hook if this
2403 # project does not appear to use it for reviews.
2404 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002405 # Since the manifest project is one of those, but also
2406 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002407 continue
2408
2409 dst = os.path.join(hooks, name)
2410 if os.path.islink(dst):
2411 continue
2412 if os.path.exists(dst):
2413 if filecmp.cmp(stock_hook, dst, shallow=False):
2414 os.remove(dst)
2415 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002416 _warn("%s: Not replacing locally modified %s hook",
2417 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002418 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002419 try:
Mickaël Salaünb9477bc2012-08-05 13:39:26 +02002420 os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002421 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002422 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002423 raise GitError('filesystem must support symlinks')
2424 else:
2425 raise
2426
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002427 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002428 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002429 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002430 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002431 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002432 remote.review = self.remote.review
2433 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002434
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002435 if self.worktree:
2436 remote.ResetFetch(mirror=False)
2437 else:
2438 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002439 remote.Save()
2440
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002441 def _InitMRef(self):
2442 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002443 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002444
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002445 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002446 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002447
2448 def _InitAnyMRef(self, ref):
2449 cur = self.bare_ref.symref(ref)
2450
2451 if self.revisionId:
2452 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2453 msg = 'manifest set to %s' % self.revisionId
2454 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002455 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002456 else:
2457 remote = self.GetRemote(self.remote.name)
2458 dst = remote.ToLocal(self.revisionExpr)
2459 if cur != dst:
2460 msg = 'manifest set to %s' % self.revisionExpr
2461 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002462
Kevin Degi384b3c52014-10-16 16:02:58 -06002463 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002464 symlink_files = self.shareable_files[:]
2465 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002466 if share_refs:
2467 symlink_files += self.working_tree_files
2468 symlink_dirs += self.working_tree_dirs
2469 to_symlink = symlink_files + symlink_dirs
2470 for name in set(to_symlink):
2471 dst = os.path.realpath(os.path.join(destdir, name))
2472 if os.path.lexists(dst):
2473 src = os.path.realpath(os.path.join(srcdir, name))
2474 # Fail if the links are pointing to the wrong place
2475 if src != dst:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002476 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002477 'work tree. If you\'re comfortable with the '
2478 'possibility of losing the work tree\'s git metadata,'
2479 ' use `repo sync --force-sync {0}` to '
2480 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002481
David James8d201162013-10-11 17:03:19 -07002482 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2483 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2484
2485 Args:
2486 gitdir: The bare git repository. Must already be initialized.
2487 dotgit: The repository you would like to initialize.
2488 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2489 Only one work tree can store refs under a given |gitdir|.
2490 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2491 This saves you the effort of initializing |dotgit| yourself.
2492 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002493 symlink_files = self.shareable_files[:]
2494 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002495 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002496 symlink_files += self.working_tree_files
2497 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002498 to_symlink = symlink_files + symlink_dirs
2499
2500 to_copy = []
2501 if copy_all:
2502 to_copy = os.listdir(gitdir)
2503
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002504 dotgit = os.path.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002505 for name in set(to_copy).union(to_symlink):
2506 try:
2507 src = os.path.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002508 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002509
Kevin Degi384b3c52014-10-16 16:02:58 -06002510 if os.path.lexists(dst):
2511 continue
David James8d201162013-10-11 17:03:19 -07002512
2513 # If the source dir doesn't exist, create an empty dir.
2514 if name in symlink_dirs and not os.path.lexists(src):
2515 os.makedirs(src)
2516
Conley Owens80b87fe2014-05-09 17:13:44 -07002517 # If the source file doesn't exist, ensure the destination
2518 # file doesn't either.
2519 if name in symlink_files and not os.path.lexists(src):
2520 try:
2521 os.remove(dst)
2522 except OSError:
2523 pass
2524
David James8d201162013-10-11 17:03:19 -07002525 if name in to_symlink:
2526 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2527 elif copy_all and not os.path.islink(dst):
2528 if os.path.isdir(src):
2529 shutil.copytree(src, dst)
2530 elif os.path.isfile(src):
2531 shutil.copy(src, dst)
2532 except OSError as e:
2533 if e.errno == errno.EPERM:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002534 raise DownloadError('filesystem must support symlinks')
David James8d201162013-10-11 17:03:19 -07002535 else:
2536 raise
2537
Kevin Degiabaa7f32014-11-12 11:27:45 -07002538 def _InitWorkTree(self, force_sync=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002539 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002540 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002541 try:
2542 if init_dotgit:
2543 os.makedirs(dotgit)
2544 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2545 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002546
Kevin Degiabaa7f32014-11-12 11:27:45 -07002547 try:
2548 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2549 except GitError as e:
2550 if force_sync:
2551 try:
2552 shutil.rmtree(dotgit)
2553 return self._InitWorkTree(force_sync=False)
2554 except:
2555 raise e
2556 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002557
Kevin Degib1a07b82015-07-27 13:33:43 -06002558 if init_dotgit:
2559 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002560
Kevin Degib1a07b82015-07-27 13:33:43 -06002561 cmd = ['read-tree', '--reset', '-u']
2562 cmd.append('-v')
2563 cmd.append(HEAD)
2564 if GitCommand(self, cmd).Wait() != 0:
2565 raise GitError("cannot initialize work tree")
Victor Boivie0960b5b2010-11-26 13:42:13 +01002566
Kevin Degib1a07b82015-07-27 13:33:43 -06002567 self._CopyAndLinkFiles()
2568 except Exception:
2569 if init_dotgit:
2570 shutil.rmtree(dotgit)
2571 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002572
2573 def _gitdir_path(self, path):
David James8d201162013-10-11 17:03:19 -07002574 return os.path.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002575
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002576 def _revlist(self, *args, **kw):
2577 a = []
2578 a.extend(args)
2579 a.append('--')
2580 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002581
2582 @property
2583 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002584 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002585
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002586 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002587 """Get logs between two revisions of this project."""
2588 comp = '..'
2589 if rev1:
2590 revs = [rev1]
2591 if rev2:
2592 revs.extend([comp, rev2])
2593 cmd = ['log', ''.join(revs)]
2594 out = DiffColoring(self.config)
2595 if out.is_on and color:
2596 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002597 if pretty_format is not None:
2598 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002599 if oneline:
2600 cmd.append('--oneline')
2601
2602 try:
2603 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2604 if log.Wait() == 0:
2605 return log.stdout
2606 except GitError:
2607 # worktree may not exist if groups changed for example. In that case,
2608 # try in gitdir instead.
2609 if not os.path.exists(self.worktree):
2610 return self.bare_git.log(*cmd[1:])
2611 else:
2612 raise
2613 return None
2614
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002615 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2616 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002617 """Get the list of logs from this revision to given revisionId"""
2618 logs = {}
2619 selfId = self.GetRevisionId(self._allrefs)
2620 toId = toProject.GetRevisionId(toProject._allrefs)
2621
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002622 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2623 pretty_format=pretty_format)
2624 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2625 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002626 return logs
2627
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002628 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002629
David James8d201162013-10-11 17:03:19 -07002630 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002631 self._project = project
2632 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002633 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002634
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002635 def LsOthers(self):
2636 p = GitCommand(self._project,
2637 ['ls-files',
2638 '-z',
2639 '--others',
2640 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002641 bare=False,
David James8d201162013-10-11 17:03:19 -07002642 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002643 capture_stdout=True,
2644 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002645 if p.Wait() == 0:
2646 out = p.stdout
2647 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002648 # Backslash is not anomalous
David Pursehouse1d947b32012-10-25 12:23:11 +09002649 return out[:-1].split('\0') # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002650 return []
2651
2652 def DiffZ(self, name, *args):
2653 cmd = [name]
2654 cmd.append('-z')
2655 cmd.extend(args)
2656 p = GitCommand(self._project,
2657 cmd,
David James8d201162013-10-11 17:03:19 -07002658 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002659 bare=False,
2660 capture_stdout=True,
2661 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002662 try:
2663 out = p.process.stdout.read()
2664 r = {}
2665 if out:
David Pursehouse1d947b32012-10-25 12:23:11 +09002666 out = iter(out[:-1].split('\0')) # pylint: disable=W1401
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002667 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002668 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002669 info = next(out)
2670 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002671 except StopIteration:
2672 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002673
2674 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002675
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002676 def __init__(self, path, omode, nmode, oid, nid, state):
2677 self.path = path
2678 self.src_path = None
2679 self.old_mode = omode
2680 self.new_mode = nmode
2681 self.old_id = oid
2682 self.new_id = nid
2683
2684 if len(state) == 1:
2685 self.status = state
2686 self.level = None
2687 else:
2688 self.status = state[:1]
2689 self.level = state[1:]
2690 while self.level.startswith('0'):
2691 self.level = self.level[1:]
2692
2693 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002694 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002695 if info.status in ('R', 'C'):
2696 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002697 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002698 r[info.path] = info
2699 return r
2700 finally:
2701 p.Wait()
2702
2703 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002704 if self._bare:
2705 path = os.path.join(self._project.gitdir, HEAD)
2706 else:
2707 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002708 try:
2709 fd = open(path, 'rb')
Dan Sandler53e902a2014-03-09 13:20:02 -04002710 except IOError as e:
2711 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002712 try:
2713 line = fd.read()
2714 finally:
2715 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302716 try:
2717 line = line.decode()
2718 except AttributeError:
2719 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002720 if line.startswith('ref: '):
2721 return line[5:-1]
2722 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002723
2724 def SetHead(self, ref, message=None):
2725 cmdv = []
2726 if message is not None:
2727 cmdv.extend(['-m', message])
2728 cmdv.append(HEAD)
2729 cmdv.append(ref)
2730 self.symbolic_ref(*cmdv)
2731
2732 def DetachHead(self, new, message=None):
2733 cmdv = ['--no-deref']
2734 if message is not None:
2735 cmdv.extend(['-m', message])
2736 cmdv.append(HEAD)
2737 cmdv.append(new)
2738 self.update_ref(*cmdv)
2739
2740 def UpdateRef(self, name, new, old=None,
2741 message=None,
2742 detach=False):
2743 cmdv = []
2744 if message is not None:
2745 cmdv.extend(['-m', message])
2746 if detach:
2747 cmdv.append('--no-deref')
2748 cmdv.append(name)
2749 cmdv.append(new)
2750 if old is not None:
2751 cmdv.append(old)
2752 self.update_ref(*cmdv)
2753
2754 def DeleteRef(self, name, old=None):
2755 if not old:
2756 old = self.rev_parse(name)
2757 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002758 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002759
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002760 def rev_list(self, *args, **kw):
2761 if 'format' in kw:
2762 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2763 else:
2764 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002765 cmdv.extend(args)
2766 p = GitCommand(self._project,
2767 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002768 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002769 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002770 capture_stdout=True,
2771 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002772 r = []
2773 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002774 if line[-1] == '\n':
2775 line = line[:-1]
2776 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002777 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002778 raise GitError('%s rev-list %s: %s' %
2779 (self._project.name, str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002780 return r
2781
2782 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002783 """Allow arbitrary git commands using pythonic syntax.
2784
2785 This allows you to do things like:
2786 git_obj.rev_parse('HEAD')
2787
2788 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2789 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002790 Any other positional arguments will be passed to the git command, and the
2791 following keyword arguments are supported:
2792 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002793
2794 Args:
2795 name: The name of the git command to call. Any '_' characters will
2796 be replaced with '-'.
2797
2798 Returns:
2799 A callable object that will try to call git with the named command.
2800 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002801 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002802
Dave Borowitz091f8932012-10-23 17:01:04 -07002803 def runner(*args, **kwargs):
2804 cmdv = []
2805 config = kwargs.pop('config', None)
2806 for k in kwargs:
2807 raise TypeError('%s() got an unexpected keyword argument %r'
2808 % (name, k))
2809 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002810 if not git_require((1, 7, 2)):
2811 raise ValueError('cannot set config on command line for %s()'
2812 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302813 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002814 cmdv.append('-c')
2815 cmdv.append('%s=%s' % (k, v))
2816 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002817 cmdv.extend(args)
2818 p = GitCommand(self._project,
2819 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002820 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002821 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002822 capture_stdout=True,
2823 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002824 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002825 raise GitError('%s %s: %s' %
2826 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002827 r = p.stdout
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302828 try:
Conley Owensedd01512013-09-26 12:59:58 -07002829 r = r.decode('utf-8')
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302830 except AttributeError:
2831 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002832 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2833 return r[:-1]
2834 return r
2835 return runner
2836
2837
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002838class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002839
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002840 def __str__(self):
2841 return 'prior sync failed; rebase still in progress'
2842
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002843
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002844class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002845
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002846 def __str__(self):
2847 return 'contains uncommitted changes'
2848
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002849
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002850class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002851
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002852 def __init__(self, project, text):
2853 self.project = project
2854 self.text = text
2855
2856 def Print(self, syncbuf):
2857 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
2858 syncbuf.out.nl()
2859
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002860
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002861class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002862
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002863 def __init__(self, project, why):
2864 self.project = project
2865 self.why = why
2866
2867 def Print(self, syncbuf):
2868 syncbuf.out.fail('error: %s/: %s',
2869 self.project.relpath,
2870 str(self.why))
2871 syncbuf.out.nl()
2872
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002873
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002874class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002875
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002876 def __init__(self, project, action):
2877 self.project = project
2878 self.action = action
2879
2880 def Run(self, syncbuf):
2881 out = syncbuf.out
2882 out.project('project %s/', self.project.relpath)
2883 out.nl()
2884 try:
2885 self.action()
2886 out.nl()
2887 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09002888 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002889 out.nl()
2890 return False
2891
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002892
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002893class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002894
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002895 def __init__(self, config):
2896 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01002897 self.project = self.printer('header', attr='bold')
2898 self.info = self.printer('info')
2899 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002900
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002901
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002902class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002903
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002904 def __init__(self, config, detach_head=False):
2905 self._messages = []
2906 self._failures = []
2907 self._later_queue1 = []
2908 self._later_queue2 = []
2909
2910 self.out = _SyncColoring(config)
2911 self.out.redirect(sys.stderr)
2912
2913 self.detach_head = detach_head
2914 self.clean = True
2915
2916 def info(self, project, fmt, *args):
2917 self._messages.append(_InfoMessage(project, fmt % args))
2918
2919 def fail(self, project, err=None):
2920 self._failures.append(_Failure(project, err))
2921 self.clean = False
2922
2923 def later1(self, project, what):
2924 self._later_queue1.append(_Later(project, what))
2925
2926 def later2(self, project, what):
2927 self._later_queue2.append(_Later(project, what))
2928
2929 def Finish(self):
2930 self._PrintMessages()
2931 self._RunLater()
2932 self._PrintMessages()
2933 return self.clean
2934
2935 def _RunLater(self):
2936 for q in ['_later_queue1', '_later_queue2']:
2937 if not self._RunQueue(q):
2938 return
2939
2940 def _RunQueue(self, queue):
2941 for m in getattr(self, queue):
2942 if not m.Run(self):
2943 self.clean = False
2944 return False
2945 setattr(self, queue, [])
2946 return True
2947
2948 def _PrintMessages(self):
2949 for m in self._messages:
2950 m.Print(self)
2951 for m in self._failures:
2952 m.Print(self)
2953
2954 self._messages = []
2955 self._failures = []
2956
2957
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002958class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002959
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002960 """A special project housed under .repo.
2961 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002962
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002963 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002964 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01002965 manifest=manifest,
2966 name=name,
2967 gitdir=gitdir,
2968 objdir=gitdir,
2969 worktree=worktree,
2970 remote=RemoteSpec('origin'),
2971 relpath='.repo/%s' % name,
2972 revisionExpr='refs/heads/master',
2973 revisionId=None,
2974 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002975
2976 def PreSync(self):
2977 if self.Exists:
2978 cb = self.CurrentBranch
2979 if cb:
2980 base = self.GetBranch(cb).merge
2981 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002982 self.revisionExpr = base
2983 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002984
Anthony King7bdac712014-07-16 12:56:40 +01002985 def MetaBranchSwitch(self):
Florian Vallee5d016502012-06-07 17:19:26 +02002986 """ Prepare MetaProject for manifest branch switch
2987 """
2988
2989 # detach and delete manifest branch, allowing a new
2990 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01002991 syncbuf = SyncBuffer(self.config, detach_head=True)
Florian Vallee5d016502012-06-07 17:19:26 +02002992 self.Sync_LocalHalf(syncbuf)
2993 syncbuf.Finish()
2994
2995 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002996 ['update-ref', '-d', 'refs/heads/default'],
2997 capture_stdout=True,
2998 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02002999
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003000 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003001 def LastFetch(self):
3002 try:
3003 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3004 return os.path.getmtime(fh)
3005 except OSError:
3006 return 0
3007
3008 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003009 def HasChanges(self):
3010 """Has the remote received new commits not yet checked out?
3011 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003012 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003013 return False
3014
David Pursehouse8a68ff92012-09-24 12:15:13 +09003015 all_refs = self.bare_ref.all
3016 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003017 head = self.work_git.GetHead()
3018 if head.startswith(R_HEADS):
3019 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003020 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003021 except KeyError:
3022 head = None
3023
3024 if revid == head:
3025 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003026 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003027 return True
3028 return False