blob: 6c6534d8d682807cc6d04eb0dd5fc951b20bbf62 [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
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080015import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070016import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070017import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070019import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import re
21import shutil
22import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070023import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020025import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080026import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070027import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040028import urllib.parse
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070029
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070031from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070032from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
33 ID_RE
Remy Bohmer16c13282020-09-10 10:38:04 +020034from error import GitError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040035from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080036from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070037import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040038import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040039from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050041from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070043
George Engelbrecht9bc283e2020-04-02 12:36:09 -060044# Maximum sleep time allowed during retries.
45MAXIMUM_RETRY_SLEEP_SEC = 3600.0
46# +-10% random jitter is added to each Fetches retry sleep duration.
47RETRY_JITTER_PERCENT = 0.1
48
49
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070050def _lwrite(path, content):
51 lock = '%s.lock' % path
52
Remy Bohmer169b0212020-11-21 10:57:52 +010053 # Maintain Unix line endings on all OS's to match git behavior.
54 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070055 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070056
57 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070058 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070059 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080060 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070061 raise
62
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070063
Shawn O. Pearce48244782009-04-16 08:25:57 -070064def _error(fmt, *args):
65 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070066 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070067
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070068
David Pursehousef33929d2015-08-24 14:39:14 +090069def _warn(fmt, *args):
70 msg = fmt % args
71 print('warn: %s' % msg, file=sys.stderr)
72
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070073
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070074def not_rev(r):
75 return '^' + r
76
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070077
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080078def sq(r):
79 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080080
David Pursehouse819827a2020-02-12 15:20:19 +090081
Jonathan Nieder93719792015-03-17 11:29:58 -070082_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070083
84
Jonathan Nieder93719792015-03-17 11:29:58 -070085def _ProjectHooks():
86 """List the hooks present in the 'hooks' directory.
87
88 These hooks are project hooks and are copied to the '.git/hooks' directory
89 of all subprojects.
90
91 This function caches the list of hooks (based on the contents of the
92 'repo/hooks' directory) on the first call.
93
94 Returns:
95 A list of absolute paths to all of the files in the hooks directory.
96 """
97 global _project_hook_list
98 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -070099 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700100 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700101 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700102 return _project_hook_list
103
104
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700105class DownloadedChange(object):
106 _commit_cache = None
107
108 def __init__(self, project, base, change_id, ps_id, commit):
109 self.project = project
110 self.base = base
111 self.change_id = change_id
112 self.ps_id = ps_id
113 self.commit = commit
114
115 @property
116 def commits(self):
117 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700118 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
119 '--abbrev-commit',
120 '--pretty=oneline',
121 '--reverse',
122 '--date-order',
123 not_rev(self.base),
124 self.commit,
125 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700126 return self._commit_cache
127
128
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700129class ReviewableBranch(object):
130 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400131 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700132
133 def __init__(self, project, branch, base):
134 self.project = project
135 self.branch = branch
136 self.base = base
137
138 @property
139 def name(self):
140 return self.branch.name
141
142 @property
143 def commits(self):
144 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400145 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
146 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
147 try:
148 self._commit_cache = self.project.bare_git.rev_list(*args)
149 except GitError:
150 # We weren't able to probe the commits for this branch. Was it tracking
151 # a branch that no longer exists? If so, return no commits. Otherwise,
152 # rethrow the error as we don't know what's going on.
153 if self.base_exists:
154 raise
155
156 self._commit_cache = []
157
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158 return self._commit_cache
159
160 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800161 def unabbrev_commits(self):
162 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700163 for commit in self.project.bare_git.rev_list(not_rev(self.base),
164 R_HEADS + self.name,
165 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800166 r[commit[0:8]] = commit
167 return r
168
169 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700171 return self.project.bare_git.log('--pretty=format:%cd',
172 '-n', '1',
173 R_HEADS + self.name,
174 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175
Mike Frysinger6da17752019-09-11 18:43:17 -0400176 @property
177 def base_exists(self):
178 """Whether the branch we're tracking exists.
179
180 Normally it should, but sometimes branches we track can get deleted.
181 """
182 if self._base_exists is None:
183 try:
184 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
185 # If we're still here, the base branch exists.
186 self._base_exists = True
187 except GitError:
188 # If we failed to verify, the base branch doesn't exist.
189 self._base_exists = False
190
191 return self._base_exists
192
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700193 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500194 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700195 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500196 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500197 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200198 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700199 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200200 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200201 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800202 validate_certs=True,
203 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500204 self.project.UploadForReview(branch=self.name,
205 people=people,
206 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700207 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500208 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500209 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200210 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700211 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200212 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200213 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800214 validate_certs=validate_certs,
215 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700217 def GetPublishedRefs(self):
218 refs = {}
219 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700220 self.branch.remote.SshReviewUrl(self.project.UserEmail),
221 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700222 for line in output.split('\n'):
223 try:
224 (sha, ref) = line.split()
225 refs[sha] = ref
226 except ValueError:
227 pass
228
229 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700231
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700233
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234 def __init__(self, config):
235 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100236 self.project = self.printer('header', attr='bold')
237 self.branch = self.printer('header', attr='bold')
238 self.nobranch = self.printer('nobranch', fg='red')
239 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240
Anthony King7bdac712014-07-16 12:56:40 +0100241 self.added = self.printer('added', fg='green')
242 self.changed = self.printer('changed', fg='red')
243 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
245
246class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700247
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248 def __init__(self, config):
249 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100250 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400251 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700253
Anthony King7bdac712014-07-16 12:56:40 +0100254class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700255
James W. Mills24c13082012-04-12 15:04:13 -0500256 def __init__(self, name, value, keep):
257 self.name = name
258 self.value = value
259 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700260
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700261
Mike Frysingere6a202f2019-08-02 15:57:57 -0400262def _SafeExpandPath(base, subpath, skipfinal=False):
263 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700264
Mike Frysingere6a202f2019-08-02 15:57:57 -0400265 We make sure no intermediate symlinks are traversed, and that the final path
266 is not a special file (e.g. not a socket or fifo).
267
268 NB: We rely on a number of paths already being filtered out while parsing the
269 manifest. See the validation logic in manifest_xml.py for more details.
270 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500271 # Split up the path by its components. We can't use os.path.sep exclusively
272 # as some platforms (like Windows) will convert / to \ and that bypasses all
273 # our constructed logic here. Especially since manifest authors only use
274 # / in their paths.
275 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
276 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400277 if skipfinal:
278 # Whether the caller handles the final component itself.
279 finalpart = components.pop()
280
281 path = base
282 for part in components:
283 if part in {'.', '..'}:
284 raise ManifestInvalidPathError(
285 '%s: "%s" not allowed in paths' % (subpath, part))
286
287 path = os.path.join(path, part)
288 if platform_utils.islink(path):
289 raise ManifestInvalidPathError(
290 '%s: traversing symlinks not allow' % (path,))
291
292 if os.path.exists(path):
293 if not os.path.isfile(path) and not platform_utils.isdir(path):
294 raise ManifestInvalidPathError(
295 '%s: only regular files & directories allowed' % (path,))
296
297 if skipfinal:
298 path = os.path.join(path, finalpart)
299
300 return path
301
302
303class _CopyFile(object):
304 """Container for <copyfile> manifest element."""
305
306 def __init__(self, git_worktree, src, topdir, dest):
307 """Register a <copyfile> request.
308
309 Args:
310 git_worktree: Absolute path to the git project checkout.
311 src: Relative path under |git_worktree| of file to read.
312 topdir: Absolute path to the top of the repo client checkout.
313 dest: Relative path under |topdir| of file to write.
314 """
315 self.git_worktree = git_worktree
316 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700317 self.src = src
318 self.dest = dest
319
320 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400321 src = _SafeExpandPath(self.git_worktree, self.src)
322 dest = _SafeExpandPath(self.topdir, self.dest)
323
324 if platform_utils.isdir(src):
325 raise ManifestInvalidPathError(
326 '%s: copying from directory not supported' % (self.src,))
327 if platform_utils.isdir(dest):
328 raise ManifestInvalidPathError(
329 '%s: copying to directory not allowed' % (self.dest,))
330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700331 # copy file if it does not exist or is out of date
332 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
333 try:
334 # remove existing file first, since it might be read-only
335 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800336 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400337 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200338 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700339 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200340 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700341 shutil.copy(src, dest)
342 # make the file read-only
343 mode = os.stat(dest)[stat.ST_MODE]
344 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
345 os.chmod(dest, mode)
346 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700347 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700349
Anthony King7bdac712014-07-16 12:56:40 +0100350class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400351 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700352
Mike Frysingere6a202f2019-08-02 15:57:57 -0400353 def __init__(self, git_worktree, src, topdir, dest):
354 """Register a <linkfile> request.
355
356 Args:
357 git_worktree: Absolute path to the git project checkout.
358 src: Target of symlink relative to path under |git_worktree|.
359 topdir: Absolute path to the top of the repo client checkout.
360 dest: Relative path under |topdir| of symlink to create.
361 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700362 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400363 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500364 self.src = src
365 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500366
Wink Saville4c426ef2015-06-03 08:05:17 -0700367 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500368 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700369 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500370 try:
371 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800372 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800373 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500374 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700375 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700376 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500377 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700378 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500379 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700380 _error('Cannot link file %s to %s', relSrc, absDest)
381
382 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400383 """Link the self.src & self.dest paths.
384
385 Handles wild cards on the src linking all of the files in the source in to
386 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700387 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500388 # Some people use src="." to create stable links to projects. Lets allow
389 # that but reject all other uses of "." to keep things simple.
390 if self.src == '.':
391 src = self.git_worktree
392 else:
393 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400394
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300395 if not glob.has_magic(src):
396 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400397 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
398 # dest & src are absolute paths at this point. Make sure the target of
399 # the symlink is relative in the context of the repo client checkout.
400 relpath = os.path.relpath(src, os.path.dirname(dest))
401 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700402 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400403 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300404 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400405 if os.path.exists(dest) and not platform_utils.isdir(dest):
406 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700407 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400408 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700409 # Create a releative path from source dir to destination dir
410 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400411 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700412
413 # Get the source file name
414 srcFile = os.path.basename(absSrcFile)
415
416 # Now form the final full paths to srcFile. They will be
417 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400418 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700419 relSrc = os.path.join(relSrcDir, srcFile)
420 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500421
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700422
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700423class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700424
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700425 def __init__(self,
426 name,
Anthony King7bdac712014-07-16 12:56:40 +0100427 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700428 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100429 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700430 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700431 orig_name=None,
432 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700433 self.name = name
434 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700435 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700436 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100437 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700438 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700439 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700440
441class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600442 # These objects can be shared between several working trees.
443 shareable_files = ['description', 'info']
444 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
445 # These objects can only be used by a single working tree.
446 working_tree_files = ['config', 'packed-refs', 'shallow']
447 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700448
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700449 def __init__(self,
450 manifest,
451 name,
452 remote,
453 gitdir,
David James8d201162013-10-11 17:03:19 -0700454 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700455 worktree,
456 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700457 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800458 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100459 rebase=True,
460 groups=None,
461 sync_c=False,
462 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900463 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100464 clone_depth=None,
465 upstream=None,
466 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500467 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100468 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900469 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700470 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600471 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700472 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800473 """Init a Project object.
474
475 Args:
476 manifest: The XmlManifest object.
477 name: The `name` attribute of manifest.xml's project element.
478 remote: RemoteSpec object specifying its remote's properties.
479 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700480 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800481 worktree: Absolute path of git working tree.
482 relpath: Relative path of git working tree to repo's top directory.
483 revisionExpr: The `revision` attribute of manifest.xml's project element.
484 revisionId: git commit id for checking out.
485 rebase: The `rebase` attribute of manifest.xml's project element.
486 groups: The `groups` attribute of manifest.xml's project element.
487 sync_c: The `sync-c` attribute of manifest.xml's project element.
488 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900489 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800490 upstream: The `upstream` attribute of manifest.xml's project element.
491 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500492 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800493 is_derived: False if the project was explicitly defined in the manifest;
494 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400495 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900496 optimized_fetch: If True, when a project is set to a sha1 revision, only
497 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600498 retry_fetches: Retry remote fetches n times upon receiving transient error
499 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700500 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800501 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400502 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700503 self.name = name
504 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800505 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700506 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800507 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700508 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800509 else:
510 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700511 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700512 self.revisionExpr = revisionExpr
513
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700514 if revisionId is None \
515 and revisionExpr \
516 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700517 self.revisionId = revisionExpr
518 else:
519 self.revisionId = revisionId
520
Mike Pontillod3153822012-02-28 11:53:24 -0800521 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700522 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700523 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800524 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900525 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900526 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700527 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800528 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500529 # NB: Do not use this setting in __init__ to change behavior so that the
530 # manifest.git checkout can inspect & change it after instantiating. See
531 # the XmlManifest init code for more info.
532 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800533 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900534 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600535 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800536 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800537
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700538 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700539 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500540 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500541 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700542 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400543 defaults=self.client.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700544
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800545 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700546 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800547 else:
548 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700549 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700550 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700551 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400552 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700553 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700554
Doug Anderson37282b42011-03-04 11:54:18 -0800555 # This will be filled in if a project is later identified to be the
556 # project containing repo hooks.
557 self.enabled_repo_hooks = []
558
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700559 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800560 def Derived(self):
561 return self.is_derived
562
563 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700564 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700565 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700566
567 @property
568 def CurrentBranch(self):
569 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400570
571 The branch name omits the 'refs/heads/' prefix.
572 None is returned if the project is on a detached HEAD, or if the work_git is
573 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700574 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400575 try:
576 b = self.work_git.GetHead()
577 except NoManifestException:
578 # If the local checkout is in a bad state, don't barf. Let the callers
579 # process this like the head is unreadable.
580 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700581 if b.startswith(R_HEADS):
582 return b[len(R_HEADS):]
583 return None
584
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700585 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500586 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
587 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
588 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200589
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700590 def IsDirty(self, consider_untracked=True):
591 """Is the working directory modified in some way?
592 """
593 self.work_git.update_index('-q',
594 '--unmerged',
595 '--ignore-missing',
596 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900597 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700598 return True
599 if self.work_git.DiffZ('diff-files'):
600 return True
601 if consider_untracked and self.work_git.LsOthers():
602 return True
603 return False
604
605 _userident_name = None
606 _userident_email = None
607
608 @property
609 def UserName(self):
610 """Obtain the user's personal name.
611 """
612 if self._userident_name is None:
613 self._LoadUserIdentity()
614 return self._userident_name
615
616 @property
617 def UserEmail(self):
618 """Obtain the user's email address. This is very likely
619 to be their Gerrit login.
620 """
621 if self._userident_email is None:
622 self._LoadUserIdentity()
623 return self._userident_email
624
625 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900626 u = self.bare_git.var('GIT_COMMITTER_IDENT')
627 m = re.compile("^(.*) <([^>]*)> ").match(u)
628 if m:
629 self._userident_name = m.group(1)
630 self._userident_email = m.group(2)
631 else:
632 self._userident_name = ''
633 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700634
635 def GetRemote(self, name):
636 """Get the configuration for a single remote.
637 """
638 return self.config.GetRemote(name)
639
640 def GetBranch(self, name):
641 """Get the configuration for a single branch.
642 """
643 return self.config.GetBranch(name)
644
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700645 def GetBranches(self):
646 """Get all existing local branches.
647 """
648 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900649 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700650 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700651
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530652 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700653 if name.startswith(R_HEADS):
654 name = name[len(R_HEADS):]
655 b = self.GetBranch(name)
656 b.current = name == current
657 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900658 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700659 heads[name] = b
660
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530661 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700662 if name.startswith(R_PUB):
663 name = name[len(R_PUB):]
664 b = heads.get(name)
665 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900666 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700667
668 return heads
669
Colin Cross5acde752012-03-28 20:15:45 -0700670 def MatchesGroups(self, manifest_groups):
671 """Returns true if the manifest groups specified at init should cause
672 this project to be synced.
673 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700674 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700675
676 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700677 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700678 manifest_groups: "-group1,group2"
679 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500680
681 The special manifest group "default" will match any project that
682 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700683 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500684 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700685 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700686 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500687 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700688
Conley Owens971de8e2012-04-16 10:36:08 -0700689 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700690 for group in expanded_manifest_groups:
691 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700692 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700693 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700694 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700695
Conley Owens971de8e2012-04-16 10:36:08 -0700696 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700697
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700698# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700699 def UncommitedFiles(self, get_all=True):
700 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700701
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700702 Args:
703 get_all: a boolean, if True - get information about all different
704 uncommitted files. If False - return as soon as any kind of
705 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500706 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700707 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500708 self.work_git.update_index('-q',
709 '--unmerged',
710 '--ignore-missing',
711 '--refresh')
712 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700713 details.append("rebase in progress")
714 if not get_all:
715 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500716
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700717 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
718 if changes:
719 details.extend(changes)
720 if not get_all:
721 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500722
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700723 changes = self.work_git.DiffZ('diff-files').keys()
724 if changes:
725 details.extend(changes)
726 if not get_all:
727 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500728
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700729 changes = self.work_git.LsOthers()
730 if changes:
731 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500732
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700733 return details
734
735 def HasChanges(self):
736 """Returns true if there are uncommitted changes.
737 """
738 if self.UncommitedFiles(get_all=False):
739 return True
740 else:
741 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500742
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600743 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200745
746 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200747 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600748 quiet: If True then only print the project name. Do not print
749 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700750 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700751 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700752 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200753 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700754 print(file=output_redir)
755 print('project %s/' % self.relpath, file=output_redir)
756 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 return
758
759 self.work_git.update_index('-q',
760 '--unmerged',
761 '--ignore-missing',
762 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700763 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700764 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
765 df = self.work_git.DiffZ('diff-files')
766 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100767 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700768 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700769
770 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700771 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200772 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700773 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700774
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600775 if quiet:
776 out.nl()
777 return 'DIRTY'
778
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700779 branch = self.CurrentBranch
780 if branch is None:
781 out.nobranch('(*** NO BRANCH ***)')
782 else:
783 out.branch('branch %s', branch)
784 out.nl()
785
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700786 if rb:
787 out.important('prior sync failed; rebase still in progress')
788 out.nl()
789
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700790 paths = list()
791 paths.extend(di.keys())
792 paths.extend(df.keys())
793 paths.extend(do)
794
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530795 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900796 try:
797 i = di[p]
798 except KeyError:
799 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700800
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900801 try:
802 f = df[p]
803 except KeyError:
804 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200805
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900806 if i:
807 i_status = i.status.upper()
808 else:
809 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900811 if f:
812 f_status = f.status.lower()
813 else:
814 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700815
816 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800817 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700818 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700819 else:
820 line = ' %s%s\t%s' % (i_status, f_status, p)
821
822 if i and not f:
823 out.added('%s', line)
824 elif (i and f) or (not i and f):
825 out.changed('%s', line)
826 elif not i and not f:
827 out.untracked('%s', line)
828 else:
829 out.write('%s', line)
830 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200831
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700832 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700833
pelyad67872d2012-03-28 14:49:58 +0300834 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835 """Prints the status of the repository to stdout.
836 """
837 out = DiffColoring(self.config)
838 cmd = ['diff']
839 if out.is_on:
840 cmd.append('--color')
841 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300842 if absolute_paths:
843 cmd.append('--src-prefix=a/%s/' % self.relpath)
844 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400846 try:
847 p = GitCommand(self,
848 cmd,
849 capture_stdout=True,
850 capture_stderr=True)
851 except GitError as e:
852 out.nl()
853 out.project('project %s/' % self.relpath)
854 out.nl()
855 out.fail('%s', str(e))
856 out.nl()
857 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858 has_diff = False
859 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -0400860 if not hasattr(line, 'encode'):
861 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862 if not has_diff:
863 out.nl()
864 out.project('project %s/' % self.relpath)
865 out.nl()
866 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700867 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400868 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700870# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900871 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872 """Was the branch published (uploaded) for code review?
873 If so, returns the SHA-1 hash of the last published
874 state for the branch.
875 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700876 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900877 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700878 try:
879 return self.bare_git.rev_parse(key)
880 except GitError:
881 return None
882 else:
883 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900884 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700885 except KeyError:
886 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700887
David Pursehouse8a68ff92012-09-24 12:15:13 +0900888 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889 """Prunes any stale published refs.
890 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900891 if all_refs is None:
892 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893 heads = set()
894 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530895 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896 if name.startswith(R_HEADS):
897 heads.add(name)
898 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900899 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530901 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902 n = name[len(R_PUB):]
903 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900904 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700906 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700907 """List any branches which can be uploaded for review.
908 """
909 heads = {}
910 pubed = {}
911
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530912 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900914 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700915 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900916 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917
918 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530919 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900920 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700922 if selected_branch and branch != selected_branch:
923 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800925 rb = self.GetUploadableBranch(branch)
926 if rb:
927 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928 return ready
929
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800930 def GetUploadableBranch(self, branch_name):
931 """Get a single uploadable branch, or None.
932 """
933 branch = self.GetBranch(branch_name)
934 base = branch.LocalMerge
935 if branch.LocalMerge:
936 rb = ReviewableBranch(self, branch, base)
937 if rb.commits:
938 return rb
939 return None
940
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700941 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100942 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500943 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700944 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500945 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500946 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200947 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700948 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200949 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200950 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800951 validate_certs=True,
952 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953 """Uploads the named branch for code review.
954 """
955 if branch is None:
956 branch = self.CurrentBranch
957 if branch is None:
958 raise GitError('not currently on a branch')
959
960 branch = self.GetBranch(branch)
961 if not branch.LocalMerge:
962 raise GitError('branch %s does not track a remote' % branch.name)
963 if not branch.remote.review:
964 raise GitError('remote %s has no review url' % branch.remote.name)
965
Bryan Jacobsf609f912013-05-06 13:36:24 -0400966 if dest_branch is None:
967 dest_branch = self.dest_branch
968 if dest_branch is None:
969 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970 if not dest_branch.startswith(R_HEADS):
971 dest_branch = R_HEADS + dest_branch
972
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800973 if not branch.remote.projectname:
974 branch.remote.projectname = self.name
975 branch.remote.Save()
976
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200977 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800978 if url is None:
979 raise UploadError('review not configured')
980 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -0500981 if dryrun:
982 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800983
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800984 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -0800985 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700986
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800987 for push_option in (push_options or []):
988 cmd.append('-o')
989 cmd.append(push_option)
990
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800991 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800992
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800993 if dest_branch.startswith(R_HEADS):
994 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700995
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -0500996 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -0800997 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800998 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -0800999 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001000 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001001 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001002
David Pursehousef25a3702018-11-14 19:01:22 -08001003 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001004 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001005 if notify:
1006 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001007 if private:
1008 opts += ['private']
1009 if wip:
1010 opts += ['wip']
1011 if opts:
1012 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001013 cmd.append(ref_spec)
1014
Anthony King7bdac712014-07-16 12:56:40 +01001015 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001016 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017
Mike Frysingerd7f86832020-11-19 19:18:46 -05001018 if not dryrun:
1019 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1020 self.bare_git.UpdateRef(R_PUB + branch.name,
1021 R_HEADS + branch.name,
1022 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001024# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001025 def _ExtractArchive(self, tarpath, path=None):
1026 """Extract the given tar on its current location
1027
1028 Args:
1029 - tarpath: The path to the actual tar file
1030
1031 """
1032 try:
1033 with tarfile.open(tarpath, 'r') as tar:
1034 tar.extractall(path=path)
1035 return True
1036 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001037 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001038 return False
1039
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001040 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001041 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001042 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001043 is_new=None,
1044 current_branch_only=False,
1045 force_sync=False,
1046 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001047 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001048 archive=False,
1049 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001050 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001051 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001052 submodules=False,
1053 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054 """Perform only the network IO portion of the sync process.
1055 Local working directory/branch state is not affected.
1056 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001057 if archive and not isinstance(self, MetaProject):
1058 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001059 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001060 return False
1061
1062 name = self.relpath.replace('\\', '/')
1063 name = name.replace('/', '_')
1064 tarpath = '%s.tar' % name
1065 topdir = self.manifest.topdir
1066
1067 try:
1068 self._FetchArchive(tarpath, cwd=topdir)
1069 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001070 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001071 return False
1072
1073 # From now on, we only need absolute tarpath
1074 tarpath = os.path.join(topdir, tarpath)
1075
1076 if not self._ExtractArchive(tarpath, path=topdir):
1077 return False
1078 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001079 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001080 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001081 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001082 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001083 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001084 if is_new is None:
1085 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001086 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001087 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001088 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001089 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001091
1092 if is_new:
1093 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1094 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001095 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001096 # This works for both absolute and relative alternate directories.
1097 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001098 except IOError:
1099 alt_dir = None
1100 else:
1101 alt_dir = None
1102
Mike Frysingere50b6a72020-02-19 01:45:48 -05001103 if (clone_bundle
1104 and alt_dir is None
1105 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001106 is_new = False
1107
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001108 if not current_branch_only:
1109 if self.sync_c:
1110 current_branch_only = True
1111 elif not self.manifest._loaded:
1112 # Manifest cannot check defaults until it syncs.
1113 current_branch_only = False
1114 elif self.manifest.default.sync_c:
1115 current_branch_only = True
1116
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001117 if not self.sync_tags:
1118 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001119
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001120 if self.clone_depth:
1121 depth = self.clone_depth
1122 else:
1123 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1124
Mike Frysinger521d01b2020-02-17 01:51:49 -05001125 # See if we can skip the network fetch entirely.
1126 if not (optimized_fetch and
1127 (ID_RE.match(self.revisionExpr) and
1128 self._CheckForImmutableRevision())):
1129 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001130 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1131 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001132 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001133 submodules=submodules, force_sync=force_sync,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001134 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001135 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001136
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001137 mp = self.manifest.manifestProject
1138 dissociate = mp.config.GetBoolean('repo.dissociate')
1139 if dissociate:
1140 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1141 if os.path.exists(alternates_file):
1142 cmd = ['repack', '-a', '-d']
1143 if GitCommand(self, cmd, bare=True).Wait() != 0:
1144 return False
1145 platform_utils.remove(alternates_file)
1146
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001147 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001148 self._InitMRef()
1149 else:
1150 self._InitMirrorHead()
1151 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001152 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001153 except OSError:
1154 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001155 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001156
1157 def PostRepoUpgrade(self):
1158 self._InitHooks()
1159
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001160 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001161 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001162 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001163 for copyfile in self.copyfiles:
1164 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001165 for linkfile in self.linkfiles:
1166 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167
Julien Camperguedd654222014-01-09 16:21:37 +01001168 def GetCommitRevisionId(self):
1169 """Get revisionId of a commit.
1170
1171 Use this method instead of GetRevisionId to get the id of the commit rather
1172 than the id of the current git object (for example, a tag)
1173
1174 """
1175 if not self.revisionExpr.startswith(R_TAGS):
1176 return self.GetRevisionId(self._allrefs)
1177
1178 try:
1179 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1180 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001181 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1182 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001183
David Pursehouse8a68ff92012-09-24 12:15:13 +09001184 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001185 if self.revisionId:
1186 return self.revisionId
1187
1188 rem = self.GetRemote(self.remote.name)
1189 rev = rem.ToLocal(self.revisionExpr)
1190
David Pursehouse8a68ff92012-09-24 12:15:13 +09001191 if all_refs is not None and rev in all_refs:
1192 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001193
1194 try:
1195 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1196 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001197 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1198 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001199
Martin Kellye4e94d22017-03-21 16:05:12 -07001200 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201 """Perform only the local IO portion of the sync process.
1202 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001203 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001204 if not os.path.exists(self.gitdir):
1205 syncbuf.fail(self,
1206 'Cannot checkout %s due to missing network sync; Run '
1207 '`repo sync -n %s` first.' %
1208 (self.name, self.name))
1209 return
1210
Martin Kellye4e94d22017-03-21 16:05:12 -07001211 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001212 all_refs = self.bare_ref.all
1213 self.CleanPublishedCache(all_refs)
1214 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001215
David Pursehouse1d947b32012-10-25 12:23:11 +09001216 def _doff():
1217 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001218 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001219
Martin Kellye4e94d22017-03-21 16:05:12 -07001220 def _dosubmodules():
1221 self._SyncSubmodules(quiet=True)
1222
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001223 head = self.work_git.GetHead()
1224 if head.startswith(R_HEADS):
1225 branch = head[len(R_HEADS):]
1226 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001227 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001228 except KeyError:
1229 head = None
1230 else:
1231 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001232
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001233 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001234 # Currently on a detached HEAD. The user is assumed to
1235 # not have any local modifications worth worrying about.
1236 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001237 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001238 syncbuf.fail(self, _PriorSyncFailedError())
1239 return
1240
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001241 if head == revid:
1242 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001243 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001244 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001245 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001246 # The copy/linkfile config may have changed.
1247 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001248 return
1249 else:
1250 lost = self._revlist(not_rev(revid), HEAD)
1251 if lost:
1252 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001253
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001254 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001255 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001256 if submodules:
1257 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001258 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001259 syncbuf.fail(self, e)
1260 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001261 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001262 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001264 if head == revid:
1265 # No changes; don't do anything further.
1266 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001267 # The copy/linkfile config may have changed.
1268 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001269 return
1270
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001272
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001273 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001274 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001275 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001276 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001277 syncbuf.info(self,
1278 "leaving %s; does not track upstream",
1279 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001281 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001282 if submodules:
1283 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001284 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001285 syncbuf.fail(self, e)
1286 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001287 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001288 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001289
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001290 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001291
1292 # See if we can perform a fast forward merge. This can happen if our
1293 # branch isn't in the exact same state as we last published.
1294 try:
1295 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1296 # Skip the published logic.
1297 pub = False
1298 except GitError:
1299 pub = self.WasPublished(branch.name, all_refs)
1300
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001302 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303 if not_merged:
1304 if upstream_gain:
1305 # The user has published this branch and some of those
1306 # commits are not yet merged upstream. We do not want
1307 # to rewrite the published commits so we punt.
1308 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001309 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001310 "branch %s is published (but not merged) and is now "
1311 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001312 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001313 elif pub == head:
1314 # All published commits are merged, and thus we are a
1315 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001316 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001317 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001318 if submodules:
1319 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001320 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001322 # Examine the local commits not in the remote. Find the
1323 # last one attributed to this user, if any.
1324 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001325 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001326 last_mine = None
1327 cnt_mine = 0
1328 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001329 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001330 if committer_email == self.UserEmail:
1331 last_mine = commit_id
1332 cnt_mine += 1
1333
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001334 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001335 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001336
1337 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001338 syncbuf.fail(self, _DirtyError())
1339 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001340
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001341 # If the upstream switched on us, warn the user.
1342 #
1343 if branch.merge != self.revisionExpr:
1344 if branch.merge and self.revisionExpr:
1345 syncbuf.info(self,
1346 'manifest switched %s...%s',
1347 branch.merge,
1348 self.revisionExpr)
1349 elif branch.merge:
1350 syncbuf.info(self,
1351 'manifest no longer tracks %s',
1352 branch.merge)
1353
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001354 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001355 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001356 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001358 syncbuf.info(self,
1359 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001360 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001361
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001362 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001363 if not ID_RE.match(self.revisionExpr):
1364 # in case of manifest sync the revisionExpr might be a SHA1
1365 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001366 if not branch.merge.startswith('refs/'):
1367 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368 branch.Save()
1369
Mike Pontillod3153822012-02-28 11:53:24 -08001370 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001371 def _docopyandlink():
1372 self._CopyAndLinkFiles()
1373
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001374 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001375 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001376 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001377 if submodules:
1378 syncbuf.later2(self, _dosubmodules)
1379 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001380 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001382 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001383 if submodules:
1384 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001385 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001386 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001387 syncbuf.fail(self, e)
1388 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001390 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001391 if submodules:
1392 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393
Mike Frysingere6a202f2019-08-02 15:57:57 -04001394 def AddCopyFile(self, src, dest, topdir):
1395 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001396
Mike Frysingere6a202f2019-08-02 15:57:57 -04001397 No filesystem changes occur here. Actual copying happens later on.
1398
1399 Paths should have basic validation run on them before being queued.
1400 Further checking will be handled when the actual copy happens.
1401 """
1402 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1403
1404 def AddLinkFile(self, src, dest, topdir):
1405 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1406
1407 No filesystem changes occur here. Actual linking happens later on.
1408
1409 Paths should have basic validation run on them before being queued.
1410 Further checking will be handled when the actual link happens.
1411 """
1412 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001413
James W. Mills24c13082012-04-12 15:04:13 -05001414 def AddAnnotation(self, name, value, keep):
1415 self.annotations.append(_Annotation(name, value, keep))
1416
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001417 def DownloadPatchSet(self, change_id, patch_id):
1418 """Download a single patch set of a single change to FETCH_HEAD.
1419 """
1420 remote = self.GetRemote(self.remote.name)
1421
1422 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001423 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001424 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001425 if GitCommand(self, cmd, bare=True).Wait() != 0:
1426 return None
1427 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001428 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001429 change_id,
1430 patch_id,
1431 self.bare_git.rev_parse('FETCH_HEAD'))
1432
Mike Frysingerc0d18662020-02-19 19:19:18 -05001433 def DeleteWorktree(self, quiet=False, force=False):
1434 """Delete the source checkout and any other housekeeping tasks.
1435
1436 This currently leaves behind the internal .repo/ cache state. This helps
1437 when switching branches or manifest changes get reverted as we don't have
1438 to redownload all the git objects. But we should do some GC at some point.
1439
1440 Args:
1441 quiet: Whether to hide normal messages.
1442 force: Always delete tree even if dirty.
1443
1444 Returns:
1445 True if the worktree was completely cleaned out.
1446 """
1447 if self.IsDirty():
1448 if force:
1449 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1450 (self.relpath,), file=sys.stderr)
1451 else:
1452 print('error: %s: Cannot remove project: uncommitted changes are '
1453 'present.\n' % (self.relpath,), file=sys.stderr)
1454 return False
1455
1456 if not quiet:
1457 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1458
1459 # Unlock and delink from the main worktree. We don't use git's worktree
1460 # remove because it will recursively delete projects -- we handle that
1461 # ourselves below. https://crbug.com/git/48
1462 if self.use_git_worktrees:
1463 needle = platform_utils.realpath(self.gitdir)
1464 # Find the git worktree commondir under .repo/worktrees/.
1465 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1466 assert output.startswith('worktree '), output
1467 commondir = output[9:]
1468 # Walk each of the git worktrees to see where they point.
1469 configs = os.path.join(commondir, 'worktrees')
1470 for name in os.listdir(configs):
1471 gitdir = os.path.join(configs, name, 'gitdir')
1472 with open(gitdir) as fp:
1473 relpath = fp.read().strip()
1474 # Resolve the checkout path and see if it matches this project.
1475 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1476 if fullpath == needle:
1477 platform_utils.rmtree(os.path.join(configs, name))
1478
1479 # Delete the .git directory first, so we're less likely to have a partially
1480 # working git repository around. There shouldn't be any git projects here,
1481 # so rmtree works.
1482
1483 # Try to remove plain files first in case of git worktrees. If this fails
1484 # for any reason, we'll fall back to rmtree, and that'll display errors if
1485 # it can't remove things either.
1486 try:
1487 platform_utils.remove(self.gitdir)
1488 except OSError:
1489 pass
1490 try:
1491 platform_utils.rmtree(self.gitdir)
1492 except OSError as e:
1493 if e.errno != errno.ENOENT:
1494 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1495 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1496 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1497 return False
1498
1499 # Delete everything under the worktree, except for directories that contain
1500 # another git project.
1501 dirs_to_remove = []
1502 failed = False
1503 for root, dirs, files in platform_utils.walk(self.worktree):
1504 for f in files:
1505 path = os.path.join(root, f)
1506 try:
1507 platform_utils.remove(path)
1508 except OSError as e:
1509 if e.errno != errno.ENOENT:
1510 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1511 failed = True
1512 dirs[:] = [d for d in dirs
1513 if not os.path.lexists(os.path.join(root, d, '.git'))]
1514 dirs_to_remove += [os.path.join(root, d) for d in dirs
1515 if os.path.join(root, d) not in dirs_to_remove]
1516 for d in reversed(dirs_to_remove):
1517 if platform_utils.islink(d):
1518 try:
1519 platform_utils.remove(d)
1520 except OSError as e:
1521 if e.errno != errno.ENOENT:
1522 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1523 failed = True
1524 elif not platform_utils.listdir(d):
1525 try:
1526 platform_utils.rmdir(d)
1527 except OSError as e:
1528 if e.errno != errno.ENOENT:
1529 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1530 failed = True
1531 if failed:
1532 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1533 file=sys.stderr)
1534 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1535 return False
1536
1537 # Try deleting parent dirs if they are empty.
1538 path = self.worktree
1539 while path != self.manifest.topdir:
1540 try:
1541 platform_utils.rmdir(path)
1542 except OSError as e:
1543 if e.errno != errno.ENOENT:
1544 break
1545 path = os.path.dirname(path)
1546
1547 return True
1548
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001549# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001550 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001551 """Create a new branch off the manifest's revision.
1552 """
Simran Basib9a1b732015-08-20 12:19:28 -07001553 if not branch_merge:
1554 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001555 head = self.work_git.GetHead()
1556 if head == (R_HEADS + name):
1557 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001558
David Pursehouse8a68ff92012-09-24 12:15:13 +09001559 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001560 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001561 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001562 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001563 capture_stdout=True,
1564 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001565
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001566 branch = self.GetBranch(name)
1567 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001568 branch.merge = branch_merge
1569 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1570 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001571
1572 if revision is None:
1573 revid = self.GetRevisionId(all_refs)
1574 else:
1575 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001576
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001577 if head.startswith(R_HEADS):
1578 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001579 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001580 except KeyError:
1581 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001582 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001583 ref = R_HEADS + name
1584 self.work_git.update_ref(ref, revid)
1585 self.work_git.symbolic_ref(HEAD, ref)
1586 branch.Save()
1587 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001588
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001589 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001590 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001591 capture_stdout=True,
1592 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001593 branch.Save()
1594 return True
1595 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001596
Wink Saville02d79452009-04-10 13:01:24 -07001597 def CheckoutBranch(self, name):
1598 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001599
1600 Args:
1601 name: The name of the branch to checkout.
1602
1603 Returns:
1604 True if the checkout succeeded; False if it didn't; None if the branch
1605 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001606 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001607 rev = R_HEADS + name
1608 head = self.work_git.GetHead()
1609 if head == rev:
1610 # Already on the branch
1611 #
1612 return True
Wink Saville02d79452009-04-10 13:01:24 -07001613
David Pursehouse8a68ff92012-09-24 12:15:13 +09001614 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001615 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001616 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001617 except KeyError:
1618 # Branch does not exist in this project
1619 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001620 return None
Wink Saville02d79452009-04-10 13:01:24 -07001621
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001622 if head.startswith(R_HEADS):
1623 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001624 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001625 except KeyError:
1626 head = None
1627
1628 if head == revid:
1629 # Same revision; just update HEAD to point to the new
1630 # target branch, but otherwise take no other action.
1631 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001632 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1633 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001634 return True
1635
1636 return GitCommand(self,
1637 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001638 capture_stdout=True,
1639 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001640
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001641 def AbandonBranch(self, name):
1642 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001643
1644 Args:
1645 name: The name of the branch to abandon.
1646
1647 Returns:
1648 True if the abandon succeeded; False if it didn't; None if the branch
1649 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001650 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001651 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001652 all_refs = self.bare_ref.all
1653 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001654 # Doesn't exist
1655 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001656
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001657 head = self.work_git.GetHead()
1658 if head == rev:
1659 # We can't destroy the branch while we are sitting
1660 # on it. Switch to a detached HEAD.
1661 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001662 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001663
David Pursehouse8a68ff92012-09-24 12:15:13 +09001664 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001665 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001666 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001667 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001668 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001669
1670 return GitCommand(self,
1671 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001672 capture_stdout=True,
1673 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001674
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001675 def PruneHeads(self):
1676 """Prune any topic branches already merged into upstream.
1677 """
1678 cb = self.CurrentBranch
1679 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001680 left = self._allrefs
1681 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001682 if name.startswith(R_HEADS):
1683 name = name[len(R_HEADS):]
1684 if cb is None or name != cb:
1685 kill.append(name)
1686
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001687 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001688 if cb is not None \
1689 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001690 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691 self.work_git.DetachHead(HEAD)
1692 kill.append(cb)
1693
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001694 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001695 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001696
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001697 try:
1698 self.bare_git.DetachHead(rev)
1699
1700 b = ['branch', '-d']
1701 b.extend(kill)
1702 b = GitCommand(self, b, bare=True,
1703 capture_stdout=True,
1704 capture_stderr=True)
1705 b.Wait()
1706 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001707 if ID_RE.match(old):
1708 self.bare_git.DetachHead(old)
1709 else:
1710 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001711 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001712
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001713 for branch in kill:
1714 if (R_HEADS + branch) not in left:
1715 self.CleanPublishedCache()
1716 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001717
1718 if cb and cb not in kill:
1719 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001720 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001721
1722 kept = []
1723 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001724 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001725 branch = self.GetBranch(branch)
1726 base = branch.LocalMerge
1727 if not base:
1728 base = rev
1729 kept.append(ReviewableBranch(self, branch, base))
1730 return kept
1731
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001732# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001733 def GetRegisteredSubprojects(self):
1734 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001735
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001736 def rec(subprojects):
1737 if not subprojects:
1738 return
1739 result.extend(subprojects)
1740 for p in subprojects:
1741 rec(p.subprojects)
1742 rec(self.subprojects)
1743 return result
1744
1745 def _GetSubmodules(self):
1746 # Unfortunately we cannot call `git submodule status --recursive` here
1747 # because the working tree might not exist yet, and it cannot be used
1748 # without a working tree in its current implementation.
1749
1750 def get_submodules(gitdir, rev):
1751 # Parse .gitmodules for submodule sub_paths and sub_urls
1752 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1753 if not sub_paths:
1754 return []
1755 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1756 # revision of submodule repository
1757 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1758 submodules = []
1759 for sub_path, sub_url in zip(sub_paths, sub_urls):
1760 try:
1761 sub_rev = sub_revs[sub_path]
1762 except KeyError:
1763 # Ignore non-exist submodules
1764 continue
1765 submodules.append((sub_rev, sub_path, sub_url))
1766 return submodules
1767
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001768 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1769 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001770
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001771 def parse_gitmodules(gitdir, rev):
1772 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1773 try:
Anthony King7bdac712014-07-16 12:56:40 +01001774 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1775 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001776 except GitError:
1777 return [], []
1778 if p.Wait() != 0:
1779 return [], []
1780
1781 gitmodules_lines = []
1782 fd, temp_gitmodules_path = tempfile.mkstemp()
1783 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001784 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001785 os.close(fd)
1786 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001787 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1788 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001789 if p.Wait() != 0:
1790 return [], []
1791 gitmodules_lines = p.stdout.split('\n')
1792 except GitError:
1793 return [], []
1794 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001795 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001796
1797 names = set()
1798 paths = {}
1799 urls = {}
1800 for line in gitmodules_lines:
1801 if not line:
1802 continue
1803 m = re_path.match(line)
1804 if m:
1805 names.add(m.group(1))
1806 paths[m.group(1)] = m.group(2)
1807 continue
1808 m = re_url.match(line)
1809 if m:
1810 names.add(m.group(1))
1811 urls[m.group(1)] = m.group(2)
1812 continue
1813 names = sorted(names)
1814 return ([paths.get(name, '') for name in names],
1815 [urls.get(name, '') for name in names])
1816
1817 def git_ls_tree(gitdir, rev, paths):
1818 cmd = ['ls-tree', rev, '--']
1819 cmd.extend(paths)
1820 try:
Anthony King7bdac712014-07-16 12:56:40 +01001821 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1822 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001823 except GitError:
1824 return []
1825 if p.Wait() != 0:
1826 return []
1827 objects = {}
1828 for line in p.stdout.split('\n'):
1829 if not line.strip():
1830 continue
1831 object_rev, object_path = line.split()[2:4]
1832 objects[object_path] = object_rev
1833 return objects
1834
1835 try:
1836 rev = self.GetRevisionId()
1837 except GitError:
1838 return []
1839 return get_submodules(self.gitdir, rev)
1840
1841 def GetDerivedSubprojects(self):
1842 result = []
1843 if not self.Exists:
1844 # If git repo does not exist yet, querying its submodules will
1845 # mess up its states; so return here.
1846 return result
1847 for rev, path, url in self._GetSubmodules():
1848 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001849 relpath, worktree, gitdir, objdir = \
1850 self.manifest.GetSubprojectPaths(self, name, path)
1851 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001852 if project:
1853 result.extend(project.GetDerivedSubprojects())
1854 continue
David James8d201162013-10-11 17:03:19 -07001855
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001856 if url.startswith('..'):
1857 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001858 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001859 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001860 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001861 review=self.remote.review,
1862 revision=self.remote.revision)
1863 subproject = Project(manifest=self.manifest,
1864 name=name,
1865 remote=remote,
1866 gitdir=gitdir,
1867 objdir=objdir,
1868 worktree=worktree,
1869 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001870 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001871 revisionId=rev,
1872 rebase=self.rebase,
1873 groups=self.groups,
1874 sync_c=self.sync_c,
1875 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001876 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001877 parent=self,
1878 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001879 result.append(subproject)
1880 result.extend(subproject.GetDerivedSubprojects())
1881 return result
1882
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001883# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001884 def EnableRepositoryExtension(self, key, value='true', version=1):
1885 """Enable git repository extension |key| with |value|.
1886
1887 Args:
1888 key: The extension to enabled. Omit the "extensions." prefix.
1889 value: The value to use for the extension.
1890 version: The minimum git repository version needed.
1891 """
1892 # Make sure the git repo version is new enough already.
1893 found_version = self.config.GetInt('core.repositoryFormatVersion')
1894 if found_version is None:
1895 found_version = 0
1896 if found_version < version:
1897 self.config.SetString('core.repositoryFormatVersion', str(version))
1898
1899 # Enable the extension!
1900 self.config.SetString('extensions.%s' % (key,), value)
1901
Mike Frysinger50a81de2020-09-06 15:51:21 -04001902 def ResolveRemoteHead(self, name=None):
1903 """Find out what the default branch (HEAD) points to.
1904
1905 Normally this points to refs/heads/master, but projects are moving to main.
1906 Support whatever the server uses rather than hardcoding "master" ourselves.
1907 """
1908 if name is None:
1909 name = self.remote.name
1910
1911 # The output will look like (NB: tabs are separators):
1912 # ref: refs/heads/master HEAD
1913 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1914 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1915
1916 for line in output.splitlines():
1917 lhs, rhs = line.split('\t', 1)
1918 if rhs == 'HEAD' and lhs.startswith('ref:'):
1919 return lhs[4:].strip()
1920
1921 return None
1922
Zac Livingstone4332262017-06-16 08:56:09 -06001923 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001924 try:
1925 # if revision (sha or tag) is not present then following function
1926 # throws an error.
1927 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1928 return True
1929 except GitError:
1930 # There is no such persistent revision. We have to fetch it.
1931 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001932
Julien Campergue335f5ef2013-10-16 11:02:35 +02001933 def _FetchArchive(self, tarpath, cwd=None):
1934 cmd = ['archive', '-v', '-o', tarpath]
1935 cmd.append('--remote=%s' % self.remote.url)
1936 cmd.append('--prefix=%s/' % self.relpath)
1937 cmd.append(self.revisionExpr)
1938
1939 command = GitCommand(self, cmd, cwd=cwd,
1940 capture_stdout=True,
1941 capture_stderr=True)
1942
1943 if command.Wait() != 0:
1944 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1945
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001946 def _RemoteFetch(self, name=None,
1947 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001948 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001949 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001950 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001951 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001952 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001953 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001954 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04001955 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07001956 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001957 clone_filter=None,
1958 retry_fetches=2,
1959 retry_sleep_initial_sec=4.0,
1960 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001961 is_sha1 = False
1962 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001963 # The depth should not be used when fetching to a mirror because
1964 # it will result in a shallow repository that cannot be cloned or
1965 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001966 # The repo project should also never be synced with partial depth.
1967 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1968 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001969
Shawn Pearce69e04d82014-01-29 12:48:54 -08001970 if depth:
1971 current_branch_only = True
1972
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001973 if ID_RE.match(self.revisionExpr) is not None:
1974 is_sha1 = True
1975
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001976 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001977 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001978 # this is a tag and its sha1 value should never change
1979 tag_name = self.revisionExpr[len(R_TAGS):]
1980
1981 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06001982 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05001983 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02001984 print('Skipped fetching project %s (already have persistent ref)'
1985 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001986 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001987 if is_sha1 and not depth:
1988 # When syncing a specific commit and --depth is not set:
1989 # * if upstream is explicitly specified and is not a sha1, fetch only
1990 # upstream as users expect only upstream to be fetch.
1991 # Note: The commit might not be in upstream in which case the sync
1992 # will fail.
1993 # * otherwise, fetch all branches to make sure we end up with the
1994 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02001995 if self.upstream:
1996 current_branch_only = not ID_RE.match(self.upstream)
1997 else:
1998 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001999
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002000 if not name:
2001 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002002
2003 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002004 remote = self.GetRemote(name)
2005 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002006 ssh_proxy = True
2007
Shawn O. Pearce88443382010-10-08 10:02:09 +02002008 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002009 if alt_dir and 'objects' == os.path.basename(alt_dir):
2010 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002011 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2012 remote = self.GetRemote(name)
2013
David Pursehouse8a68ff92012-09-24 12:15:13 +09002014 all_refs = self.bare_ref.all
2015 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002016 tmp = set()
2017
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302018 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002019 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002020 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002021 all_refs[r] = ref_id
2022 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002023 continue
2024
David Pursehouse8a68ff92012-09-24 12:15:13 +09002025 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002026 continue
2027
David Pursehouse8a68ff92012-09-24 12:15:13 +09002028 r = 'refs/_alt/%s' % ref_id
2029 all_refs[r] = ref_id
2030 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002031 tmp.add(r)
2032
heping3d7bbc92017-04-12 19:51:47 +08002033 tmp_packed_lines = []
2034 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002035
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302036 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002037 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002038 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002039 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002040 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002041
heping3d7bbc92017-04-12 19:51:47 +08002042 tmp_packed = ''.join(tmp_packed_lines)
2043 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002044 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002045 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002046 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002047
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002048 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002049
Xin Li745be2e2019-06-03 11:24:30 -07002050 if clone_filter:
2051 git_require((2, 19, 0), fail=True, msg='partial clones')
2052 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002053 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -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
Mike Frysinger4847e052020-02-22 00:07:35 -05002065 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002066 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002067 if not quiet and sys.stdout.isatty():
2068 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002069 if not self.worktree:
2070 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002071 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002072
Mike Frysingere57f1142019-03-18 21:27:54 -04002073 if force_sync:
2074 cmd.append('--force')
2075
David Pursehouse74cfd272015-10-14 10:50:15 +09002076 if prune:
2077 cmd.append('--prune')
2078
Martin Kellye4e94d22017-03-21 16:05:12 -07002079 if submodules:
2080 cmd.append('--recurse-submodules=on-demand')
2081
Kuang-che Wu6856f982019-11-25 12:37:55 +08002082 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002083 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002084 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002085 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002086 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002087 spec.append('tag')
2088 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002089
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302090 if self.manifest.IsMirror and not current_branch_only:
2091 branch = None
2092 else:
2093 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002094 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002095 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002096 # Shallow checkout of a specific commit, fetch from that commit and not
2097 # the heads only as the commit might be deeper in the history.
2098 spec.append(branch)
2099 else:
2100 if is_sha1:
2101 branch = self.upstream
2102 if branch is not None and branch.strip():
2103 if not branch.startswith('refs/'):
2104 branch = R_HEADS + branch
2105 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2106
2107 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2108 # whole repo.
2109 if self.manifest.IsMirror and not spec:
2110 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2111
2112 # If using depth then we should not get all the tags since they may
2113 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002114 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002115 cmd.append('--no-tags')
2116 else:
2117 cmd.append('--tags')
2118 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2119
Conley Owens80b87fe2014-05-09 17:13:44 -07002120 cmd.extend(spec)
2121
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002122 # At least one retry minimum due to git remote prune.
2123 retry_fetches = max(retry_fetches, 2)
2124 retry_cur_sleep = retry_sleep_initial_sec
2125 ok = prune_tried = False
2126 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002127 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger4847e052020-02-22 00:07:35 -05002128 merge_output=True, capture_stdout=quiet)
John L. Villalovos126e2982015-01-29 21:58:12 -08002129 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002130 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002131 ok = True
2132 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002133
2134 # Retry later due to HTTP 429 Too Many Requests.
2135 elif ('error:' in gitcmd.stderr and
2136 'HTTP 429' in gitcmd.stderr):
2137 if not quiet:
2138 print('429 received, sleeping: %s sec' % retry_cur_sleep,
2139 file=sys.stderr)
2140 time.sleep(retry_cur_sleep)
2141 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2142 MAXIMUM_RETRY_SLEEP_SEC)
2143 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2144 RETRY_JITTER_PERCENT))
2145 continue
2146
2147 # If this is not last attempt, try 'git remote prune'.
2148 elif (try_n < retry_fetches - 1 and
2149 'error:' in gitcmd.stderr and
2150 'git remote prune' in gitcmd.stderr and
2151 not prune_tried):
2152 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002153 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002154 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002155 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002156 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002157 break
2158 continue
Brian Harring14a66742012-09-28 20:21:57 -07002159 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002160 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2161 # in sha1 mode, we just tried sync'ing from the upstream field; it
2162 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002163 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002164 elif ret < 0:
2165 # Git died with a signal, exit immediately
2166 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002167 if not verbose:
2168 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002169 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002170
2171 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002172 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002173 if old_packed != '':
2174 _lwrite(packed_refs, old_packed)
2175 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002176 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002177 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002178
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002179 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002180 # We just synced the upstream given branch; verify we
2181 # got what we wanted, else trigger a second run of all
2182 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002183 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002184 # Sync the current branch only with depth set to None.
2185 # We always pass depth=None down to avoid infinite recursion.
2186 return self._RemoteFetch(
2187 name=name, quiet=quiet, verbose=verbose,
2188 current_branch_only=current_branch_only and depth,
2189 initial=False, alt_dir=alt_dir,
2190 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002191
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002192 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002193
Mike Frysingere50b6a72020-02-19 01:45:48 -05002194 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002195 if initial and \
2196 (self.manifest.manifestProject.config.GetString('repo.depth') or
2197 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002198 return False
2199
2200 remote = self.GetRemote(self.remote.name)
2201 bundle_url = remote.url + '/clone.bundle'
2202 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002203 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2204 'persistent-http',
2205 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002206 return False
2207
2208 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2209 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2210
2211 exist_dst = os.path.exists(bundle_dst)
2212 exist_tmp = os.path.exists(bundle_tmp)
2213
2214 if not initial and not exist_dst and not exist_tmp:
2215 return False
2216
2217 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002218 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2219 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002220 if not exist_dst:
2221 return False
2222
2223 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002224 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002225 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002226 if not quiet and sys.stdout.isatty():
2227 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002228 if not self.worktree:
2229 cmd.append('--update-head-ok')
2230 cmd.append(bundle_dst)
2231 for f in remote.fetch:
2232 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002233 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002234
2235 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002236 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002237 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002238 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002239 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002240 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002241
Mike Frysingere50b6a72020-02-19 01:45:48 -05002242 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002243 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002244 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002245
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002246 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002247 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002248 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002249 if os.path.exists(tmpPath):
2250 size = os.stat(tmpPath).st_size
2251 if size >= 1024:
2252 cmd += ['--continue-at', '%d' % (size,)]
2253 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002254 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002255 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002256 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002257 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002258 if proxy:
2259 cmd += ['--proxy', proxy]
2260 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2261 cmd += ['--proxy', os.environ['http_proxy']]
2262 if srcUrl.startswith('persistent-https'):
2263 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2264 elif srcUrl.startswith('persistent-http'):
2265 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002266 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002267
Dave Borowitz137d0132015-01-02 11:12:54 -08002268 if IsTrace():
2269 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002270 if verbose:
2271 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2272 stdout = None if verbose else subprocess.PIPE
2273 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002274 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002275 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002276 except OSError:
2277 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002278
Mike Frysingere50b6a72020-02-19 01:45:48 -05002279 (output, _) = proc.communicate()
2280 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002281
Dave Borowitz137d0132015-01-02 11:12:54 -08002282 if curlret == 22:
2283 # From curl man page:
2284 # 22: HTTP page not retrieved. The requested url was not found or
2285 # returned another error with the HTTP error code being 400 or above.
2286 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002287 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002288 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2289 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002290 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002291 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002292 elif curlret and not verbose and output:
2293 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002294
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002295 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002296 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002297 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002298 return True
2299 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002300 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002301 return False
2302 else:
2303 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002304
Kris Giesingc8d882a2014-12-23 13:02:32 -08002305 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002306 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002307 with open(path, 'rb') as f:
2308 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002309 return True
2310 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002311 if not quiet:
2312 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002313 return False
2314 except OSError:
2315 return False
2316
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002317 def _Checkout(self, rev, quiet=False):
2318 cmd = ['checkout']
2319 if quiet:
2320 cmd.append('-q')
2321 cmd.append(rev)
2322 cmd.append('--')
2323 if GitCommand(self, cmd).Wait() != 0:
2324 if self._allrefs:
2325 raise GitError('%s checkout %s ' % (self.name, rev))
2326
Mike Frysinger915fda12020-03-22 12:15:20 -04002327 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002328 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002329 if ffonly:
2330 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002331 if record_origin:
2332 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002333 cmd.append(rev)
2334 cmd.append('--')
2335 if GitCommand(self, cmd).Wait() != 0:
2336 if self._allrefs:
2337 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2338
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302339 def _LsRemote(self, refs):
2340 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302341 p = GitCommand(self, cmd, capture_stdout=True)
2342 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002343 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302344 return None
2345
Anthony King7bdac712014-07-16 12:56:40 +01002346 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002347 cmd = ['revert']
2348 cmd.append('--no-edit')
2349 cmd.append(rev)
2350 cmd.append('--')
2351 if GitCommand(self, cmd).Wait() != 0:
2352 if self._allrefs:
2353 raise GitError('%s revert %s ' % (self.name, rev))
2354
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002355 def _ResetHard(self, rev, quiet=True):
2356 cmd = ['reset', '--hard']
2357 if quiet:
2358 cmd.append('-q')
2359 cmd.append(rev)
2360 if GitCommand(self, cmd).Wait() != 0:
2361 raise GitError('%s reset --hard %s ' % (self.name, rev))
2362
Martin Kellye4e94d22017-03-21 16:05:12 -07002363 def _SyncSubmodules(self, quiet=True):
2364 cmd = ['submodule', 'update', '--init', '--recursive']
2365 if quiet:
2366 cmd.append('-q')
2367 if GitCommand(self, cmd).Wait() != 0:
2368 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2369
Anthony King7bdac712014-07-16 12:56:40 +01002370 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002371 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002372 if onto is not None:
2373 cmd.extend(['--onto', onto])
2374 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002375 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002376 raise GitError('%s rebase %s ' % (self.name, upstream))
2377
Pierre Tardy3d125942012-05-04 12:18:12 +02002378 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002379 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002380 if ffonly:
2381 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002382 if GitCommand(self, cmd).Wait() != 0:
2383 raise GitError('%s merge %s ' % (self.name, head))
2384
David Pursehousee8ace262020-02-13 12:41:15 +09002385 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002386 init_git_dir = not os.path.exists(self.gitdir)
2387 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002388 try:
2389 # Initialize the bare repository, which contains all of the objects.
2390 if init_obj_dir:
2391 os.makedirs(self.objdir)
2392 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002393
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002394 if self.use_git_worktrees:
2395 # Set up the m/ space to point to the worktree-specific ref space.
2396 # We'll update the worktree-specific ref space on each checkout.
2397 if self.manifest.branch:
2398 self.bare_git.symbolic_ref(
2399 '-m', 'redirecting to worktree scope',
2400 R_M + self.manifest.branch,
2401 R_WORKTREE_M + self.manifest.branch)
2402
2403 # Enable per-worktree config file support if possible. This is more a
2404 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002405 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002406 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002407
Kevin Degib1a07b82015-07-27 13:33:43 -06002408 # If we have a separate directory to hold refs, initialize it as well.
2409 if self.objdir != self.gitdir:
2410 if init_git_dir:
2411 os.makedirs(self.gitdir)
2412
2413 if init_obj_dir or init_git_dir:
2414 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2415 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002416 try:
2417 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2418 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002419 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002420 print("Retrying clone after deleting %s" %
2421 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002422 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002423 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2424 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002425 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002426 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002427 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2428 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002429 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002430 raise e
2431 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002432
Kevin Degi384b3c52014-10-16 16:02:58 -06002433 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002434 mp = self.manifest.manifestProject
2435 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002436
Kevin Degib1a07b82015-07-27 13:33:43 -06002437 if ref_dir or mirror_git:
2438 if not mirror_git:
2439 mirror_git = os.path.join(ref_dir, self.name + '.git')
2440 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2441 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002442 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2443 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002444
Kevin Degib1a07b82015-07-27 13:33:43 -06002445 if os.path.exists(mirror_git):
2446 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002447 elif os.path.exists(repo_git):
2448 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002449 elif os.path.exists(worktrees_git):
2450 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002451 else:
2452 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002453
Kevin Degib1a07b82015-07-27 13:33:43 -06002454 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002455 if not os.path.isabs(ref_dir):
2456 # The alternate directory is relative to the object database.
2457 ref_dir = os.path.relpath(ref_dir,
2458 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002459 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2460 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002461
David Pursehousee8ace262020-02-13 12:41:15 +09002462 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002463
2464 m = self.manifest.manifestProject.config
2465 for key in ['user.name', 'user.email']:
2466 if m.Has(key, include_defaults=False):
2467 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002468 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002469 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002470 if self.manifest.IsMirror:
2471 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002472 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002473 self.config.SetString('core.bare', None)
2474 except Exception:
2475 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002476 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002477 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002478 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002479 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002480
David Pursehousee8ace262020-02-13 12:41:15 +09002481 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002482 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002483 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002484
David Pursehousee8ace262020-02-13 12:41:15 +09002485 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002486 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002487 if not os.path.exists(hooks):
2488 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002489 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002490 name = os.path.basename(stock_hook)
2491
Victor Boivie65e0f352011-04-18 11:23:29 +02002492 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002493 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002494 # Don't install a Gerrit Code Review hook if this
2495 # project does not appear to use it for reviews.
2496 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002497 # Since the manifest project is one of those, but also
2498 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002499 continue
2500
2501 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002502 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002503 continue
2504 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002505 # If the files are the same, we'll leave it alone. We create symlinks
2506 # below by default but fallback to hardlinks if the OS blocks them.
2507 # So if we're here, it's probably because we made a hardlink below.
2508 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002509 if not quiet:
2510 _warn("%s: Not replacing locally modified %s hook",
2511 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002512 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002513 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002514 platform_utils.symlink(
2515 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002516 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002517 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002518 try:
2519 os.link(stock_hook, dst)
2520 except OSError:
2521 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002522 else:
2523 raise
2524
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002525 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002526 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002527 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002528 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002529 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002530 remote.review = self.remote.review
2531 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002532
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002533 if self.worktree:
2534 remote.ResetFetch(mirror=False)
2535 else:
2536 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002537 remote.Save()
2538
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002539 def _InitMRef(self):
2540 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002541 if self.use_git_worktrees:
2542 # We can't update this ref with git worktrees until it exists.
2543 # We'll wait until the initial checkout to set it.
2544 if not os.path.exists(self.worktree):
2545 return
2546
2547 base = R_WORKTREE_M
2548 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002549
2550 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002551 else:
2552 base = R_M
2553 active_git = self.bare_git
2554
2555 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002556
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002557 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002558 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002559
Remy Böhmer1469c282020-12-15 18:49:02 +01002560 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002561 cur = self.bare_ref.symref(ref)
2562
2563 if self.revisionId:
2564 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2565 msg = 'manifest set to %s' % self.revisionId
2566 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002567 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002568 else:
2569 remote = self.GetRemote(self.remote.name)
2570 dst = remote.ToLocal(self.revisionExpr)
2571 if cur != dst:
2572 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002573 if detach:
2574 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2575 else:
2576 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002577
Kevin Degi384b3c52014-10-16 16:02:58 -06002578 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002579 # Git worktrees don't use symlinks to share at all.
2580 if self.use_git_worktrees:
2581 return
2582
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002583 symlink_files = self.shareable_files[:]
2584 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002585 if share_refs:
2586 symlink_files += self.working_tree_files
2587 symlink_dirs += self.working_tree_dirs
2588 to_symlink = symlink_files + symlink_dirs
2589 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002590 # Try to self-heal a bit in simple cases.
2591 dst_path = os.path.join(destdir, name)
2592 src_path = os.path.join(srcdir, name)
2593
2594 if name in self.working_tree_dirs:
2595 # If the dir is missing under .repo/projects/, create it.
2596 if not os.path.exists(src_path):
2597 os.makedirs(src_path)
2598
2599 elif name in self.working_tree_files:
2600 # If it's a file under the checkout .git/ and the .repo/projects/ has
2601 # nothing, move the file under the .repo/projects/ tree.
2602 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2603 platform_utils.rename(dst_path, src_path)
2604
2605 # If the path exists under the .repo/projects/ and there's no symlink
2606 # under the checkout .git/, recreate the symlink.
2607 if name in self.working_tree_dirs or name in self.working_tree_files:
2608 if os.path.exists(src_path) and not os.path.exists(dst_path):
2609 platform_utils.symlink(
2610 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2611
2612 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002613 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002614 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002615 # Fail if the links are pointing to the wrong place
2616 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002617 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002618 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002619 'work tree. If you\'re comfortable with the '
2620 'possibility of losing the work tree\'s git metadata,'
2621 ' use `repo sync --force-sync {0}` to '
2622 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002623
David James8d201162013-10-11 17:03:19 -07002624 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2625 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2626
2627 Args:
2628 gitdir: The bare git repository. Must already be initialized.
2629 dotgit: The repository you would like to initialize.
2630 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2631 Only one work tree can store refs under a given |gitdir|.
2632 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2633 This saves you the effort of initializing |dotgit| yourself.
2634 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002635 symlink_files = self.shareable_files[:]
2636 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002637 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002638 symlink_files += self.working_tree_files
2639 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002640 to_symlink = symlink_files + symlink_dirs
2641
2642 to_copy = []
2643 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002644 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002645
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002646 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002647 for name in set(to_copy).union(to_symlink):
2648 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002649 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002650 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002651
Kevin Degi384b3c52014-10-16 16:02:58 -06002652 if os.path.lexists(dst):
2653 continue
David James8d201162013-10-11 17:03:19 -07002654
2655 # If the source dir doesn't exist, create an empty dir.
2656 if name in symlink_dirs and not os.path.lexists(src):
2657 os.makedirs(src)
2658
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002659 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002660 platform_utils.symlink(
2661 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002662 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002663 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002664 shutil.copytree(src, dst)
2665 elif os.path.isfile(src):
2666 shutil.copy(src, dst)
2667
Conley Owens80b87fe2014-05-09 17:13:44 -07002668 # If the source file doesn't exist, ensure the destination
2669 # file doesn't either.
2670 if name in symlink_files and not os.path.lexists(src):
2671 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002672 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002673 except OSError:
2674 pass
2675
David James8d201162013-10-11 17:03:19 -07002676 except OSError as e:
2677 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002678 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002679 else:
2680 raise
2681
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002682 def _InitGitWorktree(self):
2683 """Init the project using git worktrees."""
2684 self.bare_git.worktree('prune')
2685 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2686 self.worktree, self.GetRevisionId())
2687
2688 # Rewrite the internal state files to use relative paths between the
2689 # checkouts & worktrees.
2690 dotgit = os.path.join(self.worktree, '.git')
2691 with open(dotgit, 'r') as fp:
2692 # Figure out the checkout->worktree path.
2693 setting = fp.read()
2694 assert setting.startswith('gitdir:')
2695 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002696 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2697 # of file permissions. Delete it and recreate it from scratch to avoid.
2698 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002699 # Use relative path from checkout->worktree & maintain Unix line endings
2700 # on all OS's to match git behavior.
2701 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002702 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2703 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002704 # Use relative path from worktree->checkout & maintain Unix line endings
2705 # on all OS's to match git behavior.
2706 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002707 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2708
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002709 self._InitMRef()
2710
Martin Kellye4e94d22017-03-21 16:05:12 -07002711 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002712 realdotgit = os.path.join(self.worktree, '.git')
2713 tmpdotgit = realdotgit + '.tmp'
2714 init_dotgit = not os.path.exists(realdotgit)
2715 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002716 if self.use_git_worktrees:
2717 self._InitGitWorktree()
2718 self._CopyAndLinkFiles()
2719 return
2720
Mike Frysingerf4545122019-11-11 04:34:16 -05002721 dotgit = tmpdotgit
2722 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2723 os.makedirs(tmpdotgit)
2724 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2725 copy_all=False)
2726 else:
2727 dotgit = realdotgit
2728
Kevin Degib1a07b82015-07-27 13:33:43 -06002729 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002730 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2731 except GitError as e:
2732 if force_sync and not init_dotgit:
2733 try:
2734 platform_utils.rmtree(dotgit)
2735 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002736 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002737 raise e
2738 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002739
Mike Frysingerf4545122019-11-11 04:34:16 -05002740 if init_dotgit:
2741 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002742
Mike Frysingerf4545122019-11-11 04:34:16 -05002743 # Now that the .git dir is fully set up, move it to its final home.
2744 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002745
Mike Frysingerf4545122019-11-11 04:34:16 -05002746 # Finish checking out the worktree.
2747 cmd = ['read-tree', '--reset', '-u']
2748 cmd.append('-v')
2749 cmd.append(HEAD)
2750 if GitCommand(self, cmd).Wait() != 0:
2751 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002752
Mike Frysingerf4545122019-11-11 04:34:16 -05002753 if submodules:
2754 self._SyncSubmodules(quiet=True)
2755 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002756
Renaud Paquay788e9622017-01-27 11:41:12 -08002757 def _get_symlink_error_message(self):
2758 if platform_utils.isWindows():
2759 return ('Unable to create symbolic link. Please re-run the command as '
2760 'Administrator, or see '
2761 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2762 'for other options.')
2763 return 'filesystem must support symlinks'
2764
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002765 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002766 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002767
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002768 def _revlist(self, *args, **kw):
2769 a = []
2770 a.extend(args)
2771 a.append('--')
2772 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002773
2774 @property
2775 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002776 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002777
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002778 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002779 """Get logs between two revisions of this project."""
2780 comp = '..'
2781 if rev1:
2782 revs = [rev1]
2783 if rev2:
2784 revs.extend([comp, rev2])
2785 cmd = ['log', ''.join(revs)]
2786 out = DiffColoring(self.config)
2787 if out.is_on and color:
2788 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002789 if pretty_format is not None:
2790 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002791 if oneline:
2792 cmd.append('--oneline')
2793
2794 try:
2795 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2796 if log.Wait() == 0:
2797 return log.stdout
2798 except GitError:
2799 # worktree may not exist if groups changed for example. In that case,
2800 # try in gitdir instead.
2801 if not os.path.exists(self.worktree):
2802 return self.bare_git.log(*cmd[1:])
2803 else:
2804 raise
2805 return None
2806
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002807 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2808 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002809 """Get the list of logs from this revision to given revisionId"""
2810 logs = {}
2811 selfId = self.GetRevisionId(self._allrefs)
2812 toId = toProject.GetRevisionId(toProject._allrefs)
2813
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002814 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2815 pretty_format=pretty_format)
2816 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2817 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002818 return logs
2819
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002820 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002821
David James8d201162013-10-11 17:03:19 -07002822 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002823 self._project = project
2824 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002825 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002826
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002827 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2828 def __getstate__(self):
2829 return (self._project, self._bare, self._gitdir)
2830
2831 def __setstate__(self, state):
2832 self._project, self._bare, self._gitdir = state
2833
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002834 def LsOthers(self):
2835 p = GitCommand(self._project,
2836 ['ls-files',
2837 '-z',
2838 '--others',
2839 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002840 bare=False,
David James8d201162013-10-11 17:03:19 -07002841 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002842 capture_stdout=True,
2843 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002844 if p.Wait() == 0:
2845 out = p.stdout
2846 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002847 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002848 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002849 return []
2850
2851 def DiffZ(self, name, *args):
2852 cmd = [name]
2853 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002854 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002855 cmd.extend(args)
2856 p = GitCommand(self._project,
2857 cmd,
David James8d201162013-10-11 17:03:19 -07002858 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002859 bare=False,
2860 capture_stdout=True,
2861 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002862 try:
2863 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04002864 if not hasattr(out, 'encode'):
2865 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002866 r = {}
2867 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09002868 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002869 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002870 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002871 info = next(out)
2872 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002873 except StopIteration:
2874 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002875
2876 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002877
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002878 def __init__(self, path, omode, nmode, oid, nid, state):
2879 self.path = path
2880 self.src_path = None
2881 self.old_mode = omode
2882 self.new_mode = nmode
2883 self.old_id = oid
2884 self.new_id = nid
2885
2886 if len(state) == 1:
2887 self.status = state
2888 self.level = None
2889 else:
2890 self.status = state[:1]
2891 self.level = state[1:]
2892 while self.level.startswith('0'):
2893 self.level = self.level[1:]
2894
2895 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002896 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002897 if info.status in ('R', 'C'):
2898 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002899 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002900 r[info.path] = info
2901 return r
2902 finally:
2903 p.Wait()
2904
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002905 def GetDotgitPath(self, subpath=None):
2906 """Return the full path to the .git dir.
2907
2908 As a convenience, append |subpath| if provided.
2909 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002910 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002911 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002912 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002913 dotgit = os.path.join(self._project.worktree, '.git')
2914 if os.path.isfile(dotgit):
2915 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
2916 with open(dotgit) as fp:
2917 setting = fp.read()
2918 assert setting.startswith('gitdir:')
2919 gitdir = setting.split(':', 1)[1].strip()
2920 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
2921
2922 return dotgit if subpath is None else os.path.join(dotgit, subpath)
2923
2924 def GetHead(self):
2925 """Return the ref that HEAD points to."""
2926 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002927 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05002928 with open(path) as fd:
2929 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04002930 except IOError as e:
2931 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002932 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302933 line = line.decode()
2934 except AttributeError:
2935 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002936 if line.startswith('ref: '):
2937 return line[5:-1]
2938 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002939
2940 def SetHead(self, ref, message=None):
2941 cmdv = []
2942 if message is not None:
2943 cmdv.extend(['-m', message])
2944 cmdv.append(HEAD)
2945 cmdv.append(ref)
2946 self.symbolic_ref(*cmdv)
2947
2948 def DetachHead(self, new, message=None):
2949 cmdv = ['--no-deref']
2950 if message is not None:
2951 cmdv.extend(['-m', message])
2952 cmdv.append(HEAD)
2953 cmdv.append(new)
2954 self.update_ref(*cmdv)
2955
2956 def UpdateRef(self, name, new, old=None,
2957 message=None,
2958 detach=False):
2959 cmdv = []
2960 if message is not None:
2961 cmdv.extend(['-m', message])
2962 if detach:
2963 cmdv.append('--no-deref')
2964 cmdv.append(name)
2965 cmdv.append(new)
2966 if old is not None:
2967 cmdv.append(old)
2968 self.update_ref(*cmdv)
2969
2970 def DeleteRef(self, name, old=None):
2971 if not old:
2972 old = self.rev_parse(name)
2973 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002974 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002975
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002976 def rev_list(self, *args, **kw):
2977 if 'format' in kw:
2978 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2979 else:
2980 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002981 cmdv.extend(args)
2982 p = GitCommand(self._project,
2983 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002984 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002985 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002986 capture_stdout=True,
2987 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002988 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002989 raise GitError('%s rev-list %s: %s' %
2990 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04002991 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002992
2993 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002994 """Allow arbitrary git commands using pythonic syntax.
2995
2996 This allows you to do things like:
2997 git_obj.rev_parse('HEAD')
2998
2999 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3000 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003001 Any other positional arguments will be passed to the git command, and the
3002 following keyword arguments are supported:
3003 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003004
3005 Args:
3006 name: The name of the git command to call. Any '_' characters will
3007 be replaced with '-'.
3008
3009 Returns:
3010 A callable object that will try to call git with the named command.
3011 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003012 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003013
Dave Borowitz091f8932012-10-23 17:01:04 -07003014 def runner(*args, **kwargs):
3015 cmdv = []
3016 config = kwargs.pop('config', None)
3017 for k in kwargs:
3018 raise TypeError('%s() got an unexpected keyword argument %r'
3019 % (name, k))
3020 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303021 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003022 cmdv.append('-c')
3023 cmdv.append('%s=%s' % (k, v))
3024 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003025 cmdv.extend(args)
3026 p = GitCommand(self._project,
3027 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003028 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003029 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003030 capture_stdout=True,
3031 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003032 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003033 raise GitError('%s %s: %s' %
3034 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003035 r = p.stdout
3036 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3037 return r[:-1]
3038 return r
3039 return runner
3040
3041
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003042class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003043
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003044 def __str__(self):
3045 return 'prior sync failed; rebase still in progress'
3046
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003047
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003048class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003049
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003050 def __str__(self):
3051 return 'contains uncommitted changes'
3052
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003053
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003054class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003055
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003056 def __init__(self, project, text):
3057 self.project = project
3058 self.text = text
3059
3060 def Print(self, syncbuf):
3061 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3062 syncbuf.out.nl()
3063
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003064
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003065class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003066
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003067 def __init__(self, project, why):
3068 self.project = project
3069 self.why = why
3070
3071 def Print(self, syncbuf):
3072 syncbuf.out.fail('error: %s/: %s',
3073 self.project.relpath,
3074 str(self.why))
3075 syncbuf.out.nl()
3076
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003077
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003078class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003079
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003080 def __init__(self, project, action):
3081 self.project = project
3082 self.action = action
3083
3084 def Run(self, syncbuf):
3085 out = syncbuf.out
3086 out.project('project %s/', self.project.relpath)
3087 out.nl()
3088 try:
3089 self.action()
3090 out.nl()
3091 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003092 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003093 out.nl()
3094 return False
3095
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003096
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003097class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003098
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003099 def __init__(self, config):
3100 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003101 self.project = self.printer('header', attr='bold')
3102 self.info = self.printer('info')
3103 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003104
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003105
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003106class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003107
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003108 def __init__(self, config, detach_head=False):
3109 self._messages = []
3110 self._failures = []
3111 self._later_queue1 = []
3112 self._later_queue2 = []
3113
3114 self.out = _SyncColoring(config)
3115 self.out.redirect(sys.stderr)
3116
3117 self.detach_head = detach_head
3118 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003119 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003120
3121 def info(self, project, fmt, *args):
3122 self._messages.append(_InfoMessage(project, fmt % args))
3123
3124 def fail(self, project, err=None):
3125 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003126 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003127
3128 def later1(self, project, what):
3129 self._later_queue1.append(_Later(project, what))
3130
3131 def later2(self, project, what):
3132 self._later_queue2.append(_Later(project, what))
3133
3134 def Finish(self):
3135 self._PrintMessages()
3136 self._RunLater()
3137 self._PrintMessages()
3138 return self.clean
3139
David Rileye0684ad2017-04-05 00:02:59 -07003140 def Recently(self):
3141 recent_clean = self.recent_clean
3142 self.recent_clean = True
3143 return recent_clean
3144
3145 def _MarkUnclean(self):
3146 self.clean = False
3147 self.recent_clean = False
3148
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003149 def _RunLater(self):
3150 for q in ['_later_queue1', '_later_queue2']:
3151 if not self._RunQueue(q):
3152 return
3153
3154 def _RunQueue(self, queue):
3155 for m in getattr(self, queue):
3156 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003157 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003158 return False
3159 setattr(self, queue, [])
3160 return True
3161
3162 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003163 if self._messages or self._failures:
3164 if os.isatty(2):
3165 self.out.write(progress.CSI_ERASE_LINE)
3166 self.out.write('\r')
3167
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003168 for m in self._messages:
3169 m.Print(self)
3170 for m in self._failures:
3171 m.Print(self)
3172
3173 self._messages = []
3174 self._failures = []
3175
3176
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003177class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003178
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003179 """A special project housed under .repo.
3180 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003181
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003182 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003183 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003184 manifest=manifest,
3185 name=name,
3186 gitdir=gitdir,
3187 objdir=gitdir,
3188 worktree=worktree,
3189 remote=RemoteSpec('origin'),
3190 relpath='.repo/%s' % name,
3191 revisionExpr='refs/heads/master',
3192 revisionId=None,
3193 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003194
3195 def PreSync(self):
3196 if self.Exists:
3197 cb = self.CurrentBranch
3198 if cb:
3199 base = self.GetBranch(cb).merge
3200 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003201 self.revisionExpr = base
3202 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003203
Martin Kelly224a31a2017-07-10 14:46:25 -07003204 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003205 """ Prepare MetaProject for manifest branch switch
3206 """
3207
3208 # detach and delete manifest branch, allowing a new
3209 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003210 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003211 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003212 syncbuf.Finish()
3213
3214 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003215 ['update-ref', '-d', 'refs/heads/default'],
3216 capture_stdout=True,
3217 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003218
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003219 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003220 def LastFetch(self):
3221 try:
3222 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3223 return os.path.getmtime(fh)
3224 except OSError:
3225 return 0
3226
3227 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003228 def HasChanges(self):
3229 """Has the remote received new commits not yet checked out?
3230 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003231 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003232 return False
3233
David Pursehouse8a68ff92012-09-24 12:15:13 +09003234 all_refs = self.bare_ref.all
3235 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003236 head = self.work_git.GetHead()
3237 if head.startswith(R_HEADS):
3238 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003239 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003240 except KeyError:
3241 head = None
3242
3243 if revid == head:
3244 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003245 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003246 return True
3247 return False