blob: 86f41f22df07de8df5a5c4657311aa3fae50fe3a [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
Ian Kasprzak0286e312021-02-05 10:06:18 -0800441
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700442class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600443 # These objects can be shared between several working trees.
444 shareable_files = ['description', 'info']
445 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
446 # These objects can only be used by a single working tree.
447 working_tree_files = ['config', 'packed-refs', 'shallow']
448 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700449
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700450 def __init__(self,
451 manifest,
452 name,
453 remote,
454 gitdir,
David James8d201162013-10-11 17:03:19 -0700455 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700456 worktree,
457 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700458 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800459 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100460 rebase=True,
461 groups=None,
462 sync_c=False,
463 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900464 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100465 clone_depth=None,
466 upstream=None,
467 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500468 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100469 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900470 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700471 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600472 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700473 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800474 """Init a Project object.
475
476 Args:
477 manifest: The XmlManifest object.
478 name: The `name` attribute of manifest.xml's project element.
479 remote: RemoteSpec object specifying its remote's properties.
480 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700481 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800482 worktree: Absolute path of git working tree.
483 relpath: Relative path of git working tree to repo's top directory.
484 revisionExpr: The `revision` attribute of manifest.xml's project element.
485 revisionId: git commit id for checking out.
486 rebase: The `rebase` attribute of manifest.xml's project element.
487 groups: The `groups` attribute of manifest.xml's project element.
488 sync_c: The `sync-c` attribute of manifest.xml's project element.
489 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900490 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800491 upstream: The `upstream` attribute of manifest.xml's project element.
492 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500493 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800494 is_derived: False if the project was explicitly defined in the manifest;
495 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400496 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900497 optimized_fetch: If True, when a project is set to a sha1 revision, only
498 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600499 retry_fetches: Retry remote fetches n times upon receiving transient error
500 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700501 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800502 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400503 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700504 self.name = name
505 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800506 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700507 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800508 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700509 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800510 else:
511 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700513 self.revisionExpr = revisionExpr
514
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700515 if revisionId is None \
516 and revisionExpr \
517 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700518 self.revisionId = revisionExpr
519 else:
520 self.revisionId = revisionId
521
Mike Pontillod3153822012-02-28 11:53:24 -0800522 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700523 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700524 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800525 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900526 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900527 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700528 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800529 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500530 # NB: Do not use this setting in __init__ to change behavior so that the
531 # manifest.git checkout can inspect & change it after instantiating. See
532 # the XmlManifest init code for more info.
533 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800534 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900535 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600536 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800537 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800538
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700539 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500541 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500542 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700543 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400544 defaults=self.client.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700545
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800546 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700547 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800548 else:
549 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700550 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700551 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700552 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400553 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700554 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700555
Doug Anderson37282b42011-03-04 11:54:18 -0800556 # This will be filled in if a project is later identified to be the
557 # project containing repo hooks.
558 self.enabled_repo_hooks = []
559
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700560 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800561 def Derived(self):
562 return self.is_derived
563
564 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700565 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700566 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567
568 @property
569 def CurrentBranch(self):
570 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400571
572 The branch name omits the 'refs/heads/' prefix.
573 None is returned if the project is on a detached HEAD, or if the work_git is
574 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700575 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400576 try:
577 b = self.work_git.GetHead()
578 except NoManifestException:
579 # If the local checkout is in a bad state, don't barf. Let the callers
580 # process this like the head is unreadable.
581 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700582 if b.startswith(R_HEADS):
583 return b[len(R_HEADS):]
584 return None
585
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700586 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500587 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
588 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
589 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200590
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700591 def IsDirty(self, consider_untracked=True):
592 """Is the working directory modified in some way?
593 """
594 self.work_git.update_index('-q',
595 '--unmerged',
596 '--ignore-missing',
597 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900598 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700599 return True
600 if self.work_git.DiffZ('diff-files'):
601 return True
602 if consider_untracked and self.work_git.LsOthers():
603 return True
604 return False
605
606 _userident_name = None
607 _userident_email = None
608
609 @property
610 def UserName(self):
611 """Obtain the user's personal name.
612 """
613 if self._userident_name is None:
614 self._LoadUserIdentity()
615 return self._userident_name
616
617 @property
618 def UserEmail(self):
619 """Obtain the user's email address. This is very likely
620 to be their Gerrit login.
621 """
622 if self._userident_email is None:
623 self._LoadUserIdentity()
624 return self._userident_email
625
626 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900627 u = self.bare_git.var('GIT_COMMITTER_IDENT')
628 m = re.compile("^(.*) <([^>]*)> ").match(u)
629 if m:
630 self._userident_name = m.group(1)
631 self._userident_email = m.group(2)
632 else:
633 self._userident_name = ''
634 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635
636 def GetRemote(self, name):
637 """Get the configuration for a single remote.
638 """
639 return self.config.GetRemote(name)
640
641 def GetBranch(self, name):
642 """Get the configuration for a single branch.
643 """
644 return self.config.GetBranch(name)
645
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700646 def GetBranches(self):
647 """Get all existing local branches.
648 """
649 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900650 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700651 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700652
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530653 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700654 if name.startswith(R_HEADS):
655 name = name[len(R_HEADS):]
656 b = self.GetBranch(name)
657 b.current = name == current
658 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900659 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700660 heads[name] = b
661
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530662 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700663 if name.startswith(R_PUB):
664 name = name[len(R_PUB):]
665 b = heads.get(name)
666 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900667 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700668
669 return heads
670
Colin Cross5acde752012-03-28 20:15:45 -0700671 def MatchesGroups(self, manifest_groups):
672 """Returns true if the manifest groups specified at init should cause
673 this project to be synced.
674 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700675 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700676
677 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700678 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700679 manifest_groups: "-group1,group2"
680 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500681
682 The special manifest group "default" will match any project that
683 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700684 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500685 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700686 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700687 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500688 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700689
Conley Owens971de8e2012-04-16 10:36:08 -0700690 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700691 for group in expanded_manifest_groups:
692 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700693 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700694 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700695 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700696
Conley Owens971de8e2012-04-16 10:36:08 -0700697 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700698
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700699# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700700 def UncommitedFiles(self, get_all=True):
701 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700703 Args:
704 get_all: a boolean, if True - get information about all different
705 uncommitted files. If False - return as soon as any kind of
706 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500707 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700708 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500709 self.work_git.update_index('-q',
710 '--unmerged',
711 '--ignore-missing',
712 '--refresh')
713 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700714 details.append("rebase in progress")
715 if not get_all:
716 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500717
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700718 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
719 if changes:
720 details.extend(changes)
721 if not get_all:
722 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500723
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700724 changes = self.work_git.DiffZ('diff-files').keys()
725 if changes:
726 details.extend(changes)
727 if not get_all:
728 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500729
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700730 changes = self.work_git.LsOthers()
731 if changes:
732 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500733
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700734 return details
735
736 def HasChanges(self):
737 """Returns true if there are uncommitted changes.
738 """
739 if self.UncommitedFiles(get_all=False):
740 return True
741 else:
742 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500743
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600744 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700745 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200746
747 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200748 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600749 quiet: If True then only print the project name. Do not print
750 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700752 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700753 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200754 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700755 print(file=output_redir)
756 print('project %s/' % self.relpath, file=output_redir)
757 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700758 return
759
760 self.work_git.update_index('-q',
761 '--unmerged',
762 '--ignore-missing',
763 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700764 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700765 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
766 df = self.work_git.DiffZ('diff-files')
767 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100768 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700769 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770
771 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700772 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200773 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700774 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600776 if quiet:
777 out.nl()
778 return 'DIRTY'
779
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780 branch = self.CurrentBranch
781 if branch is None:
782 out.nobranch('(*** NO BRANCH ***)')
783 else:
784 out.branch('branch %s', branch)
785 out.nl()
786
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700787 if rb:
788 out.important('prior sync failed; rebase still in progress')
789 out.nl()
790
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791 paths = list()
792 paths.extend(di.keys())
793 paths.extend(df.keys())
794 paths.extend(do)
795
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530796 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900797 try:
798 i = di[p]
799 except KeyError:
800 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700801
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900802 try:
803 f = df[p]
804 except KeyError:
805 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200806
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900807 if i:
808 i_status = i.status.upper()
809 else:
810 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900812 if f:
813 f_status = f.status.lower()
814 else:
815 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816
817 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800818 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700819 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700820 else:
821 line = ' %s%s\t%s' % (i_status, f_status, p)
822
823 if i and not f:
824 out.added('%s', line)
825 elif (i and f) or (not i and f):
826 out.changed('%s', line)
827 elif not i and not f:
828 out.untracked('%s', line)
829 else:
830 out.write('%s', line)
831 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200832
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700833 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834
pelyad67872d2012-03-28 14:49:58 +0300835 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 """Prints the status of the repository to stdout.
837 """
838 out = DiffColoring(self.config)
839 cmd = ['diff']
840 if out.is_on:
841 cmd.append('--color')
842 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300843 if absolute_paths:
844 cmd.append('--src-prefix=a/%s/' % self.relpath)
845 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400847 try:
848 p = GitCommand(self,
849 cmd,
850 capture_stdout=True,
851 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500852 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400853 except GitError as e:
854 out.nl()
855 out.project('project %s/' % self.relpath)
856 out.nl()
857 out.fail('%s', str(e))
858 out.nl()
859 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500860 if p.stdout:
861 out.nl()
862 out.project('project %s/' % self.relpath)
863 out.nl()
864 out.write(p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400865 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700866
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700867# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900868 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 """Was the branch published (uploaded) for code review?
870 If so, returns the SHA-1 hash of the last published
871 state for the branch.
872 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700873 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900874 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700875 try:
876 return self.bare_git.rev_parse(key)
877 except GitError:
878 return None
879 else:
880 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900881 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700882 except KeyError:
883 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700884
David Pursehouse8a68ff92012-09-24 12:15:13 +0900885 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886 """Prunes any stale published refs.
887 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900888 if all_refs is None:
889 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890 heads = set()
891 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530892 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893 if name.startswith(R_HEADS):
894 heads.add(name)
895 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900896 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700897
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530898 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899 n = name[len(R_PUB):]
900 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900901 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700903 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904 """List any branches which can be uploaded for review.
905 """
906 heads = {}
907 pubed = {}
908
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530909 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700910 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900911 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700912 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900913 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914
915 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530916 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900917 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700919 if selected_branch and branch != selected_branch:
920 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800922 rb = self.GetUploadableBranch(branch)
923 if rb:
924 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925 return ready
926
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800927 def GetUploadableBranch(self, branch_name):
928 """Get a single uploadable branch, or None.
929 """
930 branch = self.GetBranch(branch_name)
931 base = branch.LocalMerge
932 if branch.LocalMerge:
933 rb = ReviewableBranch(self, branch, base)
934 if rb.commits:
935 return rb
936 return None
937
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700938 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100939 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500940 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700941 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500942 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500943 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200944 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700945 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200946 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200947 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800948 validate_certs=True,
949 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950 """Uploads the named branch for code review.
951 """
952 if branch is None:
953 branch = self.CurrentBranch
954 if branch is None:
955 raise GitError('not currently on a branch')
956
957 branch = self.GetBranch(branch)
958 if not branch.LocalMerge:
959 raise GitError('branch %s does not track a remote' % branch.name)
960 if not branch.remote.review:
961 raise GitError('remote %s has no review url' % branch.remote.name)
962
Bryan Jacobsf609f912013-05-06 13:36:24 -0400963 if dest_branch is None:
964 dest_branch = self.dest_branch
965 if dest_branch is None:
966 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967 if not dest_branch.startswith(R_HEADS):
968 dest_branch = R_HEADS + dest_branch
969
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800970 if not branch.remote.projectname:
971 branch.remote.projectname = self.name
972 branch.remote.Save()
973
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200974 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800975 if url is None:
976 raise UploadError('review not configured')
977 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -0500978 if dryrun:
979 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800980
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800981 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -0800982 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700983
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800984 for push_option in (push_options or []):
985 cmd.append('-o')
986 cmd.append(push_option)
987
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800988 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800989
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800990 if dest_branch.startswith(R_HEADS):
991 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700992
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -0500993 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -0800994 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800995 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -0800996 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -0500997 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500998 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +0200999
David Pursehousef25a3702018-11-14 19:01:22 -08001000 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001001 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001002 if notify:
1003 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001004 if private:
1005 opts += ['private']
1006 if wip:
1007 opts += ['wip']
1008 if opts:
1009 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001010 cmd.append(ref_spec)
1011
Anthony King7bdac712014-07-16 12:56:40 +01001012 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001013 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001014
Mike Frysingerd7f86832020-11-19 19:18:46 -05001015 if not dryrun:
1016 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1017 self.bare_git.UpdateRef(R_PUB + branch.name,
1018 R_HEADS + branch.name,
1019 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001020
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001021# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001022 def _ExtractArchive(self, tarpath, path=None):
1023 """Extract the given tar on its current location
1024
1025 Args:
1026 - tarpath: The path to the actual tar file
1027
1028 """
1029 try:
1030 with tarfile.open(tarpath, 'r') as tar:
1031 tar.extractall(path=path)
1032 return True
1033 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001034 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001035 return False
1036
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001037 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001038 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001039 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001040 is_new=None,
1041 current_branch_only=False,
1042 force_sync=False,
1043 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001044 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001045 archive=False,
1046 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001047 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001048 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001049 submodules=False,
1050 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 """Perform only the network IO portion of the sync process.
1052 Local working directory/branch state is not affected.
1053 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001054 if archive and not isinstance(self, MetaProject):
1055 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001056 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001057 return False
1058
1059 name = self.relpath.replace('\\', '/')
1060 name = name.replace('/', '_')
1061 tarpath = '%s.tar' % name
1062 topdir = self.manifest.topdir
1063
1064 try:
1065 self._FetchArchive(tarpath, cwd=topdir)
1066 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001067 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001068 return False
1069
1070 # From now on, we only need absolute tarpath
1071 tarpath = os.path.join(topdir, tarpath)
1072
1073 if not self._ExtractArchive(tarpath, path=topdir):
1074 return False
1075 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001076 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001077 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001078 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001079 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001080 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001081 if is_new is None:
1082 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001083 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001084 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001085 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001086 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001088
1089 if is_new:
1090 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1091 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001092 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001093 # This works for both absolute and relative alternate directories.
1094 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001095 except IOError:
1096 alt_dir = None
1097 else:
1098 alt_dir = None
1099
Mike Frysingere50b6a72020-02-19 01:45:48 -05001100 if (clone_bundle
1101 and alt_dir is None
1102 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001103 is_new = False
1104
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001105 if not current_branch_only:
1106 if self.sync_c:
1107 current_branch_only = True
1108 elif not self.manifest._loaded:
1109 # Manifest cannot check defaults until it syncs.
1110 current_branch_only = False
1111 elif self.manifest.default.sync_c:
1112 current_branch_only = True
1113
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001114 if not self.sync_tags:
1115 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001116
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001117 if self.clone_depth:
1118 depth = self.clone_depth
1119 else:
1120 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1121
Mike Frysinger521d01b2020-02-17 01:51:49 -05001122 # See if we can skip the network fetch entirely.
1123 if not (optimized_fetch and
1124 (ID_RE.match(self.revisionExpr) and
1125 self._CheckForImmutableRevision())):
1126 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001127 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1128 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001129 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001130 submodules=submodules, force_sync=force_sync,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001131 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001132 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001133
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001134 mp = self.manifest.manifestProject
1135 dissociate = mp.config.GetBoolean('repo.dissociate')
1136 if dissociate:
1137 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1138 if os.path.exists(alternates_file):
1139 cmd = ['repack', '-a', '-d']
1140 if GitCommand(self, cmd, bare=True).Wait() != 0:
1141 return False
1142 platform_utils.remove(alternates_file)
1143
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001144 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001145 self._InitMRef()
1146 else:
1147 self._InitMirrorHead()
1148 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001149 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001150 except OSError:
1151 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001152 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001153
1154 def PostRepoUpgrade(self):
1155 self._InitHooks()
1156
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001157 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001158 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001159 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001160 for copyfile in self.copyfiles:
1161 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001162 for linkfile in self.linkfiles:
1163 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001164
Julien Camperguedd654222014-01-09 16:21:37 +01001165 def GetCommitRevisionId(self):
1166 """Get revisionId of a commit.
1167
1168 Use this method instead of GetRevisionId to get the id of the commit rather
1169 than the id of the current git object (for example, a tag)
1170
1171 """
1172 if not self.revisionExpr.startswith(R_TAGS):
1173 return self.GetRevisionId(self._allrefs)
1174
1175 try:
1176 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1177 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001178 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1179 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001180
David Pursehouse8a68ff92012-09-24 12:15:13 +09001181 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001182 if self.revisionId:
1183 return self.revisionId
1184
1185 rem = self.GetRemote(self.remote.name)
1186 rev = rem.ToLocal(self.revisionExpr)
1187
David Pursehouse8a68ff92012-09-24 12:15:13 +09001188 if all_refs is not None and rev in all_refs:
1189 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001190
1191 try:
1192 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1193 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001194 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1195 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001196
Raman Tenneti6a872c92021-01-14 19:17:50 -08001197 def SetRevisionId(self, revisionId):
1198 self.revisionId = revisionId
1199
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.
Ian Kasprzak0286e312021-02-05 10:06:18 -08001927 self.bare_git.rev_list('-1', '--missing=allow-any',
1928 '%s^0' % self.revisionExpr, '--')
Chris AtLee2fb64662014-01-16 21:32:33 -05001929 return True
1930 except GitError:
1931 # There is no such persistent revision. We have to fetch it.
1932 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001933
Julien Campergue335f5ef2013-10-16 11:02:35 +02001934 def _FetchArchive(self, tarpath, cwd=None):
1935 cmd = ['archive', '-v', '-o', tarpath]
1936 cmd.append('--remote=%s' % self.remote.url)
1937 cmd.append('--prefix=%s/' % self.relpath)
1938 cmd.append(self.revisionExpr)
1939
1940 command = GitCommand(self, cmd, cwd=cwd,
1941 capture_stdout=True,
1942 capture_stderr=True)
1943
1944 if command.Wait() != 0:
1945 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1946
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001947 def _RemoteFetch(self, name=None,
1948 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001949 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001950 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001951 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001952 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001953 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001954 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001955 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04001956 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07001957 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001958 clone_filter=None,
1959 retry_fetches=2,
1960 retry_sleep_initial_sec=4.0,
1961 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001962 is_sha1 = False
1963 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001964 # The depth should not be used when fetching to a mirror because
1965 # it will result in a shallow repository that cannot be cloned or
1966 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001967 # The repo project should also never be synced with partial depth.
1968 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1969 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001970
Shawn Pearce69e04d82014-01-29 12:48:54 -08001971 if depth:
1972 current_branch_only = True
1973
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001974 if ID_RE.match(self.revisionExpr) is not None:
1975 is_sha1 = True
1976
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001977 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001978 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001979 # this is a tag and its sha1 value should never change
1980 tag_name = self.revisionExpr[len(R_TAGS):]
1981
1982 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06001983 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05001984 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02001985 print('Skipped fetching project %s (already have persistent ref)'
1986 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001987 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001988 if is_sha1 and not depth:
1989 # When syncing a specific commit and --depth is not set:
1990 # * if upstream is explicitly specified and is not a sha1, fetch only
1991 # upstream as users expect only upstream to be fetch.
1992 # Note: The commit might not be in upstream in which case the sync
1993 # will fail.
1994 # * otherwise, fetch all branches to make sure we end up with the
1995 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02001996 if self.upstream:
1997 current_branch_only = not ID_RE.match(self.upstream)
1998 else:
1999 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002000
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002001 if not name:
2002 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002003
2004 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002005 remote = self.GetRemote(name)
2006 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002007 ssh_proxy = True
2008
Shawn O. Pearce88443382010-10-08 10:02:09 +02002009 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002010 if alt_dir and 'objects' == os.path.basename(alt_dir):
2011 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002012 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2013 remote = self.GetRemote(name)
2014
David Pursehouse8a68ff92012-09-24 12:15:13 +09002015 all_refs = self.bare_ref.all
2016 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002017 tmp = set()
2018
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302019 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002020 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002021 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002022 all_refs[r] = ref_id
2023 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002024 continue
2025
David Pursehouse8a68ff92012-09-24 12:15:13 +09002026 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002027 continue
2028
David Pursehouse8a68ff92012-09-24 12:15:13 +09002029 r = 'refs/_alt/%s' % ref_id
2030 all_refs[r] = ref_id
2031 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002032 tmp.add(r)
2033
heping3d7bbc92017-04-12 19:51:47 +08002034 tmp_packed_lines = []
2035 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002036
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302037 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002038 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002039 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002040 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002041 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002042
heping3d7bbc92017-04-12 19:51:47 +08002043 tmp_packed = ''.join(tmp_packed_lines)
2044 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002045 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002046 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002047 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002048
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002049 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002050
Xin Li745be2e2019-06-03 11:24:30 -07002051 if clone_filter:
2052 git_require((2, 19, 0), fail=True, msg='partial clones')
2053 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002054 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002055
Conley Owensf97e8382015-01-21 11:12:46 -08002056 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002057 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002058 else:
2059 # If this repo has shallow objects, then we don't know which refs have
2060 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2061 # do this with projects that don't have shallow objects, since it is less
2062 # efficient.
2063 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2064 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002065
Mike Frysinger4847e052020-02-22 00:07:35 -05002066 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002067 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002068 if not quiet and sys.stdout.isatty():
2069 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002070 if not self.worktree:
2071 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002072 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002073
Mike Frysingere57f1142019-03-18 21:27:54 -04002074 if force_sync:
2075 cmd.append('--force')
2076
David Pursehouse74cfd272015-10-14 10:50:15 +09002077 if prune:
2078 cmd.append('--prune')
2079
Martin Kellye4e94d22017-03-21 16:05:12 -07002080 if submodules:
2081 cmd.append('--recurse-submodules=on-demand')
2082
Kuang-che Wu6856f982019-11-25 12:37:55 +08002083 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002084 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002085 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002086 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002087 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002088 spec.append('tag')
2089 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002090
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302091 if self.manifest.IsMirror and not current_branch_only:
2092 branch = None
2093 else:
2094 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002095 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002096 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002097 # Shallow checkout of a specific commit, fetch from that commit and not
2098 # the heads only as the commit might be deeper in the history.
2099 spec.append(branch)
2100 else:
2101 if is_sha1:
2102 branch = self.upstream
2103 if branch is not None and branch.strip():
2104 if not branch.startswith('refs/'):
2105 branch = R_HEADS + branch
2106 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2107
2108 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2109 # whole repo.
2110 if self.manifest.IsMirror and not spec:
2111 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2112
2113 # If using depth then we should not get all the tags since they may
2114 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002115 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002116 cmd.append('--no-tags')
2117 else:
2118 cmd.append('--tags')
2119 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2120
Conley Owens80b87fe2014-05-09 17:13:44 -07002121 cmd.extend(spec)
2122
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002123 # At least one retry minimum due to git remote prune.
2124 retry_fetches = max(retry_fetches, 2)
2125 retry_cur_sleep = retry_sleep_initial_sec
2126 ok = prune_tried = False
2127 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002128 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger4847e052020-02-22 00:07:35 -05002129 merge_output=True, capture_stdout=quiet)
John L. Villalovos126e2982015-01-29 21:58:12 -08002130 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002131 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002132 ok = True
2133 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002134
2135 # Retry later due to HTTP 429 Too Many Requests.
2136 elif ('error:' in gitcmd.stderr and
2137 'HTTP 429' in gitcmd.stderr):
2138 if not quiet:
2139 print('429 received, sleeping: %s sec' % retry_cur_sleep,
2140 file=sys.stderr)
2141 time.sleep(retry_cur_sleep)
2142 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2143 MAXIMUM_RETRY_SLEEP_SEC)
2144 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2145 RETRY_JITTER_PERCENT))
2146 continue
2147
2148 # If this is not last attempt, try 'git remote prune'.
2149 elif (try_n < retry_fetches - 1 and
2150 'error:' in gitcmd.stderr and
2151 'git remote prune' in gitcmd.stderr and
2152 not prune_tried):
2153 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002154 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002155 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002156 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002157 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002158 break
2159 continue
Brian Harring14a66742012-09-28 20:21:57 -07002160 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002161 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2162 # in sha1 mode, we just tried sync'ing from the upstream field; it
2163 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002164 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002165 elif ret < 0:
2166 # Git died with a signal, exit immediately
2167 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002168 if not verbose:
2169 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002170 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002171
2172 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002173 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002174 if old_packed != '':
2175 _lwrite(packed_refs, old_packed)
2176 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002177 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002178 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002179
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002180 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002181 # We just synced the upstream given branch; verify we
2182 # got what we wanted, else trigger a second run of all
2183 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002184 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002185 # Sync the current branch only with depth set to None.
2186 # We always pass depth=None down to avoid infinite recursion.
2187 return self._RemoteFetch(
2188 name=name, quiet=quiet, verbose=verbose,
2189 current_branch_only=current_branch_only and depth,
2190 initial=False, alt_dir=alt_dir,
2191 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002192
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002193 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002194
Mike Frysingere50b6a72020-02-19 01:45:48 -05002195 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002196 if initial and \
2197 (self.manifest.manifestProject.config.GetString('repo.depth') or
2198 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002199 return False
2200
2201 remote = self.GetRemote(self.remote.name)
2202 bundle_url = remote.url + '/clone.bundle'
2203 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002204 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2205 'persistent-http',
2206 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002207 return False
2208
2209 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2210 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2211
2212 exist_dst = os.path.exists(bundle_dst)
2213 exist_tmp = os.path.exists(bundle_tmp)
2214
2215 if not initial and not exist_dst and not exist_tmp:
2216 return False
2217
2218 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002219 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2220 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002221 if not exist_dst:
2222 return False
2223
2224 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002225 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002226 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002227 if not quiet and sys.stdout.isatty():
2228 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002229 if not self.worktree:
2230 cmd.append('--update-head-ok')
2231 cmd.append(bundle_dst)
2232 for f in remote.fetch:
2233 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002234 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002235
2236 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002237 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002238 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002239 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002240 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002241 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002242
Mike Frysingere50b6a72020-02-19 01:45:48 -05002243 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002244 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002245 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002246
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002247 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002248 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002249 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002250 if os.path.exists(tmpPath):
2251 size = os.stat(tmpPath).st_size
2252 if size >= 1024:
2253 cmd += ['--continue-at', '%d' % (size,)]
2254 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002255 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002256 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002257 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002258 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002259 if proxy:
2260 cmd += ['--proxy', proxy]
2261 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2262 cmd += ['--proxy', os.environ['http_proxy']]
2263 if srcUrl.startswith('persistent-https'):
2264 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2265 elif srcUrl.startswith('persistent-http'):
2266 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002267 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002268
Dave Borowitz137d0132015-01-02 11:12:54 -08002269 if IsTrace():
2270 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002271 if verbose:
2272 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2273 stdout = None if verbose else subprocess.PIPE
2274 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002275 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002276 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002277 except OSError:
2278 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002279
Mike Frysingere50b6a72020-02-19 01:45:48 -05002280 (output, _) = proc.communicate()
2281 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002282
Dave Borowitz137d0132015-01-02 11:12:54 -08002283 if curlret == 22:
2284 # From curl man page:
2285 # 22: HTTP page not retrieved. The requested url was not found or
2286 # returned another error with the HTTP error code being 400 or above.
2287 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002288 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002289 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2290 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002291 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002292 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002293 elif curlret and not verbose and output:
2294 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002295
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002296 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002297 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002298 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002299 return True
2300 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002301 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002302 return False
2303 else:
2304 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002305
Kris Giesingc8d882a2014-12-23 13:02:32 -08002306 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002307 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002308 with open(path, 'rb') as f:
2309 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002310 return True
2311 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002312 if not quiet:
2313 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002314 return False
2315 except OSError:
2316 return False
2317
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002318 def _Checkout(self, rev, quiet=False):
2319 cmd = ['checkout']
2320 if quiet:
2321 cmd.append('-q')
2322 cmd.append(rev)
2323 cmd.append('--')
2324 if GitCommand(self, cmd).Wait() != 0:
2325 if self._allrefs:
2326 raise GitError('%s checkout %s ' % (self.name, rev))
2327
Mike Frysinger915fda12020-03-22 12:15:20 -04002328 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002329 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002330 if ffonly:
2331 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002332 if record_origin:
2333 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002334 cmd.append(rev)
2335 cmd.append('--')
2336 if GitCommand(self, cmd).Wait() != 0:
2337 if self._allrefs:
2338 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2339
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302340 def _LsRemote(self, refs):
2341 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302342 p = GitCommand(self, cmd, capture_stdout=True)
2343 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002344 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302345 return None
2346
Anthony King7bdac712014-07-16 12:56:40 +01002347 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002348 cmd = ['revert']
2349 cmd.append('--no-edit')
2350 cmd.append(rev)
2351 cmd.append('--')
2352 if GitCommand(self, cmd).Wait() != 0:
2353 if self._allrefs:
2354 raise GitError('%s revert %s ' % (self.name, rev))
2355
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002356 def _ResetHard(self, rev, quiet=True):
2357 cmd = ['reset', '--hard']
2358 if quiet:
2359 cmd.append('-q')
2360 cmd.append(rev)
2361 if GitCommand(self, cmd).Wait() != 0:
2362 raise GitError('%s reset --hard %s ' % (self.name, rev))
2363
Martin Kellye4e94d22017-03-21 16:05:12 -07002364 def _SyncSubmodules(self, quiet=True):
2365 cmd = ['submodule', 'update', '--init', '--recursive']
2366 if quiet:
2367 cmd.append('-q')
2368 if GitCommand(self, cmd).Wait() != 0:
2369 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2370
Anthony King7bdac712014-07-16 12:56:40 +01002371 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002372 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002373 if onto is not None:
2374 cmd.extend(['--onto', onto])
2375 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002376 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002377 raise GitError('%s rebase %s ' % (self.name, upstream))
2378
Pierre Tardy3d125942012-05-04 12:18:12 +02002379 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002380 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002381 if ffonly:
2382 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002383 if GitCommand(self, cmd).Wait() != 0:
2384 raise GitError('%s merge %s ' % (self.name, head))
2385
David Pursehousee8ace262020-02-13 12:41:15 +09002386 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002387 init_git_dir = not os.path.exists(self.gitdir)
2388 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002389 try:
2390 # Initialize the bare repository, which contains all of the objects.
2391 if init_obj_dir:
2392 os.makedirs(self.objdir)
2393 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002394
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002395 if self.use_git_worktrees:
2396 # Set up the m/ space to point to the worktree-specific ref space.
2397 # We'll update the worktree-specific ref space on each checkout.
2398 if self.manifest.branch:
2399 self.bare_git.symbolic_ref(
2400 '-m', 'redirecting to worktree scope',
2401 R_M + self.manifest.branch,
2402 R_WORKTREE_M + self.manifest.branch)
2403
2404 # Enable per-worktree config file support if possible. This is more a
2405 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002406 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002407 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002408
Kevin Degib1a07b82015-07-27 13:33:43 -06002409 # If we have a separate directory to hold refs, initialize it as well.
2410 if self.objdir != self.gitdir:
2411 if init_git_dir:
2412 os.makedirs(self.gitdir)
2413
2414 if init_obj_dir or init_git_dir:
2415 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2416 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002417 try:
2418 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2419 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002420 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002421 print("Retrying clone after deleting %s" %
2422 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002423 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002424 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2425 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002426 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002427 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002428 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2429 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002430 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002431 raise e
2432 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002433
Kevin Degi384b3c52014-10-16 16:02:58 -06002434 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002435 mp = self.manifest.manifestProject
2436 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002437
Kevin Degib1a07b82015-07-27 13:33:43 -06002438 if ref_dir or mirror_git:
2439 if not mirror_git:
2440 mirror_git = os.path.join(ref_dir, self.name + '.git')
2441 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2442 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002443 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2444 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002445
Kevin Degib1a07b82015-07-27 13:33:43 -06002446 if os.path.exists(mirror_git):
2447 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002448 elif os.path.exists(repo_git):
2449 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002450 elif os.path.exists(worktrees_git):
2451 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002452 else:
2453 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002454
Kevin Degib1a07b82015-07-27 13:33:43 -06002455 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002456 if not os.path.isabs(ref_dir):
2457 # The alternate directory is relative to the object database.
2458 ref_dir = os.path.relpath(ref_dir,
2459 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002460 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2461 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002462
David Pursehousee8ace262020-02-13 12:41:15 +09002463 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002464
2465 m = self.manifest.manifestProject.config
2466 for key in ['user.name', 'user.email']:
2467 if m.Has(key, include_defaults=False):
2468 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002469 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002470 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002471 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002472 except Exception:
2473 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002474 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002475 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002476 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002477 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002478
David Pursehousee8ace262020-02-13 12:41:15 +09002479 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002480 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002481 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002482
David Pursehousee8ace262020-02-13 12:41:15 +09002483 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002484 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002485 if not os.path.exists(hooks):
2486 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002487 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002488 name = os.path.basename(stock_hook)
2489
Victor Boivie65e0f352011-04-18 11:23:29 +02002490 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002491 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002492 # Don't install a Gerrit Code Review hook if this
2493 # project does not appear to use it for reviews.
2494 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002495 # Since the manifest project is one of those, but also
2496 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002497 continue
2498
2499 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002500 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002501 continue
2502 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002503 # If the files are the same, we'll leave it alone. We create symlinks
2504 # below by default but fallback to hardlinks if the OS blocks them.
2505 # So if we're here, it's probably because we made a hardlink below.
2506 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002507 if not quiet:
2508 _warn("%s: Not replacing locally modified %s hook",
2509 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002510 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002511 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002512 platform_utils.symlink(
2513 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002514 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002515 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002516 try:
2517 os.link(stock_hook, dst)
2518 except OSError:
2519 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002520 else:
2521 raise
2522
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002523 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002524 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002525 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002526 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002527 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002528 remote.review = self.remote.review
2529 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002530
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002531 if self.worktree:
2532 remote.ResetFetch(mirror=False)
2533 else:
2534 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002535 remote.Save()
2536
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002537 def _InitMRef(self):
2538 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002539 if self.use_git_worktrees:
2540 # We can't update this ref with git worktrees until it exists.
2541 # We'll wait until the initial checkout to set it.
2542 if not os.path.exists(self.worktree):
2543 return
2544
2545 base = R_WORKTREE_M
2546 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002547
2548 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002549 else:
2550 base = R_M
2551 active_git = self.bare_git
2552
2553 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002554
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002555 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002556 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002557
Remy Böhmer1469c282020-12-15 18:49:02 +01002558 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002559 cur = self.bare_ref.symref(ref)
2560
2561 if self.revisionId:
2562 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2563 msg = 'manifest set to %s' % self.revisionId
2564 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002565 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002566 else:
2567 remote = self.GetRemote(self.remote.name)
2568 dst = remote.ToLocal(self.revisionExpr)
2569 if cur != dst:
2570 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002571 if detach:
2572 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2573 else:
2574 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002575
Kevin Degi384b3c52014-10-16 16:02:58 -06002576 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002577 # Git worktrees don't use symlinks to share at all.
2578 if self.use_git_worktrees:
2579 return
2580
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002581 symlink_files = self.shareable_files[:]
2582 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002583 if share_refs:
2584 symlink_files += self.working_tree_files
2585 symlink_dirs += self.working_tree_dirs
2586 to_symlink = symlink_files + symlink_dirs
2587 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002588 # Try to self-heal a bit in simple cases.
2589 dst_path = os.path.join(destdir, name)
2590 src_path = os.path.join(srcdir, name)
2591
2592 if name in self.working_tree_dirs:
2593 # If the dir is missing under .repo/projects/, create it.
2594 if not os.path.exists(src_path):
2595 os.makedirs(src_path)
2596
2597 elif name in self.working_tree_files:
2598 # If it's a file under the checkout .git/ and the .repo/projects/ has
2599 # nothing, move the file under the .repo/projects/ tree.
2600 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2601 platform_utils.rename(dst_path, src_path)
2602
2603 # If the path exists under the .repo/projects/ and there's no symlink
2604 # under the checkout .git/, recreate the symlink.
2605 if name in self.working_tree_dirs or name in self.working_tree_files:
2606 if os.path.exists(src_path) and not os.path.exists(dst_path):
2607 platform_utils.symlink(
2608 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2609
2610 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002611 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002612 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002613 # Fail if the links are pointing to the wrong place
2614 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002615 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002616 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002617 'work tree. If you\'re comfortable with the '
2618 'possibility of losing the work tree\'s git metadata,'
2619 ' use `repo sync --force-sync {0}` to '
2620 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002621
David James8d201162013-10-11 17:03:19 -07002622 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2623 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2624
2625 Args:
2626 gitdir: The bare git repository. Must already be initialized.
2627 dotgit: The repository you would like to initialize.
2628 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2629 Only one work tree can store refs under a given |gitdir|.
2630 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2631 This saves you the effort of initializing |dotgit| yourself.
2632 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002633 symlink_files = self.shareable_files[:]
2634 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002635 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002636 symlink_files += self.working_tree_files
2637 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002638 to_symlink = symlink_files + symlink_dirs
2639
2640 to_copy = []
2641 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002642 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002643
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002644 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002645 for name in set(to_copy).union(to_symlink):
2646 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002647 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002648 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002649
Kevin Degi384b3c52014-10-16 16:02:58 -06002650 if os.path.lexists(dst):
2651 continue
David James8d201162013-10-11 17:03:19 -07002652
2653 # If the source dir doesn't exist, create an empty dir.
2654 if name in symlink_dirs and not os.path.lexists(src):
2655 os.makedirs(src)
2656
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002657 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002658 platform_utils.symlink(
2659 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002660 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002661 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002662 shutil.copytree(src, dst)
2663 elif os.path.isfile(src):
2664 shutil.copy(src, dst)
2665
Conley Owens80b87fe2014-05-09 17:13:44 -07002666 # If the source file doesn't exist, ensure the destination
2667 # file doesn't either.
2668 if name in symlink_files and not os.path.lexists(src):
2669 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002670 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002671 except OSError:
2672 pass
2673
David James8d201162013-10-11 17:03:19 -07002674 except OSError as e:
2675 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002676 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002677 else:
2678 raise
2679
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002680 def _InitGitWorktree(self):
2681 """Init the project using git worktrees."""
2682 self.bare_git.worktree('prune')
2683 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2684 self.worktree, self.GetRevisionId())
2685
2686 # Rewrite the internal state files to use relative paths between the
2687 # checkouts & worktrees.
2688 dotgit = os.path.join(self.worktree, '.git')
2689 with open(dotgit, 'r') as fp:
2690 # Figure out the checkout->worktree path.
2691 setting = fp.read()
2692 assert setting.startswith('gitdir:')
2693 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002694 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2695 # of file permissions. Delete it and recreate it from scratch to avoid.
2696 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002697 # Use relative path from checkout->worktree & maintain Unix line endings
2698 # on all OS's to match git behavior.
2699 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002700 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2701 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002702 # Use relative path from worktree->checkout & maintain Unix line endings
2703 # on all OS's to match git behavior.
2704 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002705 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2706
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002707 self._InitMRef()
2708
Martin Kellye4e94d22017-03-21 16:05:12 -07002709 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002710 realdotgit = os.path.join(self.worktree, '.git')
2711 tmpdotgit = realdotgit + '.tmp'
2712 init_dotgit = not os.path.exists(realdotgit)
2713 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002714 if self.use_git_worktrees:
2715 self._InitGitWorktree()
2716 self._CopyAndLinkFiles()
2717 return
2718
Mike Frysingerf4545122019-11-11 04:34:16 -05002719 dotgit = tmpdotgit
2720 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2721 os.makedirs(tmpdotgit)
2722 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2723 copy_all=False)
2724 else:
2725 dotgit = realdotgit
2726
Kevin Degib1a07b82015-07-27 13:33:43 -06002727 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002728 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2729 except GitError as e:
2730 if force_sync and not init_dotgit:
2731 try:
2732 platform_utils.rmtree(dotgit)
2733 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002734 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002735 raise e
2736 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002737
Mike Frysingerf4545122019-11-11 04:34:16 -05002738 if init_dotgit:
2739 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002740
Mike Frysingerf4545122019-11-11 04:34:16 -05002741 # Now that the .git dir is fully set up, move it to its final home.
2742 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002743
Mike Frysingerf4545122019-11-11 04:34:16 -05002744 # Finish checking out the worktree.
2745 cmd = ['read-tree', '--reset', '-u']
2746 cmd.append('-v')
2747 cmd.append(HEAD)
2748 if GitCommand(self, cmd).Wait() != 0:
2749 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002750
Mike Frysingerf4545122019-11-11 04:34:16 -05002751 if submodules:
2752 self._SyncSubmodules(quiet=True)
2753 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002754
Renaud Paquay788e9622017-01-27 11:41:12 -08002755 def _get_symlink_error_message(self):
2756 if platform_utils.isWindows():
2757 return ('Unable to create symbolic link. Please re-run the command as '
2758 'Administrator, or see '
2759 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2760 'for other options.')
2761 return 'filesystem must support symlinks'
2762
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002763 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002764 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002765
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002766 def _revlist(self, *args, **kw):
2767 a = []
2768 a.extend(args)
2769 a.append('--')
2770 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002771
2772 @property
2773 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002774 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002775
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002776 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002777 """Get logs between two revisions of this project."""
2778 comp = '..'
2779 if rev1:
2780 revs = [rev1]
2781 if rev2:
2782 revs.extend([comp, rev2])
2783 cmd = ['log', ''.join(revs)]
2784 out = DiffColoring(self.config)
2785 if out.is_on and color:
2786 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002787 if pretty_format is not None:
2788 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002789 if oneline:
2790 cmd.append('--oneline')
2791
2792 try:
2793 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2794 if log.Wait() == 0:
2795 return log.stdout
2796 except GitError:
2797 # worktree may not exist if groups changed for example. In that case,
2798 # try in gitdir instead.
2799 if not os.path.exists(self.worktree):
2800 return self.bare_git.log(*cmd[1:])
2801 else:
2802 raise
2803 return None
2804
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002805 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2806 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002807 """Get the list of logs from this revision to given revisionId"""
2808 logs = {}
2809 selfId = self.GetRevisionId(self._allrefs)
2810 toId = toProject.GetRevisionId(toProject._allrefs)
2811
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002812 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2813 pretty_format=pretty_format)
2814 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2815 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002816 return logs
2817
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002818 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002819
David James8d201162013-10-11 17:03:19 -07002820 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002821 self._project = project
2822 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002823 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002824
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002825 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2826 def __getstate__(self):
2827 return (self._project, self._bare, self._gitdir)
2828
2829 def __setstate__(self, state):
2830 self._project, self._bare, self._gitdir = state
2831
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002832 def LsOthers(self):
2833 p = GitCommand(self._project,
2834 ['ls-files',
2835 '-z',
2836 '--others',
2837 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002838 bare=False,
David James8d201162013-10-11 17:03:19 -07002839 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002840 capture_stdout=True,
2841 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002842 if p.Wait() == 0:
2843 out = p.stdout
2844 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002845 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002846 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002847 return []
2848
2849 def DiffZ(self, name, *args):
2850 cmd = [name]
2851 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002852 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002853 cmd.extend(args)
2854 p = GitCommand(self._project,
2855 cmd,
David James8d201162013-10-11 17:03:19 -07002856 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002857 bare=False,
2858 capture_stdout=True,
2859 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002860 p.Wait()
2861 r = {}
2862 out = p.stdout
2863 if out:
2864 out = iter(out[:-1].split('\0'))
2865 while out:
2866 try:
2867 info = next(out)
2868 path = next(out)
2869 except StopIteration:
2870 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002871
Mike Frysinger84230002021-02-16 17:08:35 -05002872 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002873
Mike Frysinger84230002021-02-16 17:08:35 -05002874 def __init__(self, path, omode, nmode, oid, nid, state):
2875 self.path = path
2876 self.src_path = None
2877 self.old_mode = omode
2878 self.new_mode = nmode
2879 self.old_id = oid
2880 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002881
Mike Frysinger84230002021-02-16 17:08:35 -05002882 if len(state) == 1:
2883 self.status = state
2884 self.level = None
2885 else:
2886 self.status = state[:1]
2887 self.level = state[1:]
2888 while self.level.startswith('0'):
2889 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002890
Mike Frysinger84230002021-02-16 17:08:35 -05002891 info = info[1:].split(' ')
2892 info = _Info(path, *info)
2893 if info.status in ('R', 'C'):
2894 info.src_path = info.path
2895 info.path = next(out)
2896 r[info.path] = info
2897 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002898
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002899 def GetDotgitPath(self, subpath=None):
2900 """Return the full path to the .git dir.
2901
2902 As a convenience, append |subpath| if provided.
2903 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002904 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002905 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002906 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002907 dotgit = os.path.join(self._project.worktree, '.git')
2908 if os.path.isfile(dotgit):
2909 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
2910 with open(dotgit) as fp:
2911 setting = fp.read()
2912 assert setting.startswith('gitdir:')
2913 gitdir = setting.split(':', 1)[1].strip()
2914 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
2915
2916 return dotgit if subpath is None else os.path.join(dotgit, subpath)
2917
2918 def GetHead(self):
2919 """Return the ref that HEAD points to."""
2920 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002921 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05002922 with open(path) as fd:
2923 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04002924 except IOError as e:
2925 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002926 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302927 line = line.decode()
2928 except AttributeError:
2929 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002930 if line.startswith('ref: '):
2931 return line[5:-1]
2932 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002933
2934 def SetHead(self, ref, message=None):
2935 cmdv = []
2936 if message is not None:
2937 cmdv.extend(['-m', message])
2938 cmdv.append(HEAD)
2939 cmdv.append(ref)
2940 self.symbolic_ref(*cmdv)
2941
2942 def DetachHead(self, new, message=None):
2943 cmdv = ['--no-deref']
2944 if message is not None:
2945 cmdv.extend(['-m', message])
2946 cmdv.append(HEAD)
2947 cmdv.append(new)
2948 self.update_ref(*cmdv)
2949
2950 def UpdateRef(self, name, new, old=None,
2951 message=None,
2952 detach=False):
2953 cmdv = []
2954 if message is not None:
2955 cmdv.extend(['-m', message])
2956 if detach:
2957 cmdv.append('--no-deref')
2958 cmdv.append(name)
2959 cmdv.append(new)
2960 if old is not None:
2961 cmdv.append(old)
2962 self.update_ref(*cmdv)
2963
2964 def DeleteRef(self, name, old=None):
2965 if not old:
2966 old = self.rev_parse(name)
2967 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002968 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002969
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002970 def rev_list(self, *args, **kw):
2971 if 'format' in kw:
2972 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2973 else:
2974 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002975 cmdv.extend(args)
2976 p = GitCommand(self._project,
2977 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002978 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002979 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002980 capture_stdout=True,
2981 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002982 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002983 raise GitError('%s rev-list %s: %s' %
2984 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04002985 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002986
2987 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002988 """Allow arbitrary git commands using pythonic syntax.
2989
2990 This allows you to do things like:
2991 git_obj.rev_parse('HEAD')
2992
2993 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2994 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002995 Any other positional arguments will be passed to the git command, and the
2996 following keyword arguments are supported:
2997 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002998
2999 Args:
3000 name: The name of the git command to call. Any '_' characters will
3001 be replaced with '-'.
3002
3003 Returns:
3004 A callable object that will try to call git with the named command.
3005 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003006 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003007
Dave Borowitz091f8932012-10-23 17:01:04 -07003008 def runner(*args, **kwargs):
3009 cmdv = []
3010 config = kwargs.pop('config', None)
3011 for k in kwargs:
3012 raise TypeError('%s() got an unexpected keyword argument %r'
3013 % (name, k))
3014 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303015 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003016 cmdv.append('-c')
3017 cmdv.append('%s=%s' % (k, v))
3018 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003019 cmdv.extend(args)
3020 p = GitCommand(self._project,
3021 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003022 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003023 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003024 capture_stdout=True,
3025 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003026 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003027 raise GitError('%s %s: %s' %
3028 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003029 r = p.stdout
3030 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3031 return r[:-1]
3032 return r
3033 return runner
3034
3035
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003036class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003037
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003038 def __str__(self):
3039 return 'prior sync failed; rebase still in progress'
3040
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003041
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003042class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003043
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003044 def __str__(self):
3045 return 'contains uncommitted changes'
3046
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003047
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003048class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003049
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003050 def __init__(self, project, text):
3051 self.project = project
3052 self.text = text
3053
3054 def Print(self, syncbuf):
3055 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3056 syncbuf.out.nl()
3057
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003058
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003059class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003060
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003061 def __init__(self, project, why):
3062 self.project = project
3063 self.why = why
3064
3065 def Print(self, syncbuf):
3066 syncbuf.out.fail('error: %s/: %s',
3067 self.project.relpath,
3068 str(self.why))
3069 syncbuf.out.nl()
3070
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003071
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003072class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003073
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003074 def __init__(self, project, action):
3075 self.project = project
3076 self.action = action
3077
3078 def Run(self, syncbuf):
3079 out = syncbuf.out
3080 out.project('project %s/', self.project.relpath)
3081 out.nl()
3082 try:
3083 self.action()
3084 out.nl()
3085 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003086 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003087 out.nl()
3088 return False
3089
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003090
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003091class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003092
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003093 def __init__(self, config):
3094 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003095 self.project = self.printer('header', attr='bold')
3096 self.info = self.printer('info')
3097 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003098
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003099
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003100class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003101
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003102 def __init__(self, config, detach_head=False):
3103 self._messages = []
3104 self._failures = []
3105 self._later_queue1 = []
3106 self._later_queue2 = []
3107
3108 self.out = _SyncColoring(config)
3109 self.out.redirect(sys.stderr)
3110
3111 self.detach_head = detach_head
3112 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003113 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003114
3115 def info(self, project, fmt, *args):
3116 self._messages.append(_InfoMessage(project, fmt % args))
3117
3118 def fail(self, project, err=None):
3119 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003120 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003121
3122 def later1(self, project, what):
3123 self._later_queue1.append(_Later(project, what))
3124
3125 def later2(self, project, what):
3126 self._later_queue2.append(_Later(project, what))
3127
3128 def Finish(self):
3129 self._PrintMessages()
3130 self._RunLater()
3131 self._PrintMessages()
3132 return self.clean
3133
David Rileye0684ad2017-04-05 00:02:59 -07003134 def Recently(self):
3135 recent_clean = self.recent_clean
3136 self.recent_clean = True
3137 return recent_clean
3138
3139 def _MarkUnclean(self):
3140 self.clean = False
3141 self.recent_clean = False
3142
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003143 def _RunLater(self):
3144 for q in ['_later_queue1', '_later_queue2']:
3145 if not self._RunQueue(q):
3146 return
3147
3148 def _RunQueue(self, queue):
3149 for m in getattr(self, queue):
3150 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003151 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003152 return False
3153 setattr(self, queue, [])
3154 return True
3155
3156 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003157 if self._messages or self._failures:
3158 if os.isatty(2):
3159 self.out.write(progress.CSI_ERASE_LINE)
3160 self.out.write('\r')
3161
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003162 for m in self._messages:
3163 m.Print(self)
3164 for m in self._failures:
3165 m.Print(self)
3166
3167 self._messages = []
3168 self._failures = []
3169
3170
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003171class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003172
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003173 """A special project housed under .repo.
3174 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003175
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003176 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003177 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003178 manifest=manifest,
3179 name=name,
3180 gitdir=gitdir,
3181 objdir=gitdir,
3182 worktree=worktree,
3183 remote=RemoteSpec('origin'),
3184 relpath='.repo/%s' % name,
3185 revisionExpr='refs/heads/master',
3186 revisionId=None,
3187 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003188
3189 def PreSync(self):
3190 if self.Exists:
3191 cb = self.CurrentBranch
3192 if cb:
3193 base = self.GetBranch(cb).merge
3194 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003195 self.revisionExpr = base
3196 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003197
Martin Kelly224a31a2017-07-10 14:46:25 -07003198 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003199 """ Prepare MetaProject for manifest branch switch
3200 """
3201
3202 # detach and delete manifest branch, allowing a new
3203 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003204 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003205 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003206 syncbuf.Finish()
3207
3208 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003209 ['update-ref', '-d', 'refs/heads/default'],
3210 capture_stdout=True,
3211 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003212
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003213 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003214 def LastFetch(self):
3215 try:
3216 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3217 return os.path.getmtime(fh)
3218 except OSError:
3219 return 0
3220
3221 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003222 def HasChanges(self):
3223 """Has the remote received new commits not yet checked out?
3224 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003225 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003226 return False
3227
David Pursehouse8a68ff92012-09-24 12:15:13 +09003228 all_refs = self.bare_ref.all
3229 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003230 head = self.work_git.GetHead()
3231 if head.startswith(R_HEADS):
3232 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003233 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003234 except KeyError:
3235 head = None
3236
3237 if revid == head:
3238 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003239 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003240 return True
3241 return False