blob: 8668bae955b3830b66729cdb8492c69702ec6054 [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
LaMont Jonesd82be3e2022-04-05 19:30:46 +000019import platform
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040029import urllib.parse
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
LaMont Jones0de4fc32022-04-21 17:18:35 +000032import fetch
Dave Borowitzb42b4742012-10-31 12:27:27 -070033from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070034from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
35 ID_RE
LaMont Jones55ee3042022-04-06 17:10:21 +000036from git_trace2_event_log import EventLog
Remy Bohmer16c13282020-09-10 10:38:04 +020037from error import GitError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040038from error import ManifestInvalidRevisionError, ManifestInvalidPathError
LaMont Jones409407a2022-04-05 21:21:56 +000039from error import NoManifestException, ManifestParseError
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070040import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040041import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040042from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070043
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050044from 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 -070045
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070046
George Engelbrecht9bc283e2020-04-02 12:36:09 -060047# Maximum sleep time allowed during retries.
48MAXIMUM_RETRY_SLEEP_SEC = 3600.0
49# +-10% random jitter is added to each Fetches retry sleep duration.
50RETRY_JITTER_PERCENT = 0.1
51
Mike Frysinger1d00a7e2021-12-21 00:40:31 -050052# Whether to use alternates.
53# TODO(vapier): Remove knob once behavior is verified.
54_ALTERNATES = os.environ.get('REPO_USE_ALTERNATES') == '1'
George Engelbrecht9bc283e2020-04-02 12:36:09 -060055
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070056def _lwrite(path, content):
57 lock = '%s.lock' % path
58
Remy Bohmer169b0212020-11-21 10:57:52 +010059 # Maintain Unix line endings on all OS's to match git behavior.
60 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070061 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062
63 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070064 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070065 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080066 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070067 raise
68
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070069
Shawn O. Pearce48244782009-04-16 08:25:57 -070070def _error(fmt, *args):
71 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070072 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070073
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070074
David Pursehousef33929d2015-08-24 14:39:14 +090075def _warn(fmt, *args):
76 msg = fmt % args
77 print('warn: %s' % msg, file=sys.stderr)
78
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070079
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070080def not_rev(r):
81 return '^' + r
82
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070083
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080084def sq(r):
85 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080086
David Pursehouse819827a2020-02-12 15:20:19 +090087
Jonathan Nieder93719792015-03-17 11:29:58 -070088_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070089
90
Jonathan Nieder93719792015-03-17 11:29:58 -070091def _ProjectHooks():
92 """List the hooks present in the 'hooks' directory.
93
94 These hooks are project hooks and are copied to the '.git/hooks' directory
95 of all subprojects.
96
97 This function caches the list of hooks (based on the contents of the
98 'repo/hooks' directory) on the first call.
99
100 Returns:
101 A list of absolute paths to all of the files in the hooks directory.
102 """
103 global _project_hook_list
104 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700105 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700106 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700107 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700108 return _project_hook_list
109
110
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700111class DownloadedChange(object):
112 _commit_cache = None
113
114 def __init__(self, project, base, change_id, ps_id, commit):
115 self.project = project
116 self.base = base
117 self.change_id = change_id
118 self.ps_id = ps_id
119 self.commit = commit
120
121 @property
122 def commits(self):
123 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700124 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
125 '--abbrev-commit',
126 '--pretty=oneline',
127 '--reverse',
128 '--date-order',
129 not_rev(self.base),
130 self.commit,
131 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700132 return self._commit_cache
133
134
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135class ReviewableBranch(object):
136 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400137 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700138
139 def __init__(self, project, branch, base):
140 self.project = project
141 self.branch = branch
142 self.base = base
143
144 @property
145 def name(self):
146 return self.branch.name
147
148 @property
149 def commits(self):
150 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400151 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
152 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
153 try:
154 self._commit_cache = self.project.bare_git.rev_list(*args)
155 except GitError:
156 # We weren't able to probe the commits for this branch. Was it tracking
157 # a branch that no longer exists? If so, return no commits. Otherwise,
158 # rethrow the error as we don't know what's going on.
159 if self.base_exists:
160 raise
161
162 self._commit_cache = []
163
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700164 return self._commit_cache
165
166 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800167 def unabbrev_commits(self):
168 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700169 for commit in self.project.bare_git.rev_list(not_rev(self.base),
170 R_HEADS + self.name,
171 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800172 r[commit[0:8]] = commit
173 return r
174
175 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700177 return self.project.bare_git.log('--pretty=format:%cd',
178 '-n', '1',
179 R_HEADS + self.name,
180 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
Mike Frysinger6da17752019-09-11 18:43:17 -0400182 @property
183 def base_exists(self):
184 """Whether the branch we're tracking exists.
185
186 Normally it should, but sometimes branches we track can get deleted.
187 """
188 if self._base_exists is None:
189 try:
190 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
191 # If we're still here, the base branch exists.
192 self._base_exists = True
193 except GitError:
194 # If we failed to verify, the base branch doesn't exist.
195 self._base_exists = False
196
197 return self._base_exists
198
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700199 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500200 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700201 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500202 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500203 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200204 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700205 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200206 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200207 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800208 validate_certs=True,
209 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500210 self.project.UploadForReview(branch=self.name,
211 people=people,
212 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700213 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500214 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500215 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200216 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700217 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200218 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200219 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800220 validate_certs=validate_certs,
221 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700223 def GetPublishedRefs(self):
224 refs = {}
225 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700226 self.branch.remote.SshReviewUrl(self.project.UserEmail),
227 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700228 for line in output.split('\n'):
229 try:
230 (sha, ref) = line.split()
231 refs[sha] = ref
232 except ValueError:
233 pass
234
235 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700236
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700237
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700239
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500241 super().__init__(config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100242 self.project = self.printer('header', attr='bold')
243 self.branch = self.printer('header', attr='bold')
244 self.nobranch = self.printer('nobranch', fg='red')
245 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246
Anthony King7bdac712014-07-16 12:56:40 +0100247 self.added = self.printer('added', fg='green')
248 self.changed = self.printer('changed', fg='red')
249 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700250
251
252class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700253
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700254 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500255 super().__init__(config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100256 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400257 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700258
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700259
Jack Neus6ea0cae2021-07-20 20:52:33 +0000260class Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700261
James W. Mills24c13082012-04-12 15:04:13 -0500262 def __init__(self, name, value, keep):
263 self.name = name
264 self.value = value
265 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700266
Jack Neus6ea0cae2021-07-20 20:52:33 +0000267 def __eq__(self, other):
268 if not isinstance(other, Annotation):
269 return False
270 return self.__dict__ == other.__dict__
271
272 def __lt__(self, other):
273 # This exists just so that lists of Annotation objects can be sorted, for
274 # use in comparisons.
275 if not isinstance(other, Annotation):
276 raise ValueError('comparison is not between two Annotation objects')
277 if self.name == other.name:
278 if self.value == other.value:
279 return self.keep < other.keep
280 return self.value < other.value
281 return self.name < other.name
282
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700283
Mike Frysingere6a202f2019-08-02 15:57:57 -0400284def _SafeExpandPath(base, subpath, skipfinal=False):
285 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700286
Mike Frysingere6a202f2019-08-02 15:57:57 -0400287 We make sure no intermediate symlinks are traversed, and that the final path
288 is not a special file (e.g. not a socket or fifo).
289
290 NB: We rely on a number of paths already being filtered out while parsing the
291 manifest. See the validation logic in manifest_xml.py for more details.
292 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500293 # Split up the path by its components. We can't use os.path.sep exclusively
294 # as some platforms (like Windows) will convert / to \ and that bypasses all
295 # our constructed logic here. Especially since manifest authors only use
296 # / in their paths.
297 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
298 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400299 if skipfinal:
300 # Whether the caller handles the final component itself.
301 finalpart = components.pop()
302
303 path = base
304 for part in components:
305 if part in {'.', '..'}:
306 raise ManifestInvalidPathError(
307 '%s: "%s" not allowed in paths' % (subpath, part))
308
309 path = os.path.join(path, part)
310 if platform_utils.islink(path):
311 raise ManifestInvalidPathError(
312 '%s: traversing symlinks not allow' % (path,))
313
314 if os.path.exists(path):
315 if not os.path.isfile(path) and not platform_utils.isdir(path):
316 raise ManifestInvalidPathError(
317 '%s: only regular files & directories allowed' % (path,))
318
319 if skipfinal:
320 path = os.path.join(path, finalpart)
321
322 return path
323
324
325class _CopyFile(object):
326 """Container for <copyfile> manifest element."""
327
328 def __init__(self, git_worktree, src, topdir, dest):
329 """Register a <copyfile> request.
330
331 Args:
332 git_worktree: Absolute path to the git project checkout.
333 src: Relative path under |git_worktree| of file to read.
334 topdir: Absolute path to the top of the repo client checkout.
335 dest: Relative path under |topdir| of file to write.
336 """
337 self.git_worktree = git_worktree
338 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700339 self.src = src
340 self.dest = dest
341
342 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400343 src = _SafeExpandPath(self.git_worktree, self.src)
344 dest = _SafeExpandPath(self.topdir, self.dest)
345
346 if platform_utils.isdir(src):
347 raise ManifestInvalidPathError(
348 '%s: copying from directory not supported' % (self.src,))
349 if platform_utils.isdir(dest):
350 raise ManifestInvalidPathError(
351 '%s: copying to directory not allowed' % (self.dest,))
352
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700353 # copy file if it does not exist or is out of date
354 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
355 try:
356 # remove existing file first, since it might be read-only
357 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800358 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400359 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200360 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700361 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200362 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700363 shutil.copy(src, dest)
364 # make the file read-only
365 mode = os.stat(dest)[stat.ST_MODE]
366 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
367 os.chmod(dest, mode)
368 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700369 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700370
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700371
Anthony King7bdac712014-07-16 12:56:40 +0100372class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400373 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700374
Mike Frysingere6a202f2019-08-02 15:57:57 -0400375 def __init__(self, git_worktree, src, topdir, dest):
376 """Register a <linkfile> request.
377
378 Args:
379 git_worktree: Absolute path to the git project checkout.
380 src: Target of symlink relative to path under |git_worktree|.
381 topdir: Absolute path to the top of the repo client checkout.
382 dest: Relative path under |topdir| of symlink to create.
383 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700384 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400385 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500386 self.src = src
387 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500388
Wink Saville4c426ef2015-06-03 08:05:17 -0700389 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500390 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700391 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500392 try:
393 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800394 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800395 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500396 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700397 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700398 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500399 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700400 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500401 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700402 _error('Cannot link file %s to %s', relSrc, absDest)
403
404 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400405 """Link the self.src & self.dest paths.
406
407 Handles wild cards on the src linking all of the files in the source in to
408 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700409 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500410 # Some people use src="." to create stable links to projects. Lets allow
411 # that but reject all other uses of "." to keep things simple.
412 if self.src == '.':
413 src = self.git_worktree
414 else:
415 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400416
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300417 if not glob.has_magic(src):
418 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400419 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
420 # dest & src are absolute paths at this point. Make sure the target of
421 # the symlink is relative in the context of the repo client checkout.
422 relpath = os.path.relpath(src, os.path.dirname(dest))
423 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700424 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400425 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300426 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400427 if os.path.exists(dest) and not platform_utils.isdir(dest):
428 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700429 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400430 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700431 # Create a releative path from source dir to destination dir
432 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400433 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700434
435 # Get the source file name
436 srcFile = os.path.basename(absSrcFile)
437
438 # Now form the final full paths to srcFile. They will be
439 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400440 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700441 relSrc = os.path.join(relSrcDir, srcFile)
442 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500443
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700444
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700445class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700446
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700447 def __init__(self,
448 name,
Anthony King7bdac712014-07-16 12:56:40 +0100449 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700450 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100451 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700452 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700453 orig_name=None,
454 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700455 self.name = name
456 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700457 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700458 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100459 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700460 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700461 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700462
Ian Kasprzak0286e312021-02-05 10:06:18 -0800463
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700464class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600465 # These objects can be shared between several working trees.
Mike Frysinger1d00a7e2021-12-21 00:40:31 -0500466 shareable_dirs = ['hooks', 'rr-cache']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700467
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700468 def __init__(self,
469 manifest,
470 name,
471 remote,
472 gitdir,
David James8d201162013-10-11 17:03:19 -0700473 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700474 worktree,
475 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700476 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800477 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100478 rebase=True,
479 groups=None,
480 sync_c=False,
481 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900482 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100483 clone_depth=None,
484 upstream=None,
485 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500486 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100487 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900488 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700489 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600490 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700491 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800492 """Init a Project object.
493
494 Args:
495 manifest: The XmlManifest object.
496 name: The `name` attribute of manifest.xml's project element.
497 remote: RemoteSpec object specifying its remote's properties.
498 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700499 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800500 worktree: Absolute path of git working tree.
501 relpath: Relative path of git working tree to repo's top directory.
502 revisionExpr: The `revision` attribute of manifest.xml's project element.
503 revisionId: git commit id for checking out.
504 rebase: The `rebase` attribute of manifest.xml's project element.
505 groups: The `groups` attribute of manifest.xml's project element.
506 sync_c: The `sync-c` attribute of manifest.xml's project element.
507 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900508 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800509 upstream: The `upstream` attribute of manifest.xml's project element.
510 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500511 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800512 is_derived: False if the project was explicitly defined in the manifest;
513 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400514 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900515 optimized_fetch: If True, when a project is set to a sha1 revision, only
516 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600517 retry_fetches: Retry remote fetches n times upon receiving transient error
518 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700519 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800520 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400521 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700522 self.name = name
523 self.remote = remote
Michael Kelly37c21c22020-06-13 02:10:40 -0700524 self.UpdatePaths(relpath, worktree, gitdir, objdir)
Michael Kelly2f3c3312020-07-21 19:40:38 -0700525 self.SetRevision(revisionExpr, revisionId=revisionId)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700526
Mike Pontillod3153822012-02-28 11:53:24 -0800527 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700528 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700529 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800530 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900531 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900532 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700533 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800534 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500535 # NB: Do not use this setting in __init__ to change behavior so that the
536 # manifest.git checkout can inspect & change it after instantiating. See
537 # the XmlManifest init code for more info.
538 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800539 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900540 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600541 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800542 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800543
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700544 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700545 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500546 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500547 self.annotations = []
Bryan Jacobsf609f912013-05-06 13:36:24 -0400548 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700549 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700550
Doug Anderson37282b42011-03-04 11:54:18 -0800551 # This will be filled in if a project is later identified to be the
552 # project containing repo hooks.
553 self.enabled_repo_hooks = []
554
LaMont Jonescc879a92021-11-18 22:40:18 +0000555 def RelPath(self, local=True):
556 """Return the path for the project relative to a manifest.
557
558 Args:
559 local: a boolean, if True, the path is relative to the local
560 (sub)manifest. If false, the path is relative to the
561 outermost manifest.
562 """
563 if local:
564 return self.relpath
565 return os.path.join(self.manifest.path_prefix, self.relpath)
566
Michael Kelly2f3c3312020-07-21 19:40:38 -0700567 def SetRevision(self, revisionExpr, revisionId=None):
568 """Set revisionId based on revision expression and id"""
569 self.revisionExpr = revisionExpr
570 if revisionId is None and revisionExpr and IsId(revisionExpr):
571 self.revisionId = self.revisionExpr
572 else:
573 self.revisionId = revisionId
574
Michael Kelly37c21c22020-06-13 02:10:40 -0700575 def UpdatePaths(self, relpath, worktree, gitdir, objdir):
576 """Update paths used by this project"""
577 self.gitdir = gitdir.replace('\\', '/')
578 self.objdir = objdir.replace('\\', '/')
579 if worktree:
580 self.worktree = os.path.normpath(worktree).replace('\\', '/')
581 else:
582 self.worktree = None
583 self.relpath = relpath
584
585 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
586 defaults=self.manifest.globalConfig)
587
588 if self.worktree:
589 self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
590 else:
591 self.work_git = None
592 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
593 self.bare_ref = GitRefs(self.gitdir)
594 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
595
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700596 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800597 def Derived(self):
598 return self.is_derived
599
600 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700601 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700602 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700603
604 @property
605 def CurrentBranch(self):
606 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400607
608 The branch name omits the 'refs/heads/' prefix.
609 None is returned if the project is on a detached HEAD, or if the work_git is
610 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700611 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400612 try:
613 b = self.work_git.GetHead()
614 except NoManifestException:
615 # If the local checkout is in a bad state, don't barf. Let the callers
616 # process this like the head is unreadable.
617 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700618 if b.startswith(R_HEADS):
619 return b[len(R_HEADS):]
620 return None
621
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700622 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500623 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
624 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
625 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200626
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700627 def IsDirty(self, consider_untracked=True):
628 """Is the working directory modified in some way?
629 """
630 self.work_git.update_index('-q',
631 '--unmerged',
632 '--ignore-missing',
633 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900634 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635 return True
636 if self.work_git.DiffZ('diff-files'):
637 return True
638 if consider_untracked and self.work_git.LsOthers():
639 return True
640 return False
641
642 _userident_name = None
643 _userident_email = None
644
645 @property
646 def UserName(self):
647 """Obtain the user's personal name.
648 """
649 if self._userident_name is None:
650 self._LoadUserIdentity()
651 return self._userident_name
652
653 @property
654 def UserEmail(self):
655 """Obtain the user's email address. This is very likely
656 to be their Gerrit login.
657 """
658 if self._userident_email is None:
659 self._LoadUserIdentity()
660 return self._userident_email
661
662 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900663 u = self.bare_git.var('GIT_COMMITTER_IDENT')
664 m = re.compile("^(.*) <([^>]*)> ").match(u)
665 if m:
666 self._userident_name = m.group(1)
667 self._userident_email = m.group(2)
668 else:
669 self._userident_name = ''
670 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700671
672 def GetRemote(self, name):
673 """Get the configuration for a single remote.
674 """
675 return self.config.GetRemote(name)
676
677 def GetBranch(self, name):
678 """Get the configuration for a single branch.
679 """
680 return self.config.GetBranch(name)
681
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700682 def GetBranches(self):
683 """Get all existing local branches.
684 """
685 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900686 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700687 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700688
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530689 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700690 if name.startswith(R_HEADS):
691 name = name[len(R_HEADS):]
692 b = self.GetBranch(name)
693 b.current = name == current
694 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900695 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700696 heads[name] = b
697
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530698 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700699 if name.startswith(R_PUB):
700 name = name[len(R_PUB):]
701 b = heads.get(name)
702 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900703 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700704
705 return heads
706
Colin Cross5acde752012-03-28 20:15:45 -0700707 def MatchesGroups(self, manifest_groups):
708 """Returns true if the manifest groups specified at init should cause
709 this project to be synced.
710 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700711 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700712
713 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700714 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700715 manifest_groups: "-group1,group2"
716 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500717
718 The special manifest group "default" will match any project that
719 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700720 """
LaMont Jones501733c2022-04-20 16:42:32 +0000721 default_groups = self.manifest.default_groups or ['default']
722 expanded_manifest_groups = manifest_groups or default_groups
Conley Owensbb1b5f52012-08-13 13:11:18 -0700723 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700724 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500725 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700726
Conley Owens971de8e2012-04-16 10:36:08 -0700727 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700728 for group in expanded_manifest_groups:
729 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700730 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700731 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700732 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700733
Conley Owens971de8e2012-04-16 10:36:08 -0700734 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700736# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700737 def UncommitedFiles(self, get_all=True):
738 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700739
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700740 Args:
741 get_all: a boolean, if True - get information about all different
742 uncommitted files. If False - return as soon as any kind of
743 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500744 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700745 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500746 self.work_git.update_index('-q',
747 '--unmerged',
748 '--ignore-missing',
749 '--refresh')
750 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700751 details.append("rebase in progress")
752 if not get_all:
753 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500754
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700755 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
756 if changes:
757 details.extend(changes)
758 if not get_all:
759 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500760
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700761 changes = self.work_git.DiffZ('diff-files').keys()
762 if changes:
763 details.extend(changes)
764 if not get_all:
765 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500766
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700767 changes = self.work_git.LsOthers()
768 if changes:
769 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500770
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700771 return details
772
773 def HasChanges(self):
774 """Returns true if there are uncommitted changes.
775 """
776 if self.UncommitedFiles(get_all=False):
777 return True
778 else:
779 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500780
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600781 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200783
784 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200785 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600786 quiet: If True then only print the project name. Do not print
787 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700789 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700790 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200791 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700792 print(file=output_redir)
793 print('project %s/' % self.relpath, file=output_redir)
794 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700795 return
796
797 self.work_git.update_index('-q',
798 '--unmerged',
799 '--ignore-missing',
800 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700801 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700802 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
803 df = self.work_git.DiffZ('diff-files')
804 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100805 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700806 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807
808 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700809 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200810 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700811 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600813 if quiet:
814 out.nl()
815 return 'DIRTY'
816
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700817 branch = self.CurrentBranch
818 if branch is None:
819 out.nobranch('(*** NO BRANCH ***)')
820 else:
821 out.branch('branch %s', branch)
822 out.nl()
823
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700824 if rb:
825 out.important('prior sync failed; rebase still in progress')
826 out.nl()
827
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828 paths = list()
829 paths.extend(di.keys())
830 paths.extend(df.keys())
831 paths.extend(do)
832
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530833 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900834 try:
835 i = di[p]
836 except KeyError:
837 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900839 try:
840 f = df[p]
841 except KeyError:
842 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200843
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900844 if i:
845 i_status = i.status.upper()
846 else:
847 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900849 if f:
850 f_status = f.status.lower()
851 else:
852 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853
854 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800855 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700856 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857 else:
858 line = ' %s%s\t%s' % (i_status, f_status, p)
859
860 if i and not f:
861 out.added('%s', line)
862 elif (i and f) or (not i and f):
863 out.changed('%s', line)
864 elif not i and not f:
865 out.untracked('%s', line)
866 else:
867 out.write('%s', line)
868 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200869
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700870 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500872 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700873 """Prints the status of the repository to stdout.
874 """
875 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500876 if output_redir:
877 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878 cmd = ['diff']
879 if out.is_on:
880 cmd.append('--color')
881 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300882 if absolute_paths:
883 cmd.append('--src-prefix=a/%s/' % self.relpath)
884 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700885 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400886 try:
887 p = GitCommand(self,
888 cmd,
889 capture_stdout=True,
890 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500891 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400892 except GitError as e:
893 out.nl()
894 out.project('project %s/' % self.relpath)
895 out.nl()
896 out.fail('%s', str(e))
897 out.nl()
898 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500899 if p.stdout:
900 out.nl()
901 out.project('project %s/' % self.relpath)
902 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500903 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400904 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700906# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900907 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700908 """Was the branch published (uploaded) for code review?
909 If so, returns the SHA-1 hash of the last published
910 state for the branch.
911 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700912 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900913 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700914 try:
915 return self.bare_git.rev_parse(key)
916 except GitError:
917 return None
918 else:
919 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900920 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700921 except KeyError:
922 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923
David Pursehouse8a68ff92012-09-24 12:15:13 +0900924 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925 """Prunes any stale published refs.
926 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900927 if all_refs is None:
928 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929 heads = set()
930 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530931 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932 if name.startswith(R_HEADS):
933 heads.add(name)
934 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900935 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530937 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700938 n = name[len(R_PUB):]
939 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900940 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700942 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943 """List any branches which can be uploaded for review.
944 """
945 heads = {}
946 pubed = {}
947
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530948 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900950 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900952 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953
954 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530955 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900956 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700958 if selected_branch and branch != selected_branch:
959 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700960
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800961 rb = self.GetUploadableBranch(branch)
962 if rb:
963 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964 return ready
965
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800966 def GetUploadableBranch(self, branch_name):
967 """Get a single uploadable branch, or None.
968 """
969 branch = self.GetBranch(branch_name)
970 base = branch.LocalMerge
971 if branch.LocalMerge:
972 rb = ReviewableBranch(self, branch, base)
973 if rb.commits:
974 return rb
975 return None
976
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700977 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100978 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500979 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700980 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500981 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500982 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200983 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700984 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200985 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200986 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800987 validate_certs=True,
988 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 """Uploads the named branch for code review.
990 """
991 if branch is None:
992 branch = self.CurrentBranch
993 if branch is None:
994 raise GitError('not currently on a branch')
995
996 branch = self.GetBranch(branch)
997 if not branch.LocalMerge:
998 raise GitError('branch %s does not track a remote' % branch.name)
999 if not branch.remote.review:
1000 raise GitError('remote %s has no review url' % branch.remote.name)
1001
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001002 # Basic validity check on label syntax.
1003 for label in labels:
1004 if not re.match(r'^.+[+-][0-9]+$', label):
1005 raise UploadError(
1006 f'invalid label syntax "{label}": labels use forms like '
1007 'CodeReview+1 or Verified-1')
1008
Bryan Jacobsf609f912013-05-06 13:36:24 -04001009 if dest_branch is None:
1010 dest_branch = self.dest_branch
1011 if dest_branch is None:
1012 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001013 if not dest_branch.startswith(R_HEADS):
1014 dest_branch = R_HEADS + dest_branch
1015
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001016 if not branch.remote.projectname:
1017 branch.remote.projectname = self.name
1018 branch.remote.Save()
1019
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001020 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001021 if url is None:
1022 raise UploadError('review not configured')
1023 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001024 if dryrun:
1025 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001026
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001027 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001028 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001029
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001030 for push_option in (push_options or []):
1031 cmd.append('-o')
1032 cmd.append(push_option)
1033
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001034 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001035
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001036 if dest_branch.startswith(R_HEADS):
1037 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001038
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001039 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001040 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001041 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001042 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001043 opts += ['t=%s' % p for p in hashtags]
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001044 # NB: No need to encode labels as they've been validated above.
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001045 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001046
David Pursehousef25a3702018-11-14 19:01:22 -08001047 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001048 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001049 if notify:
1050 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001051 if private:
1052 opts += ['private']
1053 if wip:
1054 opts += ['wip']
1055 if opts:
1056 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001057 cmd.append(ref_spec)
1058
Anthony King7bdac712014-07-16 12:56:40 +01001059 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001060 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061
Mike Frysingerd7f86832020-11-19 19:18:46 -05001062 if not dryrun:
1063 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1064 self.bare_git.UpdateRef(R_PUB + branch.name,
1065 R_HEADS + branch.name,
1066 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001068# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001069 def _ExtractArchive(self, tarpath, path=None):
1070 """Extract the given tar on its current location
1071
1072 Args:
1073 - tarpath: The path to the actual tar file
1074
1075 """
1076 try:
1077 with tarfile.open(tarpath, 'r') as tar:
1078 tar.extractall(path=path)
1079 return True
1080 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001081 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001082 return False
1083
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001084 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001085 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001086 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001087 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001088 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001089 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001090 force_sync=False,
1091 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001092 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001093 archive=False,
1094 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001095 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001096 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001097 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001098 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001099 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001100 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101 """Perform only the network IO portion of the sync process.
1102 Local working directory/branch state is not affected.
1103 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001104 if archive and not isinstance(self, MetaProject):
1105 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001106 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001107 return False
1108
1109 name = self.relpath.replace('\\', '/')
1110 name = name.replace('/', '_')
1111 tarpath = '%s.tar' % name
1112 topdir = self.manifest.topdir
1113
1114 try:
1115 self._FetchArchive(tarpath, cwd=topdir)
1116 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001117 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001118 return False
1119
1120 # From now on, we only need absolute tarpath
1121 tarpath = os.path.join(topdir, tarpath)
1122
1123 if not self._ExtractArchive(tarpath, path=topdir):
1124 return False
1125 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001126 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001127 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001128 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001129 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001130 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001131
1132 # If the shared object dir already exists, don't try to rebootstrap with a
1133 # clone bundle download. We should have the majority of objects already.
1134 if clone_bundle and os.path.exists(self.objdir):
1135 clone_bundle = False
1136
Raman Tennetif32f2432021-04-12 20:57:25 -07001137 if self.name in partial_clone_exclude:
1138 clone_bundle = True
1139 clone_filter = None
1140
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001141 if is_new is None:
1142 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001143 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001144 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001145 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001146 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001147 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001148
Mike Frysinger1d00a7e2021-12-21 00:40:31 -05001149 if _ALTERNATES or self.manifest.is_multimanifest:
1150 # If gitdir/objects is a symlink, migrate it from the old layout.
1151 gitdir_objects = os.path.join(self.gitdir, 'objects')
1152 if platform_utils.islink(gitdir_objects):
1153 platform_utils.remove(gitdir_objects, missing_ok=True)
1154 gitdir_alt = os.path.join(self.gitdir, 'objects/info/alternates')
1155 if not os.path.exists(gitdir_alt):
1156 os.makedirs(os.path.dirname(gitdir_alt), exist_ok=True)
1157 _lwrite(gitdir_alt, os.path.join(
1158 os.path.relpath(self.objdir, gitdir_objects), 'objects') + '\n')
1159
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001160 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001161 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001162 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001163 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001164 # This works for both absolute and relative alternate directories.
1165 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001166 except IOError:
1167 alt_dir = None
1168 else:
1169 alt_dir = None
1170
Mike Frysingere50b6a72020-02-19 01:45:48 -05001171 if (clone_bundle
1172 and alt_dir is None
1173 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001174 is_new = False
1175
Mike Frysinger73561142021-05-03 01:10:09 -04001176 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001177 if self.sync_c:
1178 current_branch_only = True
1179 elif not self.manifest._loaded:
1180 # Manifest cannot check defaults until it syncs.
1181 current_branch_only = False
1182 elif self.manifest.default.sync_c:
1183 current_branch_only = True
1184
Mike Frysingerd68ed632021-05-03 01:21:35 -04001185 if tags is None:
1186 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001187
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001188 if self.clone_depth:
1189 depth = self.clone_depth
1190 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001191 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001192
Mike Frysinger521d01b2020-02-17 01:51:49 -05001193 # See if we can skip the network fetch entirely.
1194 if not (optimized_fetch and
1195 (ID_RE.match(self.revisionExpr) and
1196 self._CheckForImmutableRevision())):
1197 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001198 initial=is_new,
1199 quiet=quiet, verbose=verbose, output_redir=output_redir,
1200 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001201 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001202 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001203 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001204 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001205 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001206
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001207 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001208 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001209 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001210 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001211 if os.path.exists(alternates_file):
1212 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001213 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1214 merge_output=bool(output_redir))
1215 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001216 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001217 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001218 return False
1219 platform_utils.remove(alternates_file)
1220
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001221 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001222 self._InitMRef()
1223 else:
1224 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001225 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1226 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001228
1229 def PostRepoUpgrade(self):
1230 self._InitHooks()
1231
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001232 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001233 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001234 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001235 for copyfile in self.copyfiles:
1236 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001237 for linkfile in self.linkfiles:
1238 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001239
Julien Camperguedd654222014-01-09 16:21:37 +01001240 def GetCommitRevisionId(self):
1241 """Get revisionId of a commit.
1242
1243 Use this method instead of GetRevisionId to get the id of the commit rather
1244 than the id of the current git object (for example, a tag)
1245
1246 """
1247 if not self.revisionExpr.startswith(R_TAGS):
1248 return self.GetRevisionId(self._allrefs)
1249
1250 try:
1251 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1252 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001253 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1254 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001255
David Pursehouse8a68ff92012-09-24 12:15:13 +09001256 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001257 if self.revisionId:
1258 return self.revisionId
1259
1260 rem = self.GetRemote(self.remote.name)
1261 rev = rem.ToLocal(self.revisionExpr)
1262
David Pursehouse8a68ff92012-09-24 12:15:13 +09001263 if all_refs is not None and rev in all_refs:
1264 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001265
1266 try:
1267 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1268 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001269 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1270 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001271
Raman Tenneti6a872c92021-01-14 19:17:50 -08001272 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001273 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001274 self.upstream = self.revisionExpr
1275
Raman Tenneti6a872c92021-01-14 19:17:50 -08001276 self.revisionId = revisionId
1277
Martin Kellye4e94d22017-03-21 16:05:12 -07001278 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279 """Perform only the local IO portion of the sync process.
1280 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001281 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001282 if not os.path.exists(self.gitdir):
1283 syncbuf.fail(self,
1284 'Cannot checkout %s due to missing network sync; Run '
1285 '`repo sync -n %s` first.' %
1286 (self.name, self.name))
1287 return
1288
Martin Kellye4e94d22017-03-21 16:05:12 -07001289 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001290 all_refs = self.bare_ref.all
1291 self.CleanPublishedCache(all_refs)
1292 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001293
Mike Frysinger0458faa2021-03-10 23:35:44 -05001294 # Special case the root of the repo client checkout. Make sure it doesn't
1295 # contain files being checked out to dirs we don't allow.
1296 if self.relpath == '.':
1297 PROTECTED_PATHS = {'.repo'}
1298 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1299 bad_paths = paths & PROTECTED_PATHS
1300 if bad_paths:
1301 syncbuf.fail(self,
1302 'Refusing to checkout project that writes to protected '
1303 'paths: %s' % (', '.join(bad_paths),))
1304 return
1305
David Pursehouse1d947b32012-10-25 12:23:11 +09001306 def _doff():
1307 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001308 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001309
Martin Kellye4e94d22017-03-21 16:05:12 -07001310 def _dosubmodules():
1311 self._SyncSubmodules(quiet=True)
1312
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001313 head = self.work_git.GetHead()
1314 if head.startswith(R_HEADS):
1315 branch = head[len(R_HEADS):]
1316 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001317 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001318 except KeyError:
1319 head = None
1320 else:
1321 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001323 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 # Currently on a detached HEAD. The user is assumed to
1325 # not have any local modifications worth worrying about.
1326 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001327 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001328 syncbuf.fail(self, _PriorSyncFailedError())
1329 return
1330
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001331 if head == revid:
1332 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001333 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001334 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001335 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001336 # The copy/linkfile config may have changed.
1337 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001338 return
1339 else:
1340 lost = self._revlist(not_rev(revid), HEAD)
1341 if lost:
1342 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001343
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001345 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001346 if submodules:
1347 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001348 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001349 syncbuf.fail(self, e)
1350 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001351 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001352 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001354 if head == revid:
1355 # No changes; don't do anything further.
1356 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001357 # The copy/linkfile config may have changed.
1358 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001359 return
1360
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001361 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001362
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001363 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001365 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001366 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001367 syncbuf.info(self,
1368 "leaving %s; does not track upstream",
1369 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001370 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001371 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001372 if submodules:
1373 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001374 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001375 syncbuf.fail(self, e)
1376 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001377 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001378 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001379
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001380 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001381
1382 # See if we can perform a fast forward merge. This can happen if our
1383 # branch isn't in the exact same state as we last published.
1384 try:
1385 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1386 # Skip the published logic.
1387 pub = False
1388 except GitError:
1389 pub = self.WasPublished(branch.name, all_refs)
1390
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001392 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393 if not_merged:
1394 if upstream_gain:
1395 # The user has published this branch and some of those
1396 # commits are not yet merged upstream. We do not want
1397 # to rewrite the published commits so we punt.
1398 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001399 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001400 "branch %s is published (but not merged) and is now "
1401 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001402 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001403 elif pub == head:
1404 # All published commits are merged, and thus we are a
1405 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001406 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001407 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001408 if submodules:
1409 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001410 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001411
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001412 # Examine the local commits not in the remote. Find the
1413 # last one attributed to this user, if any.
1414 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001415 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001416 last_mine = None
1417 cnt_mine = 0
1418 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001419 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001420 if committer_email == self.UserEmail:
1421 last_mine = commit_id
1422 cnt_mine += 1
1423
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001424 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001425 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001426
1427 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001428 syncbuf.fail(self, _DirtyError())
1429 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001430
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001431 # If the upstream switched on us, warn the user.
1432 #
1433 if branch.merge != self.revisionExpr:
1434 if branch.merge and self.revisionExpr:
1435 syncbuf.info(self,
1436 'manifest switched %s...%s',
1437 branch.merge,
1438 self.revisionExpr)
1439 elif branch.merge:
1440 syncbuf.info(self,
1441 'manifest no longer tracks %s',
1442 branch.merge)
1443
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001444 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001445 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001446 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001447 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001448 syncbuf.info(self,
1449 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001450 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001451
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001452 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001453 if not ID_RE.match(self.revisionExpr):
1454 # in case of manifest sync the revisionExpr might be a SHA1
1455 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001456 if not branch.merge.startswith('refs/'):
1457 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001458 branch.Save()
1459
Mike Pontillod3153822012-02-28 11:53:24 -08001460 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001461 def _docopyandlink():
1462 self._CopyAndLinkFiles()
1463
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001464 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001465 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001466 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001467 if submodules:
1468 syncbuf.later2(self, _dosubmodules)
1469 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001470 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001471 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001472 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001473 if submodules:
1474 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001475 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001476 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001477 syncbuf.fail(self, e)
1478 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001479 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001480 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001481 if submodules:
1482 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001483
Mike Frysingere6a202f2019-08-02 15:57:57 -04001484 def AddCopyFile(self, src, dest, topdir):
1485 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001486
Mike Frysingere6a202f2019-08-02 15:57:57 -04001487 No filesystem changes occur here. Actual copying happens later on.
1488
1489 Paths should have basic validation run on them before being queued.
1490 Further checking will be handled when the actual copy happens.
1491 """
1492 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1493
1494 def AddLinkFile(self, src, dest, topdir):
1495 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1496
1497 No filesystem changes occur here. Actual linking happens later on.
1498
1499 Paths should have basic validation run on them before being queued.
1500 Further checking will be handled when the actual link happens.
1501 """
1502 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001503
James W. Mills24c13082012-04-12 15:04:13 -05001504 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001505 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001506
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001507 def DownloadPatchSet(self, change_id, patch_id):
1508 """Download a single patch set of a single change to FETCH_HEAD.
1509 """
1510 remote = self.GetRemote(self.remote.name)
1511
1512 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001513 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001514 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001515 if GitCommand(self, cmd, bare=True).Wait() != 0:
1516 return None
1517 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001518 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001519 change_id,
1520 patch_id,
1521 self.bare_git.rev_parse('FETCH_HEAD'))
1522
Mike Frysingerc0d18662020-02-19 19:19:18 -05001523 def DeleteWorktree(self, quiet=False, force=False):
1524 """Delete the source checkout and any other housekeeping tasks.
1525
1526 This currently leaves behind the internal .repo/ cache state. This helps
1527 when switching branches or manifest changes get reverted as we don't have
1528 to redownload all the git objects. But we should do some GC at some point.
1529
1530 Args:
1531 quiet: Whether to hide normal messages.
1532 force: Always delete tree even if dirty.
1533
1534 Returns:
1535 True if the worktree was completely cleaned out.
1536 """
1537 if self.IsDirty():
1538 if force:
1539 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1540 (self.relpath,), file=sys.stderr)
1541 else:
1542 print('error: %s: Cannot remove project: uncommitted changes are '
1543 'present.\n' % (self.relpath,), file=sys.stderr)
1544 return False
1545
1546 if not quiet:
1547 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1548
1549 # Unlock and delink from the main worktree. We don't use git's worktree
1550 # remove because it will recursively delete projects -- we handle that
1551 # ourselves below. https://crbug.com/git/48
1552 if self.use_git_worktrees:
1553 needle = platform_utils.realpath(self.gitdir)
1554 # Find the git worktree commondir under .repo/worktrees/.
1555 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1556 assert output.startswith('worktree '), output
1557 commondir = output[9:]
1558 # Walk each of the git worktrees to see where they point.
1559 configs = os.path.join(commondir, 'worktrees')
1560 for name in os.listdir(configs):
1561 gitdir = os.path.join(configs, name, 'gitdir')
1562 with open(gitdir) as fp:
1563 relpath = fp.read().strip()
1564 # Resolve the checkout path and see if it matches this project.
1565 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1566 if fullpath == needle:
1567 platform_utils.rmtree(os.path.join(configs, name))
1568
1569 # Delete the .git directory first, so we're less likely to have a partially
1570 # working git repository around. There shouldn't be any git projects here,
1571 # so rmtree works.
1572
1573 # Try to remove plain files first in case of git worktrees. If this fails
1574 # for any reason, we'll fall back to rmtree, and that'll display errors if
1575 # it can't remove things either.
1576 try:
1577 platform_utils.remove(self.gitdir)
1578 except OSError:
1579 pass
1580 try:
1581 platform_utils.rmtree(self.gitdir)
1582 except OSError as e:
1583 if e.errno != errno.ENOENT:
1584 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1585 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1586 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1587 return False
1588
1589 # Delete everything under the worktree, except for directories that contain
1590 # another git project.
1591 dirs_to_remove = []
1592 failed = False
1593 for root, dirs, files in platform_utils.walk(self.worktree):
1594 for f in files:
1595 path = os.path.join(root, f)
1596 try:
1597 platform_utils.remove(path)
1598 except OSError as e:
1599 if e.errno != errno.ENOENT:
1600 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1601 failed = True
1602 dirs[:] = [d for d in dirs
1603 if not os.path.lexists(os.path.join(root, d, '.git'))]
1604 dirs_to_remove += [os.path.join(root, d) for d in dirs
1605 if os.path.join(root, d) not in dirs_to_remove]
1606 for d in reversed(dirs_to_remove):
1607 if platform_utils.islink(d):
1608 try:
1609 platform_utils.remove(d)
1610 except OSError as e:
1611 if e.errno != errno.ENOENT:
1612 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1613 failed = True
1614 elif not platform_utils.listdir(d):
1615 try:
1616 platform_utils.rmdir(d)
1617 except OSError as e:
1618 if e.errno != errno.ENOENT:
1619 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1620 failed = True
1621 if failed:
1622 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1623 file=sys.stderr)
1624 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1625 return False
1626
1627 # Try deleting parent dirs if they are empty.
1628 path = self.worktree
1629 while path != self.manifest.topdir:
1630 try:
1631 platform_utils.rmdir(path)
1632 except OSError as e:
1633 if e.errno != errno.ENOENT:
1634 break
1635 path = os.path.dirname(path)
1636
1637 return True
1638
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001639# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001640 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001641 """Create a new branch off the manifest's revision.
1642 """
Simran Basib9a1b732015-08-20 12:19:28 -07001643 if not branch_merge:
1644 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001645 head = self.work_git.GetHead()
1646 if head == (R_HEADS + name):
1647 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001648
David Pursehouse8a68ff92012-09-24 12:15:13 +09001649 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001650 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001651 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001652 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001653 capture_stdout=True,
1654 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001655
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001656 branch = self.GetBranch(name)
1657 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001658 branch.merge = branch_merge
1659 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1660 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001661
1662 if revision is None:
1663 revid = self.GetRevisionId(all_refs)
1664 else:
1665 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001666
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001667 if head.startswith(R_HEADS):
1668 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001669 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001670 except KeyError:
1671 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001672 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001673 ref = R_HEADS + name
1674 self.work_git.update_ref(ref, revid)
1675 self.work_git.symbolic_ref(HEAD, ref)
1676 branch.Save()
1677 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001678
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001679 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001680 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001681 capture_stdout=True,
1682 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001683 branch.Save()
1684 return True
1685 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001686
Wink Saville02d79452009-04-10 13:01:24 -07001687 def CheckoutBranch(self, name):
1688 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001689
1690 Args:
1691 name: The name of the branch to checkout.
1692
1693 Returns:
1694 True if the checkout succeeded; False if it didn't; None if the branch
1695 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001696 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001697 rev = R_HEADS + name
1698 head = self.work_git.GetHead()
1699 if head == rev:
1700 # Already on the branch
1701 #
1702 return True
Wink Saville02d79452009-04-10 13:01:24 -07001703
David Pursehouse8a68ff92012-09-24 12:15:13 +09001704 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001705 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001706 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001707 except KeyError:
1708 # Branch does not exist in this project
1709 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001710 return None
Wink Saville02d79452009-04-10 13:01:24 -07001711
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001712 if head.startswith(R_HEADS):
1713 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001714 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001715 except KeyError:
1716 head = None
1717
1718 if head == revid:
1719 # Same revision; just update HEAD to point to the new
1720 # target branch, but otherwise take no other action.
1721 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001722 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1723 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001724 return True
1725
1726 return GitCommand(self,
1727 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001728 capture_stdout=True,
1729 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001730
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001731 def AbandonBranch(self, name):
1732 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001733
1734 Args:
1735 name: The name of the branch to abandon.
1736
1737 Returns:
1738 True if the abandon succeeded; False if it didn't; None if the branch
1739 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001740 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001741 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001742 all_refs = self.bare_ref.all
1743 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001744 # Doesn't exist
1745 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001746
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001747 head = self.work_git.GetHead()
1748 if head == rev:
1749 # We can't destroy the branch while we are sitting
1750 # on it. Switch to a detached HEAD.
1751 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001752 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001753
David Pursehouse8a68ff92012-09-24 12:15:13 +09001754 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001755 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001756 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001757 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001758 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001759
1760 return GitCommand(self,
1761 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001762 capture_stdout=True,
1763 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001764
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001765 def PruneHeads(self):
1766 """Prune any topic branches already merged into upstream.
1767 """
1768 cb = self.CurrentBranch
1769 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001770 left = self._allrefs
1771 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001772 if name.startswith(R_HEADS):
1773 name = name[len(R_HEADS):]
1774 if cb is None or name != cb:
1775 kill.append(name)
1776
Mike Frysingera3794e92021-03-11 23:24:01 -05001777 # Minor optimization: If there's nothing to prune, then don't try to read
1778 # any project state.
1779 if not kill and not cb:
1780 return []
1781
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001782 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001783 if cb is not None \
1784 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001785 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001786 self.work_git.DetachHead(HEAD)
1787 kill.append(cb)
1788
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001789 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001790 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001791
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001792 try:
1793 self.bare_git.DetachHead(rev)
1794
1795 b = ['branch', '-d']
1796 b.extend(kill)
1797 b = GitCommand(self, b, bare=True,
1798 capture_stdout=True,
1799 capture_stderr=True)
1800 b.Wait()
1801 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001802 if ID_RE.match(old):
1803 self.bare_git.DetachHead(old)
1804 else:
1805 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001806 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001807
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001808 for branch in kill:
1809 if (R_HEADS + branch) not in left:
1810 self.CleanPublishedCache()
1811 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001812
1813 if cb and cb not in kill:
1814 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001815 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001816
1817 kept = []
1818 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001819 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001820 branch = self.GetBranch(branch)
1821 base = branch.LocalMerge
1822 if not base:
1823 base = rev
1824 kept.append(ReviewableBranch(self, branch, base))
1825 return kept
1826
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001827# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001828 def GetRegisteredSubprojects(self):
1829 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001830
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001831 def rec(subprojects):
1832 if not subprojects:
1833 return
1834 result.extend(subprojects)
1835 for p in subprojects:
1836 rec(p.subprojects)
1837 rec(self.subprojects)
1838 return result
1839
1840 def _GetSubmodules(self):
1841 # Unfortunately we cannot call `git submodule status --recursive` here
1842 # because the working tree might not exist yet, and it cannot be used
1843 # without a working tree in its current implementation.
1844
1845 def get_submodules(gitdir, rev):
1846 # Parse .gitmodules for submodule sub_paths and sub_urls
1847 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1848 if not sub_paths:
1849 return []
1850 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1851 # revision of submodule repository
1852 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1853 submodules = []
1854 for sub_path, sub_url in zip(sub_paths, sub_urls):
1855 try:
1856 sub_rev = sub_revs[sub_path]
1857 except KeyError:
1858 # Ignore non-exist submodules
1859 continue
1860 submodules.append((sub_rev, sub_path, sub_url))
1861 return submodules
1862
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001863 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1864 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001865
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001866 def parse_gitmodules(gitdir, rev):
1867 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1868 try:
Anthony King7bdac712014-07-16 12:56:40 +01001869 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1870 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001871 except GitError:
1872 return [], []
1873 if p.Wait() != 0:
1874 return [], []
1875
1876 gitmodules_lines = []
1877 fd, temp_gitmodules_path = tempfile.mkstemp()
1878 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001879 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001880 os.close(fd)
1881 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001882 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1883 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001884 if p.Wait() != 0:
1885 return [], []
1886 gitmodules_lines = p.stdout.split('\n')
1887 except GitError:
1888 return [], []
1889 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001890 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001891
1892 names = set()
1893 paths = {}
1894 urls = {}
1895 for line in gitmodules_lines:
1896 if not line:
1897 continue
1898 m = re_path.match(line)
1899 if m:
1900 names.add(m.group(1))
1901 paths[m.group(1)] = m.group(2)
1902 continue
1903 m = re_url.match(line)
1904 if m:
1905 names.add(m.group(1))
1906 urls[m.group(1)] = m.group(2)
1907 continue
1908 names = sorted(names)
1909 return ([paths.get(name, '') for name in names],
1910 [urls.get(name, '') for name in names])
1911
1912 def git_ls_tree(gitdir, rev, paths):
1913 cmd = ['ls-tree', rev, '--']
1914 cmd.extend(paths)
1915 try:
Anthony King7bdac712014-07-16 12:56:40 +01001916 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1917 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001918 except GitError:
1919 return []
1920 if p.Wait() != 0:
1921 return []
1922 objects = {}
1923 for line in p.stdout.split('\n'):
1924 if not line.strip():
1925 continue
1926 object_rev, object_path = line.split()[2:4]
1927 objects[object_path] = object_rev
1928 return objects
1929
1930 try:
1931 rev = self.GetRevisionId()
1932 except GitError:
1933 return []
1934 return get_submodules(self.gitdir, rev)
1935
1936 def GetDerivedSubprojects(self):
1937 result = []
1938 if not self.Exists:
1939 # If git repo does not exist yet, querying its submodules will
1940 # mess up its states; so return here.
1941 return result
1942 for rev, path, url in self._GetSubmodules():
1943 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001944 relpath, worktree, gitdir, objdir = \
1945 self.manifest.GetSubprojectPaths(self, name, path)
1946 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001947 if project:
1948 result.extend(project.GetDerivedSubprojects())
1949 continue
David James8d201162013-10-11 17:03:19 -07001950
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001951 if url.startswith('..'):
1952 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001953 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001954 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001955 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001956 review=self.remote.review,
1957 revision=self.remote.revision)
1958 subproject = Project(manifest=self.manifest,
1959 name=name,
1960 remote=remote,
1961 gitdir=gitdir,
1962 objdir=objdir,
1963 worktree=worktree,
1964 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001965 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001966 revisionId=rev,
1967 rebase=self.rebase,
1968 groups=self.groups,
1969 sync_c=self.sync_c,
1970 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001971 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001972 parent=self,
1973 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001974 result.append(subproject)
1975 result.extend(subproject.GetDerivedSubprojects())
1976 return result
1977
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001978# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001979 def EnableRepositoryExtension(self, key, value='true', version=1):
1980 """Enable git repository extension |key| with |value|.
1981
1982 Args:
1983 key: The extension to enabled. Omit the "extensions." prefix.
1984 value: The value to use for the extension.
1985 version: The minimum git repository version needed.
1986 """
1987 # Make sure the git repo version is new enough already.
1988 found_version = self.config.GetInt('core.repositoryFormatVersion')
1989 if found_version is None:
1990 found_version = 0
1991 if found_version < version:
1992 self.config.SetString('core.repositoryFormatVersion', str(version))
1993
1994 # Enable the extension!
1995 self.config.SetString('extensions.%s' % (key,), value)
1996
Mike Frysinger50a81de2020-09-06 15:51:21 -04001997 def ResolveRemoteHead(self, name=None):
1998 """Find out what the default branch (HEAD) points to.
1999
2000 Normally this points to refs/heads/master, but projects are moving to main.
2001 Support whatever the server uses rather than hardcoding "master" ourselves.
2002 """
2003 if name is None:
2004 name = self.remote.name
2005
2006 # The output will look like (NB: tabs are separators):
2007 # ref: refs/heads/master HEAD
2008 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2009 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2010
2011 for line in output.splitlines():
2012 lhs, rhs = line.split('\t', 1)
2013 if rhs == 'HEAD' and lhs.startswith('ref:'):
2014 return lhs[4:].strip()
2015
2016 return None
2017
Zac Livingstone4332262017-06-16 08:56:09 -06002018 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002019 try:
2020 # if revision (sha or tag) is not present then following function
2021 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002022 self.bare_git.rev_list('-1', '--missing=allow-any',
2023 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00002024 if self.upstream:
2025 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
2026 self.bare_git.rev_list('-1', '--missing=allow-any',
2027 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002028 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002029 return True
2030 except GitError:
2031 # There is no such persistent revision. We have to fetch it.
2032 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002033
Julien Campergue335f5ef2013-10-16 11:02:35 +02002034 def _FetchArchive(self, tarpath, cwd=None):
2035 cmd = ['archive', '-v', '-o', tarpath]
2036 cmd.append('--remote=%s' % self.remote.url)
2037 cmd.append('--prefix=%s/' % self.relpath)
2038 cmd.append(self.revisionExpr)
2039
2040 command = GitCommand(self, cmd, cwd=cwd,
2041 capture_stdout=True,
2042 capture_stderr=True)
2043
2044 if command.Wait() != 0:
2045 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2046
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002047 def _RemoteFetch(self, name=None,
2048 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002049 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002050 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002051 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002052 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002053 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002054 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002055 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002056 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002057 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002058 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002059 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002060 clone_filter=None,
2061 retry_fetches=2,
2062 retry_sleep_initial_sec=4.0,
2063 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002064 is_sha1 = False
2065 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002066 # The depth should not be used when fetching to a mirror because
2067 # it will result in a shallow repository that cannot be cloned or
2068 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002069 # The repo project should also never be synced with partial depth.
2070 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2071 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002072
Shawn Pearce69e04d82014-01-29 12:48:54 -08002073 if depth:
2074 current_branch_only = True
2075
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002076 if ID_RE.match(self.revisionExpr) is not None:
2077 is_sha1 = True
2078
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002079 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002080 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002081 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002082 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002083 elif self.upstream and self.upstream.startswith(R_TAGS):
2084 # This is a tag and its commit id should never change.
2085 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002086
2087 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002088 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002089 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002090 print('Skipped fetching project %s (already have persistent ref)'
2091 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002092 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002093 if is_sha1 and not depth:
2094 # When syncing a specific commit and --depth is not set:
2095 # * if upstream is explicitly specified and is not a sha1, fetch only
2096 # upstream as users expect only upstream to be fetch.
2097 # Note: The commit might not be in upstream in which case the sync
2098 # will fail.
2099 # * otherwise, fetch all branches to make sure we end up with the
2100 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002101 if self.upstream:
2102 current_branch_only = not ID_RE.match(self.upstream)
2103 else:
2104 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002105
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002106 if not name:
2107 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002108
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002109 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002110 if not remote.PreConnectFetch(ssh_proxy):
2111 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002112
Shawn O. Pearce88443382010-10-08 10:02:09 +02002113 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002114 if alt_dir and 'objects' == os.path.basename(alt_dir):
2115 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002116 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002117
David Pursehouse8a68ff92012-09-24 12:15:13 +09002118 all_refs = self.bare_ref.all
2119 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002120 tmp = set()
2121
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302122 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002123 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002124 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002125 all_refs[r] = ref_id
2126 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002127 continue
2128
David Pursehouse8a68ff92012-09-24 12:15:13 +09002129 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002130 continue
2131
David Pursehouse8a68ff92012-09-24 12:15:13 +09002132 r = 'refs/_alt/%s' % ref_id
2133 all_refs[r] = ref_id
2134 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002135 tmp.add(r)
2136
heping3d7bbc92017-04-12 19:51:47 +08002137 tmp_packed_lines = []
2138 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002139
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302140 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002141 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002142 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002143 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002144 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002145
heping3d7bbc92017-04-12 19:51:47 +08002146 tmp_packed = ''.join(tmp_packed_lines)
2147 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002148 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002149 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002150 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002151
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002152 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002153
Xin Li745be2e2019-06-03 11:24:30 -07002154 if clone_filter:
2155 git_require((2, 19, 0), fail=True, msg='partial clones')
2156 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002157 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002158
Conley Owensf97e8382015-01-21 11:12:46 -08002159 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002160 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002161 else:
2162 # If this repo has shallow objects, then we don't know which refs have
2163 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2164 # do this with projects that don't have shallow objects, since it is less
2165 # efficient.
2166 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2167 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002168
Mike Frysinger4847e052020-02-22 00:07:35 -05002169 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002170 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002171 if not quiet and sys.stdout.isatty():
2172 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002173 if not self.worktree:
2174 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002175 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002176
Mike Frysingere57f1142019-03-18 21:27:54 -04002177 if force_sync:
2178 cmd.append('--force')
2179
David Pursehouse74cfd272015-10-14 10:50:15 +09002180 if prune:
2181 cmd.append('--prune')
2182
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002183 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002184
Kuang-che Wu6856f982019-11-25 12:37:55 +08002185 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002186 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002187 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002188 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002189 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002190 spec.append('tag')
2191 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002192
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302193 if self.manifest.IsMirror and not current_branch_only:
2194 branch = None
2195 else:
2196 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002197 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002198 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002199 # Shallow checkout of a specific commit, fetch from that commit and not
2200 # the heads only as the commit might be deeper in the history.
2201 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002202 if self.upstream:
2203 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002204 else:
2205 if is_sha1:
2206 branch = self.upstream
2207 if branch is not None and branch.strip():
2208 if not branch.startswith('refs/'):
2209 branch = R_HEADS + branch
2210 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2211
2212 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2213 # whole repo.
2214 if self.manifest.IsMirror and not spec:
2215 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2216
2217 # If using depth then we should not get all the tags since they may
2218 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002219 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002220 cmd.append('--no-tags')
2221 else:
2222 cmd.append('--tags')
2223 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2224
Conley Owens80b87fe2014-05-09 17:13:44 -07002225 cmd.extend(spec)
2226
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002227 # At least one retry minimum due to git remote prune.
2228 retry_fetches = max(retry_fetches, 2)
2229 retry_cur_sleep = retry_sleep_initial_sec
2230 ok = prune_tried = False
2231 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002232 gitcmd = GitCommand(
2233 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2234 ssh_proxy=ssh_proxy,
2235 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002236 if gitcmd.stdout and not quiet and output_redir:
2237 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002238 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002239 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002240 ok = True
2241 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002242
2243 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002244 elif (gitcmd.stdout and
2245 'error:' in gitcmd.stdout and
2246 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002247 # Fallthru to sleep+retry logic at the bottom.
2248 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002249
Mike Frysinger6823bc22021-04-15 02:06:28 -04002250 # Try to prune remote branches once in case there are conflicts.
2251 # For example, if the remote had refs/heads/upstream, but deleted that and
2252 # now has refs/heads/upstream/foo.
2253 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002254 'error:' in gitcmd.stdout and
2255 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002256 not prune_tried):
2257 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002258 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002259 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002260 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002261 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002262 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002263 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002264 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002265 continue
Brian Harring14a66742012-09-28 20:21:57 -07002266 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002267 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2268 # in sha1 mode, we just tried sync'ing from the upstream field; it
2269 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002270 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002271 elif ret < 0:
2272 # Git died with a signal, exit immediately
2273 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002274
2275 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002276 if not verbose and gitcmd.stdout:
2277 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002278 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002279 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2280 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002281 time.sleep(retry_cur_sleep)
2282 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2283 MAXIMUM_RETRY_SLEEP_SEC)
2284 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2285 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002286
2287 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002288 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002289 if old_packed != '':
2290 _lwrite(packed_refs, old_packed)
2291 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002292 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002293 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002294
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002295 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002296 # We just synced the upstream given branch; verify we
2297 # got what we wanted, else trigger a second run of all
2298 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002299 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002300 # Sync the current branch only with depth set to None.
2301 # We always pass depth=None down to avoid infinite recursion.
2302 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002303 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002304 current_branch_only=current_branch_only and depth,
2305 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002306 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002307
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002308 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002309
Mike Frysingere50b6a72020-02-19 01:45:48 -05002310 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002311 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002312 return False
2313
2314 remote = self.GetRemote(self.remote.name)
2315 bundle_url = remote.url + '/clone.bundle'
2316 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002317 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2318 'persistent-http',
2319 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002320 return False
2321
2322 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2323 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2324
2325 exist_dst = os.path.exists(bundle_dst)
2326 exist_tmp = os.path.exists(bundle_tmp)
2327
2328 if not initial and not exist_dst and not exist_tmp:
2329 return False
2330
2331 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002332 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2333 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002334 if not exist_dst:
2335 return False
2336
2337 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002338 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002339 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002340 if not quiet and sys.stdout.isatty():
2341 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002342 if not self.worktree:
2343 cmd.append('--update-head-ok')
2344 cmd.append(bundle_dst)
2345 for f in remote.fetch:
2346 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002347 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002348
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002349 ok = GitCommand(
2350 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002351 platform_utils.remove(bundle_dst, missing_ok=True)
2352 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002353 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002354
Mike Frysingere50b6a72020-02-19 01:45:48 -05002355 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002356 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002357
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002358 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002359 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002360 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002361 if os.path.exists(tmpPath):
2362 size = os.stat(tmpPath).st_size
2363 if size >= 1024:
2364 cmd += ['--continue-at', '%d' % (size,)]
2365 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002366 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002367 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002368 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002369 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002370 if proxy:
2371 cmd += ['--proxy', proxy]
2372 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2373 cmd += ['--proxy', os.environ['http_proxy']]
2374 if srcUrl.startswith('persistent-https'):
2375 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2376 elif srcUrl.startswith('persistent-http'):
2377 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002378 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002379
Dave Borowitz137d0132015-01-02 11:12:54 -08002380 if IsTrace():
2381 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002382 if verbose:
2383 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2384 stdout = None if verbose else subprocess.PIPE
2385 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002386 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002387 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002388 except OSError:
2389 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002390
Mike Frysingere50b6a72020-02-19 01:45:48 -05002391 (output, _) = proc.communicate()
2392 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002393
Dave Borowitz137d0132015-01-02 11:12:54 -08002394 if curlret == 22:
2395 # From curl man page:
2396 # 22: HTTP page not retrieved. The requested url was not found or
2397 # returned another error with the HTTP error code being 400 or above.
2398 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002399 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002400 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2401 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002402 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002403 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002404 elif curlret and not verbose and output:
2405 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002406
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002407 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002408 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002409 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002410 return True
2411 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002412 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002413 return False
2414 else:
2415 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002416
Kris Giesingc8d882a2014-12-23 13:02:32 -08002417 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002418 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002419 with open(path, 'rb') as f:
2420 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002421 return True
2422 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002423 if not quiet:
2424 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002425 return False
2426 except OSError:
2427 return False
2428
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002429 def _Checkout(self, rev, quiet=False):
2430 cmd = ['checkout']
2431 if quiet:
2432 cmd.append('-q')
2433 cmd.append(rev)
2434 cmd.append('--')
2435 if GitCommand(self, cmd).Wait() != 0:
2436 if self._allrefs:
2437 raise GitError('%s checkout %s ' % (self.name, rev))
2438
Mike Frysinger915fda12020-03-22 12:15:20 -04002439 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002440 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002441 if ffonly:
2442 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002443 if record_origin:
2444 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002445 cmd.append(rev)
2446 cmd.append('--')
2447 if GitCommand(self, cmd).Wait() != 0:
2448 if self._allrefs:
2449 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2450
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302451 def _LsRemote(self, refs):
2452 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302453 p = GitCommand(self, cmd, capture_stdout=True)
2454 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002455 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302456 return None
2457
Anthony King7bdac712014-07-16 12:56:40 +01002458 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002459 cmd = ['revert']
2460 cmd.append('--no-edit')
2461 cmd.append(rev)
2462 cmd.append('--')
2463 if GitCommand(self, cmd).Wait() != 0:
2464 if self._allrefs:
2465 raise GitError('%s revert %s ' % (self.name, rev))
2466
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002467 def _ResetHard(self, rev, quiet=True):
2468 cmd = ['reset', '--hard']
2469 if quiet:
2470 cmd.append('-q')
2471 cmd.append(rev)
2472 if GitCommand(self, cmd).Wait() != 0:
2473 raise GitError('%s reset --hard %s ' % (self.name, rev))
2474
Martin Kellye4e94d22017-03-21 16:05:12 -07002475 def _SyncSubmodules(self, quiet=True):
2476 cmd = ['submodule', 'update', '--init', '--recursive']
2477 if quiet:
2478 cmd.append('-q')
2479 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002480 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002481
Anthony King7bdac712014-07-16 12:56:40 +01002482 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002483 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002484 if onto is not None:
2485 cmd.extend(['--onto', onto])
2486 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002487 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002488 raise GitError('%s rebase %s ' % (self.name, upstream))
2489
Pierre Tardy3d125942012-05-04 12:18:12 +02002490 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002491 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002492 if ffonly:
2493 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002494 if GitCommand(self, cmd).Wait() != 0:
2495 raise GitError('%s merge %s ' % (self.name, head))
2496
David Pursehousee8ace262020-02-13 12:41:15 +09002497 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002498 init_git_dir = not os.path.exists(self.gitdir)
2499 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002500 try:
2501 # Initialize the bare repository, which contains all of the objects.
2502 if init_obj_dir:
2503 os.makedirs(self.objdir)
2504 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002505
Mike Frysinger333c0a42021-11-15 12:39:00 -05002506 self._UpdateHooks(quiet=quiet)
2507
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002508 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002509 # Enable per-worktree config file support if possible. This is more a
2510 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002511 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002512 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002513
Kevin Degib1a07b82015-07-27 13:33:43 -06002514 # If we have a separate directory to hold refs, initialize it as well.
2515 if self.objdir != self.gitdir:
2516 if init_git_dir:
2517 os.makedirs(self.gitdir)
2518
2519 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002520 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002521 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002522 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002523 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002524 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002525 print("Retrying clone after deleting %s" %
2526 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002527 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002528 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2529 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002530 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002531 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002532 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2533 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002534 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002535 raise e
2536 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002537
Kevin Degi384b3c52014-10-16 16:02:58 -06002538 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002539 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002540 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002541
LaMont Jonescc879a92021-11-18 22:40:18 +00002542 def _expanded_ref_dirs():
2543 """Iterate through the possible git reference directory paths."""
2544 name = self.name + '.git'
2545 yield mirror_git or os.path.join(ref_dir, name)
2546 for prefix in '', self.remote.name:
2547 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2548 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002549
LaMont Jonescc879a92021-11-18 22:40:18 +00002550 if ref_dir or mirror_git:
2551 found_ref_dir = None
2552 for path in _expanded_ref_dirs():
2553 if os.path.exists(path):
2554 found_ref_dir = path
2555 break
2556 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002557
Kevin Degib1a07b82015-07-27 13:33:43 -06002558 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002559 if not os.path.isabs(ref_dir):
2560 # The alternate directory is relative to the object database.
2561 ref_dir = os.path.relpath(ref_dir,
2562 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002563 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002564 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002565
Kevin Degib1a07b82015-07-27 13:33:43 -06002566 m = self.manifest.manifestProject.config
2567 for key in ['user.name', 'user.email']:
2568 if m.Has(key, include_defaults=False):
2569 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002570 if not self.manifest.EnableGitLfs:
2571 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2572 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002573 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002574 except Exception:
2575 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002576 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002577 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002578 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002579 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002580
David Pursehousee8ace262020-02-13 12:41:15 +09002581 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002582 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002583 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002584
David Pursehousee8ace262020-02-13 12:41:15 +09002585 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002586 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002587 if not os.path.exists(hooks):
2588 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002589
2590 # Delete sample hooks. They're noise.
2591 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002592 try:
2593 platform_utils.remove(hook, missing_ok=True)
2594 except PermissionError:
2595 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002596
Jonathan Nieder93719792015-03-17 11:29:58 -07002597 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002598 name = os.path.basename(stock_hook)
2599
Victor Boivie65e0f352011-04-18 11:23:29 +02002600 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002601 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002602 # Don't install a Gerrit Code Review hook if this
2603 # project does not appear to use it for reviews.
2604 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002605 # Since the manifest project is one of those, but also
2606 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002607 continue
2608
2609 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002610 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002611 continue
2612 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002613 # If the files are the same, we'll leave it alone. We create symlinks
2614 # below by default but fallback to hardlinks if the OS blocks them.
2615 # So if we're here, it's probably because we made a hardlink below.
2616 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002617 if not quiet:
2618 _warn("%s: Not replacing locally modified %s hook",
2619 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002620 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002621 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002622 platform_utils.symlink(
2623 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002624 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002625 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002626 try:
2627 os.link(stock_hook, dst)
2628 except OSError:
2629 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002630 else:
2631 raise
2632
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002633 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002634 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002635 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002636 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002637 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002638 remote.review = self.remote.review
2639 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002640
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002641 if self.worktree:
2642 remote.ResetFetch(mirror=False)
2643 else:
2644 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002645 remote.Save()
2646
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002647 def _InitMRef(self):
2648 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002649 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002650 # Set up the m/ space to point to the worktree-specific ref space.
2651 # We'll update the worktree-specific ref space on each checkout.
2652 ref = R_M + self.manifest.branch
2653 if not self.bare_ref.symref(ref):
2654 self.bare_git.symbolic_ref(
2655 '-m', 'redirecting to worktree scope',
2656 ref, R_WORKTREE_M + self.manifest.branch)
2657
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002658 # We can't update this ref with git worktrees until it exists.
2659 # We'll wait until the initial checkout to set it.
2660 if not os.path.exists(self.worktree):
2661 return
2662
2663 base = R_WORKTREE_M
2664 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002665
2666 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002667 else:
2668 base = R_M
2669 active_git = self.bare_git
2670
2671 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002672
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002673 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002674 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002675
Remy Böhmer1469c282020-12-15 18:49:02 +01002676 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002677 cur = self.bare_ref.symref(ref)
2678
2679 if self.revisionId:
2680 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2681 msg = 'manifest set to %s' % self.revisionId
2682 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002683 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002684 else:
2685 remote = self.GetRemote(self.remote.name)
2686 dst = remote.ToLocal(self.revisionExpr)
2687 if cur != dst:
2688 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002689 if detach:
2690 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2691 else:
2692 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002693
Mike Frysingerc72bd842021-11-14 03:58:00 -05002694 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002695 # Git worktrees don't use symlinks to share at all.
2696 if self.use_git_worktrees:
2697 return
2698
Mike Frysingerd33dce02021-12-20 18:16:33 -05002699 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002700 # Try to self-heal a bit in simple cases.
2701 dst_path = os.path.join(destdir, name)
2702 src_path = os.path.join(srcdir, name)
2703
Mike Frysingered4f2112020-02-11 23:06:29 -05002704 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002705 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002706 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002707 # Fail if the links are pointing to the wrong place
2708 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002709 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002710 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002711 'work tree. If you\'re comfortable with the '
2712 'possibility of losing the work tree\'s git metadata,'
2713 ' use `repo sync --force-sync {0}` to '
2714 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002715
Mike Frysingerc72bd842021-11-14 03:58:00 -05002716 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002717 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2718
2719 Args:
2720 gitdir: The bare git repository. Must already be initialized.
2721 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002722 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2723 This saves you the effort of initializing |dotgit| yourself.
2724 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002725 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002726 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002727
2728 to_copy = []
2729 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002730 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002731
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002732 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002733 for name in set(to_copy).union(to_symlink):
2734 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002735 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002736 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002737
Kevin Degi384b3c52014-10-16 16:02:58 -06002738 if os.path.lexists(dst):
2739 continue
David James8d201162013-10-11 17:03:19 -07002740
2741 # If the source dir doesn't exist, create an empty dir.
2742 if name in symlink_dirs and not os.path.lexists(src):
2743 os.makedirs(src)
2744
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002745 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002746 platform_utils.symlink(
2747 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002748 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002749 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002750 shutil.copytree(src, dst)
2751 elif os.path.isfile(src):
2752 shutil.copy(src, dst)
2753
David James8d201162013-10-11 17:03:19 -07002754 except OSError as e:
2755 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002756 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002757 else:
2758 raise
2759
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002760 def _InitGitWorktree(self):
2761 """Init the project using git worktrees."""
2762 self.bare_git.worktree('prune')
2763 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2764 self.worktree, self.GetRevisionId())
2765
2766 # Rewrite the internal state files to use relative paths between the
2767 # checkouts & worktrees.
2768 dotgit = os.path.join(self.worktree, '.git')
2769 with open(dotgit, 'r') as fp:
2770 # Figure out the checkout->worktree path.
2771 setting = fp.read()
2772 assert setting.startswith('gitdir:')
2773 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002774 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2775 # of file permissions. Delete it and recreate it from scratch to avoid.
2776 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002777 # Use relative path from checkout->worktree & maintain Unix line endings
2778 # on all OS's to match git behavior.
2779 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002780 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2781 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002782 # Use relative path from worktree->checkout & maintain Unix line endings
2783 # on all OS's to match git behavior.
2784 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002785 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2786
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002787 self._InitMRef()
2788
Martin Kellye4e94d22017-03-21 16:05:12 -07002789 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002790 """Setup the worktree .git path.
2791
2792 This is the user-visible path like src/foo/.git/.
2793
2794 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2795 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2796
2797 Older checkouts had .git/ directories. If we see that, migrate it.
2798
2799 This also handles changes in the manifest. Maybe this project was backed
2800 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2801 the path we point to under .repo/projects/ to match.
2802 """
2803 dotgit = os.path.join(self.worktree, '.git')
2804
2805 # If using an old layout style (a directory), migrate it.
2806 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2807 self._MigrateOldWorkTreeGitDir(dotgit)
2808
2809 init_dotgit = not os.path.exists(dotgit)
2810 if self.use_git_worktrees:
2811 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002812 self._InitGitWorktree()
2813 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002814 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002815 if not init_dotgit:
2816 # See if the project has changed.
2817 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2818 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002819
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002820 if init_dotgit or not os.path.exists(dotgit):
2821 os.makedirs(self.worktree, exist_ok=True)
2822 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002823
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002824 if init_dotgit:
2825 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002826
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002827 # Finish checking out the worktree.
2828 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2829 if GitCommand(self, cmd).Wait() != 0:
2830 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002831
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002832 if submodules:
2833 self._SyncSubmodules(quiet=True)
2834 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002835
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002836 @classmethod
2837 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2838 """Migrate the old worktree .git/ dir style to a symlink.
2839
2840 This logic specifically only uses state from |dotgit| to figure out where to
2841 move content and not |self|. This way if the backing project also changed
2842 places, we only do the .git/ dir to .git symlink migration here. The path
2843 updates will happen independently.
2844 """
2845 # Figure out where in .repo/projects/ it's pointing to.
2846 if not os.path.islink(os.path.join(dotgit, 'refs')):
2847 raise GitError(f'{dotgit}: unsupported checkout state')
2848 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2849
2850 # Remove known symlink paths that exist in .repo/projects/.
2851 KNOWN_LINKS = {
2852 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2853 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2854 }
2855 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2856 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002857 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2858 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002859 }
2860
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002861 # First see if we'd succeed before starting the migration.
2862 unknown_paths = []
2863 for name in platform_utils.listdir(dotgit):
2864 # Ignore all temporary/backup names. These are common with vim & emacs.
2865 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2866 continue
2867
2868 dotgit_path = os.path.join(dotgit, name)
2869 if name in KNOWN_LINKS:
2870 if not platform_utils.islink(dotgit_path):
2871 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2872 else:
2873 gitdir_path = os.path.join(gitdir, name)
2874 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2875 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2876 if unknown_paths:
2877 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2878
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002879 # Now walk the paths and sync the .git/ to .repo/projects/.
2880 for name in platform_utils.listdir(dotgit):
2881 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002882
2883 # Ignore all temporary/backup names. These are common with vim & emacs.
2884 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2885 platform_utils.remove(dotgit_path)
2886 elif name in KNOWN_LINKS:
2887 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002888 else:
2889 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002890 platform_utils.remove(gitdir_path, missing_ok=True)
2891 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002892
2893 # Now that the dir should be empty, clear it out, and symlink it over.
2894 platform_utils.rmdir(dotgit)
2895 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002896
Renaud Paquay788e9622017-01-27 11:41:12 -08002897 def _get_symlink_error_message(self):
2898 if platform_utils.isWindows():
2899 return ('Unable to create symbolic link. Please re-run the command as '
2900 'Administrator, or see '
2901 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2902 'for other options.')
2903 return 'filesystem must support symlinks'
2904
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002905 def _revlist(self, *args, **kw):
2906 a = []
2907 a.extend(args)
2908 a.append('--')
2909 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002910
2911 @property
2912 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002913 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002914
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002915 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002916 """Get logs between two revisions of this project."""
2917 comp = '..'
2918 if rev1:
2919 revs = [rev1]
2920 if rev2:
2921 revs.extend([comp, rev2])
2922 cmd = ['log', ''.join(revs)]
2923 out = DiffColoring(self.config)
2924 if out.is_on and color:
2925 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002926 if pretty_format is not None:
2927 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002928 if oneline:
2929 cmd.append('--oneline')
2930
2931 try:
2932 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2933 if log.Wait() == 0:
2934 return log.stdout
2935 except GitError:
2936 # worktree may not exist if groups changed for example. In that case,
2937 # try in gitdir instead.
2938 if not os.path.exists(self.worktree):
2939 return self.bare_git.log(*cmd[1:])
2940 else:
2941 raise
2942 return None
2943
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002944 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2945 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002946 """Get the list of logs from this revision to given revisionId"""
2947 logs = {}
2948 selfId = self.GetRevisionId(self._allrefs)
2949 toId = toProject.GetRevisionId(toProject._allrefs)
2950
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002951 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2952 pretty_format=pretty_format)
2953 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2954 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002955 return logs
2956
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002957 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002958
David James8d201162013-10-11 17:03:19 -07002959 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002960 self._project = project
2961 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002962 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002963
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002964 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2965 def __getstate__(self):
2966 return (self._project, self._bare, self._gitdir)
2967
2968 def __setstate__(self, state):
2969 self._project, self._bare, self._gitdir = state
2970
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002971 def LsOthers(self):
2972 p = GitCommand(self._project,
2973 ['ls-files',
2974 '-z',
2975 '--others',
2976 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002977 bare=False,
David James8d201162013-10-11 17:03:19 -07002978 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002979 capture_stdout=True,
2980 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002981 if p.Wait() == 0:
2982 out = p.stdout
2983 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002984 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002985 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002986 return []
2987
2988 def DiffZ(self, name, *args):
2989 cmd = [name]
2990 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002991 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002992 cmd.extend(args)
2993 p = GitCommand(self._project,
2994 cmd,
David James8d201162013-10-11 17:03:19 -07002995 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002996 bare=False,
2997 capture_stdout=True,
2998 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002999 p.Wait()
3000 r = {}
3001 out = p.stdout
3002 if out:
3003 out = iter(out[:-1].split('\0'))
3004 while out:
3005 try:
3006 info = next(out)
3007 path = next(out)
3008 except StopIteration:
3009 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003010
Mike Frysinger84230002021-02-16 17:08:35 -05003011 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003012
Mike Frysinger84230002021-02-16 17:08:35 -05003013 def __init__(self, path, omode, nmode, oid, nid, state):
3014 self.path = path
3015 self.src_path = None
3016 self.old_mode = omode
3017 self.new_mode = nmode
3018 self.old_id = oid
3019 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003020
Mike Frysinger84230002021-02-16 17:08:35 -05003021 if len(state) == 1:
3022 self.status = state
3023 self.level = None
3024 else:
3025 self.status = state[:1]
3026 self.level = state[1:]
3027 while self.level.startswith('0'):
3028 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003029
Mike Frysinger84230002021-02-16 17:08:35 -05003030 info = info[1:].split(' ')
3031 info = _Info(path, *info)
3032 if info.status in ('R', 'C'):
3033 info.src_path = info.path
3034 info.path = next(out)
3035 r[info.path] = info
3036 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003037
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003038 def GetDotgitPath(self, subpath=None):
3039 """Return the full path to the .git dir.
3040
3041 As a convenience, append |subpath| if provided.
3042 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003043 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003044 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003045 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003046 dotgit = os.path.join(self._project.worktree, '.git')
3047 if os.path.isfile(dotgit):
3048 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3049 with open(dotgit) as fp:
3050 setting = fp.read()
3051 assert setting.startswith('gitdir:')
3052 gitdir = setting.split(':', 1)[1].strip()
3053 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3054
3055 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3056
3057 def GetHead(self):
3058 """Return the ref that HEAD points to."""
3059 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003060 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003061 with open(path) as fd:
3062 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003063 except IOError as e:
3064 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003065 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303066 line = line.decode()
3067 except AttributeError:
3068 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003069 if line.startswith('ref: '):
3070 return line[5:-1]
3071 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003072
3073 def SetHead(self, ref, message=None):
3074 cmdv = []
3075 if message is not None:
3076 cmdv.extend(['-m', message])
3077 cmdv.append(HEAD)
3078 cmdv.append(ref)
3079 self.symbolic_ref(*cmdv)
3080
3081 def DetachHead(self, new, message=None):
3082 cmdv = ['--no-deref']
3083 if message is not None:
3084 cmdv.extend(['-m', message])
3085 cmdv.append(HEAD)
3086 cmdv.append(new)
3087 self.update_ref(*cmdv)
3088
3089 def UpdateRef(self, name, new, old=None,
3090 message=None,
3091 detach=False):
3092 cmdv = []
3093 if message is not None:
3094 cmdv.extend(['-m', message])
3095 if detach:
3096 cmdv.append('--no-deref')
3097 cmdv.append(name)
3098 cmdv.append(new)
3099 if old is not None:
3100 cmdv.append(old)
3101 self.update_ref(*cmdv)
3102
3103 def DeleteRef(self, name, old=None):
3104 if not old:
3105 old = self.rev_parse(name)
3106 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003107 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003108
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003109 def rev_list(self, *args, **kw):
3110 if 'format' in kw:
3111 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3112 else:
3113 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003114 cmdv.extend(args)
3115 p = GitCommand(self._project,
3116 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003117 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003118 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003119 capture_stdout=True,
3120 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003121 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003122 raise GitError('%s rev-list %s: %s' %
3123 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003124 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003125
3126 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003127 """Allow arbitrary git commands using pythonic syntax.
3128
3129 This allows you to do things like:
3130 git_obj.rev_parse('HEAD')
3131
3132 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3133 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003134 Any other positional arguments will be passed to the git command, and the
3135 following keyword arguments are supported:
3136 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003137
3138 Args:
3139 name: The name of the git command to call. Any '_' characters will
3140 be replaced with '-'.
3141
3142 Returns:
3143 A callable object that will try to call git with the named command.
3144 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003145 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003146
Dave Borowitz091f8932012-10-23 17:01:04 -07003147 def runner(*args, **kwargs):
3148 cmdv = []
3149 config = kwargs.pop('config', None)
3150 for k in kwargs:
3151 raise TypeError('%s() got an unexpected keyword argument %r'
3152 % (name, k))
3153 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303154 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003155 cmdv.append('-c')
3156 cmdv.append('%s=%s' % (k, v))
3157 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003158 cmdv.extend(args)
3159 p = GitCommand(self._project,
3160 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003161 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003162 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003163 capture_stdout=True,
3164 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003165 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003166 raise GitError('%s %s: %s' %
3167 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003168 r = p.stdout
3169 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3170 return r[:-1]
3171 return r
3172 return runner
3173
3174
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003175class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003176
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003177 def __str__(self):
3178 return 'prior sync failed; rebase still in progress'
3179
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003180
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003181class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003182
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003183 def __str__(self):
3184 return 'contains uncommitted changes'
3185
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003186
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003187class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003188
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003189 def __init__(self, project, text):
3190 self.project = project
3191 self.text = text
3192
3193 def Print(self, syncbuf):
3194 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3195 syncbuf.out.nl()
3196
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003197
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003198class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003199
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003200 def __init__(self, project, why):
3201 self.project = project
3202 self.why = why
3203
3204 def Print(self, syncbuf):
3205 syncbuf.out.fail('error: %s/: %s',
3206 self.project.relpath,
3207 str(self.why))
3208 syncbuf.out.nl()
3209
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003210
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003211class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003212
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003213 def __init__(self, project, action):
3214 self.project = project
3215 self.action = action
3216
3217 def Run(self, syncbuf):
3218 out = syncbuf.out
3219 out.project('project %s/', self.project.relpath)
3220 out.nl()
3221 try:
3222 self.action()
3223 out.nl()
3224 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003225 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003226 out.nl()
3227 return False
3228
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003229
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003230class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003231
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003232 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003233 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003234 self.project = self.printer('header', attr='bold')
3235 self.info = self.printer('info')
3236 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003237
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003238
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003239class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003240
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003241 def __init__(self, config, detach_head=False):
3242 self._messages = []
3243 self._failures = []
3244 self._later_queue1 = []
3245 self._later_queue2 = []
3246
3247 self.out = _SyncColoring(config)
3248 self.out.redirect(sys.stderr)
3249
3250 self.detach_head = detach_head
3251 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003252 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003253
3254 def info(self, project, fmt, *args):
3255 self._messages.append(_InfoMessage(project, fmt % args))
3256
3257 def fail(self, project, err=None):
3258 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003259 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003260
3261 def later1(self, project, what):
3262 self._later_queue1.append(_Later(project, what))
3263
3264 def later2(self, project, what):
3265 self._later_queue2.append(_Later(project, what))
3266
3267 def Finish(self):
3268 self._PrintMessages()
3269 self._RunLater()
3270 self._PrintMessages()
3271 return self.clean
3272
David Rileye0684ad2017-04-05 00:02:59 -07003273 def Recently(self):
3274 recent_clean = self.recent_clean
3275 self.recent_clean = True
3276 return recent_clean
3277
3278 def _MarkUnclean(self):
3279 self.clean = False
3280 self.recent_clean = False
3281
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003282 def _RunLater(self):
3283 for q in ['_later_queue1', '_later_queue2']:
3284 if not self._RunQueue(q):
3285 return
3286
3287 def _RunQueue(self, queue):
3288 for m in getattr(self, queue):
3289 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003290 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003291 return False
3292 setattr(self, queue, [])
3293 return True
3294
3295 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003296 if self._messages or self._failures:
3297 if os.isatty(2):
3298 self.out.write(progress.CSI_ERASE_LINE)
3299 self.out.write('\r')
3300
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003301 for m in self._messages:
3302 m.Print(self)
3303 for m in self._failures:
3304 m.Print(self)
3305
3306 self._messages = []
3307 self._failures = []
3308
3309
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003310class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003311 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003312
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003313 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003314 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003315 manifest=manifest,
3316 name=name,
3317 gitdir=gitdir,
3318 objdir=gitdir,
3319 worktree=worktree,
3320 remote=RemoteSpec('origin'),
3321 relpath='.repo/%s' % name,
3322 revisionExpr='refs/heads/master',
3323 revisionId=None,
3324 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003325
3326 def PreSync(self):
3327 if self.Exists:
3328 cb = self.CurrentBranch
3329 if cb:
3330 base = self.GetBranch(cb).merge
3331 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003332 self.revisionExpr = base
3333 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003334
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003335 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003336 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003337 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003338 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003339 return False
3340
David Pursehouse8a68ff92012-09-24 12:15:13 +09003341 all_refs = self.bare_ref.all
3342 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003343 head = self.work_git.GetHead()
3344 if head.startswith(R_HEADS):
3345 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003346 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003347 except KeyError:
3348 head = None
3349
3350 if revid == head:
3351 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003352 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003353 return True
3354 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003355
3356
3357class RepoProject(MetaProject):
3358 """The MetaProject for repo itself."""
3359
3360 @property
3361 def LastFetch(self):
3362 try:
3363 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3364 return os.path.getmtime(fh)
3365 except OSError:
3366 return 0
3367
3368class ManifestProject(MetaProject):
3369 """The MetaProject for manifests."""
3370
3371 def MetaBranchSwitch(self, submodules=False):
3372 """Prepare for manifest branch switch."""
3373
3374 # detach and delete manifest branch, allowing a new
3375 # branch to take over
3376 syncbuf = SyncBuffer(self.config, detach_head=True)
3377 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3378 syncbuf.Finish()
3379
3380 return GitCommand(self,
3381 ['update-ref', '-d', 'refs/heads/default'],
3382 capture_stdout=True,
3383 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003384
3385 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003386 def standalone_manifest_url(self):
3387 """The URL of the standalone manifest, or None."""
LaMont Jones55ee3042022-04-06 17:10:21 +00003388 return self.config.GetString('manifest.standalone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003389
3390 @property
3391 def manifest_groups(self):
3392 """The manifest groups string."""
3393 return self.config.GetString('manifest.groups')
3394
3395 @property
3396 def reference(self):
3397 """The --reference for this manifest."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003398 return self.config.GetString('repo.reference')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003399
3400 @property
3401 def dissociate(self):
3402 """Whether to dissociate."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003403 return self.config.GetBoolean('repo.dissociate')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003404
3405 @property
3406 def archive(self):
3407 """Whether we use archive."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003408 return self.config.GetBoolean('repo.archive')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003409
3410 @property
3411 def mirror(self):
3412 """Whether we use mirror."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003413 return self.config.GetBoolean('repo.mirror')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003414
3415 @property
3416 def use_worktree(self):
3417 """Whether we use worktree."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003418 return self.config.GetBoolean('repo.worktree')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003419
3420 @property
3421 def clone_bundle(self):
3422 """Whether we use clone_bundle."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003423 return self.config.GetBoolean('repo.clonebundle')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003424
3425 @property
3426 def submodules(self):
3427 """Whether we use submodules."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003428 return self.config.GetBoolean('repo.submodules')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003429
3430 @property
3431 def git_lfs(self):
3432 """Whether we use git_lfs."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003433 return self.config.GetBoolean('repo.git-lfs')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003434
3435 @property
3436 def use_superproject(self):
3437 """Whether we use superproject."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003438 return self.config.GetBoolean('repo.superproject')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003439
3440 @property
3441 def partial_clone(self):
3442 """Whether this is a partial clone."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003443 return self.config.GetBoolean('repo.partialclone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003444
3445 @property
3446 def depth(self):
3447 """Partial clone depth."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003448 return self.config.GetString('repo.depth')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003449
3450 @property
3451 def clone_filter(self):
3452 """The clone filter."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003453 return self.config.GetString('repo.clonefilter')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003454
3455 @property
3456 def partial_clone_exclude(self):
3457 """Partial clone exclude string"""
LaMont Jones4ada0432022-04-14 15:10:43 +00003458 return self.config.GetBoolean('repo.partialcloneexclude')
3459
3460 @property
3461 def manifest_platform(self):
3462 """The --platform argument from `repo init`."""
3463 return self.config.GetString('manifest.platform')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003464
3465 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003466 def _platform_name(self):
3467 """Return the name of the platform."""
3468 return platform.system().lower()
3469
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003470 def SyncWithPossibleInit(self, submanifest, verbose=False,
3471 current_branch_only=False, tags='', git_event_log=None):
3472 """Sync a manifestProject, possibly for the first time.
3473
3474 Call Sync() with arguments from the most recent `repo init`. If this is a
3475 new sub manifest, then inherit options from the parent's manifestProject.
3476
3477 This is used by subcmds.Sync() to do an initial download of new sub
3478 manifests.
3479
3480 Args:
3481 submanifest: an XmlSubmanifest, the submanifest to re-sync.
3482 verbose: a boolean, whether to show all output, rather than only errors.
3483 current_branch_only: a boolean, whether to only fetch the current manifest
3484 branch from the server.
3485 tags: a boolean, whether to fetch tags.
3486 git_event_log: an EventLog, for git tracing.
3487 """
3488 # TODO(lamontjones): when refactoring sync (and init?) consider how to
3489 # better get the init options that we should use when syncing uncovers a new
3490 # submanifest.
3491 git_event_log = git_event_log or EventLog()
3492 spec = submanifest.ToSubmanifestSpec()
3493 # Use the init options from the existing manifestProject, or the parent if
3494 # it doesn't exist.
3495 #
3496 # Today, we only support changing manifest_groups on the sub-manifest, with
3497 # no supported-for-the-user way to change the other arguments from those
3498 # specified by the outermost manifest.
3499 #
3500 # TODO(lamontjones): determine which of these should come from the outermost
3501 # manifest and which should come from the parent manifest.
3502 mp = self if self.Exists else submanifest.parent.manifestProject
3503 return self.Sync(
3504 manifest_url=spec.manifestUrl,
3505 manifest_branch=spec.revision,
3506 standalone_manifest=mp.standalone_manifest_url,
3507 groups=mp.manifest_groups,
3508 platform=mp.manifest_platform,
3509 mirror=mp.mirror,
3510 dissociate=mp.dissociate,
3511 reference=mp.reference,
3512 worktree=mp.use_worktree,
3513 submodules=mp.submodules,
3514 archive=mp.archive,
3515 partial_clone=mp.partial_clone,
3516 clone_filter=mp.clone_filter,
3517 partial_clone_exclude=mp.partial_clone_exclude,
3518 clone_bundle=mp.clone_bundle,
3519 git_lfs=mp.git_lfs,
3520 use_superproject=mp.use_superproject,
3521 verbose=verbose,
3522 current_branch_only=current_branch_only,
3523 tags=tags,
3524 depth=mp.depth,
3525 git_event_log=git_event_log,
3526 manifest_name=spec.manifestName,
3527 this_manifest_only=True,
3528 outer_manifest=False,
3529 )
3530
LaMont Jones9b03f152022-03-29 23:01:18 +00003531 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003532 standalone_manifest=False, groups='', mirror=False, reference='',
3533 dissociate=False, worktree=False, submodules=False, archive=False,
3534 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003535 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3536 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jones55ee3042022-04-06 17:10:21 +00003537 git_event_log=None, platform='', manifest_name='default.xml',
3538 tags='', this_manifest_only=False, outer_manifest=True):
LaMont Jones9b03f152022-03-29 23:01:18 +00003539 """Sync the manifest and all submanifests.
3540
3541 Args:
3542 manifest_url: a string, the URL of the manifest project.
3543 manifest_branch: a string, the manifest branch to use.
3544 standalone_manifest: a boolean, whether to store the manifest as a static
3545 file.
3546 groups: a string, restricts the checkout to projects with the specified
3547 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003548 mirror: a boolean, whether to create a mirror of the remote repository.
3549 reference: a string, location of a repo instance to use as a reference.
3550 dissociate: a boolean, whether to dissociate from reference mirrors after
3551 clone.
3552 worktree: a boolean, whether to use git-worktree to manage projects.
3553 submodules: a boolean, whether sync submodules associated with the
3554 manifest project.
3555 archive: a boolean, whether to checkout each project as an archive. See
3556 git-archive.
3557 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003558 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003559 clone_filter: a string, filter to use with partial_clone.
3560 partial_clone_exclude : a string, comma-delimeted list of project namess
3561 to exclude from partial clone.
3562 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3563 git_lfs: a boolean, whether to enable git LFS support.
3564 use_superproject: a boolean, whether to use the manifest superproject to
3565 sync projects.
3566 verbose: a boolean, whether to show all output, rather than only errors.
3567 current_branch_only: a boolean, whether to only fetch the current manifest
3568 branch from the server.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003569 platform: a string, restrict the checkout to projects with the specified
3570 platform group.
LaMont Jones55ee3042022-04-06 17:10:21 +00003571 git_event_log: an EventLog, for git tracing.
LaMont Jones4ada0432022-04-14 15:10:43 +00003572 tags: a boolean, whether to fetch tags.
LaMont Jones409407a2022-04-05 21:21:56 +00003573 manifest_name: a string, the name of the manifest file to use.
3574 this_manifest_only: a boolean, whether to only operate on the current sub
3575 manifest.
3576 outer_manifest: a boolean, whether to start at the outermost manifest.
LaMont Jones9b03f152022-03-29 23:01:18 +00003577
3578 Returns:
3579 a boolean, whether the sync was successful.
3580 """
3581 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3582
LaMont Jones501733c2022-04-20 16:42:32 +00003583 groups = groups or self.manifest.GetDefaultGroupsStr(with_platform=False)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003584 platform = platform or 'auto'
LaMont Jones55ee3042022-04-06 17:10:21 +00003585 git_event_log = git_event_log or EventLog()
LaMont Jones409407a2022-04-05 21:21:56 +00003586 if outer_manifest and self.manifest.is_submanifest:
3587 # In a multi-manifest checkout, use the outer manifest unless we are told
3588 # not to.
3589 return self.client.outer_manifest.manifestProject.Sync(
3590 manifest_url=manifest_url,
3591 manifest_branch=manifest_branch,
3592 standalone_manifest=standalone_manifest,
3593 groups=groups,
3594 platform=platform,
3595 mirror=mirror,
3596 dissociate=dissociate,
3597 reference=reference,
3598 worktree=worktree,
3599 submodules=submodules,
3600 archive=archive,
3601 partial_clone=partial_clone,
3602 clone_filter=clone_filter,
3603 partial_clone_exclude=partial_clone_exclude,
3604 clone_bundle=clone_bundle,
3605 git_lfs=git_lfs,
3606 use_superproject=use_superproject,
3607 verbose=verbose,
3608 current_branch_only=current_branch_only,
3609 tags=tags,
3610 depth=depth,
LaMont Jones55ee3042022-04-06 17:10:21 +00003611 git_event_log=git_event_log,
LaMont Jones409407a2022-04-05 21:21:56 +00003612 manifest_name=manifest_name,
3613 this_manifest_only=this_manifest_only,
3614 outer_manifest=False)
3615
LaMont Jones9b03f152022-03-29 23:01:18 +00003616 # If repo has already been initialized, we take -u with the absence of
3617 # --standalone-manifest to mean "transition to a standard repo set up",
3618 # which necessitates starting fresh.
3619 # If --standalone-manifest is set, we always tear everything down and start
3620 # anew.
3621 if self.Exists:
3622 was_standalone_manifest = self.config.GetString('manifest.standalone')
3623 if was_standalone_manifest and not manifest_url:
3624 print('fatal: repo was initialized with a standlone manifest, '
3625 'cannot be re-initialized without --manifest-url/-u')
3626 return False
3627
3628 if standalone_manifest or (was_standalone_manifest and manifest_url):
3629 self.config.ClearCache()
3630 if self.gitdir and os.path.exists(self.gitdir):
3631 platform_utils.rmtree(self.gitdir)
3632 if self.worktree and os.path.exists(self.worktree):
3633 platform_utils.rmtree(self.worktree)
3634
3635 is_new = not self.Exists
3636 if is_new:
3637 if not manifest_url:
3638 print('fatal: manifest url is required.', file=sys.stderr)
3639 return False
3640
LaMont Jones409407a2022-04-05 21:21:56 +00003641 if verbose:
LaMont Jones9b03f152022-03-29 23:01:18 +00003642 print('Downloading manifest from %s' %
3643 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3644 file=sys.stderr)
3645
3646 # The manifest project object doesn't keep track of the path on the
3647 # server where this git is located, so let's save that here.
3648 mirrored_manifest_git = None
3649 if reference:
3650 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3651 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3652 if not mirrored_manifest_git.endswith(".git"):
3653 mirrored_manifest_git += ".git"
3654 if not os.path.exists(mirrored_manifest_git):
3655 mirrored_manifest_git = os.path.join(reference,
3656 '.repo/manifests.git')
3657
3658 self._InitGitDir(mirror_git=mirrored_manifest_git)
3659
3660 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3661 # still do much of the manifests.git set up, but will avoid actual syncs to
3662 # a remote.
3663 if standalone_manifest:
3664 self.config.SetString('manifest.standalone', manifest_url)
3665 elif not manifest_url and not manifest_branch:
3666 # If -u is set and --standalone-manifest is not, then we're not in
3667 # standalone mode. Otherwise, use config to infer what we were in the last
3668 # init.
3669 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3670 if not standalone_manifest:
3671 self.config.SetString('manifest.standalone', None)
3672
3673 self._ConfigureDepth(depth)
3674
3675 # Set the remote URL before the remote branch as we might need it below.
3676 if manifest_url:
3677 r = self.GetRemote(self.remote.name)
3678 r.url = manifest_url
3679 r.ResetFetch()
3680 r.Save()
3681
3682 if not standalone_manifest:
3683 if manifest_branch:
3684 if manifest_branch == 'HEAD':
3685 manifest_branch = self.ResolveRemoteHead()
3686 if manifest_branch is None:
3687 print('fatal: unable to resolve HEAD', file=sys.stderr)
3688 return False
3689 self.revisionExpr = manifest_branch
3690 else:
3691 if is_new:
3692 default_branch = self.ResolveRemoteHead()
3693 if default_branch is None:
3694 # If the remote doesn't have HEAD configured, default to master.
3695 default_branch = 'refs/heads/master'
3696 self.revisionExpr = default_branch
3697 else:
3698 self.PreSync()
3699
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003700 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003701 all_platforms = ['linux', 'darwin', 'windows']
3702 platformize = lambda x: 'platform-' + x
3703 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003704 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003705 groups.append(platformize(self._platform_name))
3706 elif platform == 'all':
3707 groups.extend(map(platformize, all_platforms))
3708 elif platform in all_platforms:
3709 groups.append(platformize(platform))
3710 elif platform != 'none':
3711 print('fatal: invalid platform flag', file=sys.stderr)
3712 return False
LaMont Jones4ada0432022-04-14 15:10:43 +00003713 self.config.SetString('manifest.platform', platform)
LaMont Jones9b03f152022-03-29 23:01:18 +00003714
3715 groups = [x for x in groups if x]
3716 groupstr = ','.join(groups)
3717 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3718 groupstr = None
3719 self.config.SetString('manifest.groups', groupstr)
3720
3721 if reference:
3722 self.config.SetString('repo.reference', reference)
3723
3724 if dissociate:
3725 self.config.SetBoolean('repo.dissociate', dissociate)
3726
3727 if worktree:
3728 if mirror:
3729 print('fatal: --mirror and --worktree are incompatible',
3730 file=sys.stderr)
3731 return False
3732 if submodules:
3733 print('fatal: --submodules and --worktree are incompatible',
3734 file=sys.stderr)
3735 return False
3736 self.config.SetBoolean('repo.worktree', worktree)
3737 if is_new:
3738 self.use_git_worktrees = True
3739 print('warning: --worktree is experimental!', file=sys.stderr)
3740
3741 if archive:
3742 if is_new:
3743 self.config.SetBoolean('repo.archive', archive)
3744 else:
3745 print('fatal: --archive is only supported when initializing a new '
3746 'workspace.', file=sys.stderr)
3747 print('Either delete the .repo folder in this workspace, or initialize '
3748 'in another location.', file=sys.stderr)
3749 return False
3750
3751 if mirror:
3752 if is_new:
3753 self.config.SetBoolean('repo.mirror', mirror)
3754 else:
3755 print('fatal: --mirror is only supported when initializing a new '
3756 'workspace.', file=sys.stderr)
3757 print('Either delete the .repo folder in this workspace, or initialize '
3758 'in another location.', file=sys.stderr)
3759 return False
3760
3761 if partial_clone is not None:
3762 if mirror:
3763 print('fatal: --mirror and --partial-clone are mutually exclusive',
3764 file=sys.stderr)
3765 return False
3766 self.config.SetBoolean('repo.partialclone', partial_clone)
3767 if clone_filter:
3768 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jones55ee3042022-04-06 17:10:21 +00003769 elif self.partial_clone:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003770 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003771 else:
3772 clone_filter = None
3773
3774 if partial_clone_exclude is not None:
3775 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3776
3777 if clone_bundle is None:
3778 clone_bundle = False if partial_clone else True
3779 else:
3780 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3781
3782 if submodules:
3783 self.config.SetBoolean('repo.submodules', submodules)
3784
3785 if git_lfs is not None:
3786 if git_lfs:
3787 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3788
3789 self.config.SetBoolean('repo.git-lfs', git_lfs)
3790 if not is_new:
3791 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3792 ' Existing projects will require manual updates.\n', file=sys.stderr)
3793
3794 if use_superproject is not None:
3795 self.config.SetBoolean('repo.superproject', use_superproject)
3796
LaMont Jones0165e202022-04-27 17:34:42 +00003797 if not standalone_manifest:
3798 if not self.Sync_NetworkHalf(
3799 is_new=is_new, quiet=not verbose, verbose=verbose,
3800 clone_bundle=clone_bundle, current_branch_only=current_branch_only,
3801 tags=tags, submodules=submodules, clone_filter=clone_filter,
3802 partial_clone_exclude=self.manifest.PartialCloneExclude):
3803 r = self.GetRemote(self.remote.name)
3804 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
LaMont Jones9b03f152022-03-29 23:01:18 +00003805
LaMont Jones0165e202022-04-27 17:34:42 +00003806 # Better delete the manifest git dir if we created it; otherwise next
3807 # time (when user fixes problems) we won't go through the "is_new" logic.
3808 if is_new:
3809 platform_utils.rmtree(self.gitdir)
LaMont Jones9b03f152022-03-29 23:01:18 +00003810 return False
3811
LaMont Jones0165e202022-04-27 17:34:42 +00003812 if manifest_branch:
3813 self.MetaBranchSwitch(submodules=submodules)
3814
3815 syncbuf = SyncBuffer(self.config)
3816 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3817 syncbuf.Finish()
3818
3819 if is_new or self.CurrentBranch is None:
3820 if not self.StartBranch('default'):
3821 print('fatal: cannot create default in manifest', file=sys.stderr)
3822 return False
3823
3824 if not manifest_name:
3825 print('fatal: manifest name (-m) is required.', file=sys.stderr)
3826 return False
3827
3828 elif is_new:
3829 # This is a new standalone manifest.
3830 manifest_name = 'default.xml'
3831 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3832 dest = os.path.join(self.worktree, manifest_name)
3833 os.makedirs(os.path.dirname(dest), exist_ok=True)
3834 with open(dest, 'wb') as f:
3835 f.write(manifest_data)
LaMont Jones409407a2022-04-05 21:21:56 +00003836
3837 try:
3838 self.manifest.Link(manifest_name)
3839 except ManifestParseError as e:
3840 print("fatal: manifest '%s' not available" % manifest_name,
3841 file=sys.stderr)
3842 print('fatal: %s' % str(e), file=sys.stderr)
3843 return False
3844
LaMont Jones55ee3042022-04-06 17:10:21 +00003845 if not this_manifest_only:
3846 for submanifest in self.manifest.submanifests.values():
LaMont Jonesb90a4222022-04-14 15:00:09 +00003847 spec = submanifest.ToSubmanifestSpec()
LaMont Jones55ee3042022-04-06 17:10:21 +00003848 submanifest.repo_client.manifestProject.Sync(
3849 manifest_url=spec.manifestUrl,
3850 manifest_branch=spec.revision,
3851 standalone_manifest=standalone_manifest,
3852 groups=self.manifest_groups,
3853 platform=platform,
3854 mirror=mirror,
3855 dissociate=dissociate,
3856 reference=reference,
3857 worktree=worktree,
3858 submodules=submodules,
3859 archive=archive,
3860 partial_clone=partial_clone,
3861 clone_filter=clone_filter,
3862 partial_clone_exclude=partial_clone_exclude,
3863 clone_bundle=clone_bundle,
3864 git_lfs=git_lfs,
3865 use_superproject=use_superproject,
3866 verbose=verbose,
3867 current_branch_only=current_branch_only,
3868 tags=tags,
3869 depth=depth,
3870 git_event_log=git_event_log,
3871 manifest_name=spec.manifestName,
3872 this_manifest_only=False,
3873 outer_manifest=False,
3874 )
LaMont Jones409407a2022-04-05 21:21:56 +00003875
LaMont Jones0ddb6772022-05-20 09:11:54 +00003876 # Lastly, if the manifest has a <superproject> then have the superproject
3877 # sync it if it will be used.
3878 if self.manifest.superproject:
3879 sync_result = self.manifest.superproject.Sync(git_event_log)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003880 if not sync_result.success:
3881 print('warning: git update of superproject for '
3882 f'{self.manifest.path_prefix} failed, repo sync will not use '
3883 'superproject to fetch source; while this error is not fatal, '
3884 'and you can continue to run repo sync, please run repo init '
3885 'with the --no-use-superproject option to stop seeing this '
3886 'warning', file=sys.stderr)
3887 if sync_result.fatal and use_superproject is not None:
3888 return False
LaMont Jones409407a2022-04-05 21:21:56 +00003889
LaMont Jones9b03f152022-03-29 23:01:18 +00003890 return True
3891
3892 def _ConfigureDepth(self, depth):
3893 """Configure the depth we'll sync down.
3894
3895 Args:
3896 depth: an int, how deep of a partial clone to create.
3897 """
3898 # Opt.depth will be non-None if user actually passed --depth to repo init.
3899 if depth is not None:
3900 if depth > 0:
3901 # Positive values will set the depth.
3902 depth = str(depth)
3903 else:
3904 # Negative numbers will clear the depth; passing None to SetString
3905 # will do that.
3906 depth = None
3907
3908 # We store the depth in the main manifest project.
3909 self.config.SetString('repo.depth', depth)