blob: 7afd6a79b1f944e6c5ffd1cbe1148b5fc6e6879a [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
52
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070053def _lwrite(path, content):
54 lock = '%s.lock' % path
55
Remy Bohmer169b0212020-11-21 10:57:52 +010056 # Maintain Unix line endings on all OS's to match git behavior.
57 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070058 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070059
60 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070061 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080063 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070064 raise
65
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070066
Shawn O. Pearce48244782009-04-16 08:25:57 -070067def _error(fmt, *args):
68 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070069 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070070
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070071
David Pursehousef33929d2015-08-24 14:39:14 +090072def _warn(fmt, *args):
73 msg = fmt % args
74 print('warn: %s' % msg, file=sys.stderr)
75
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070076
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070077def not_rev(r):
78 return '^' + r
79
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070080
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080081def sq(r):
82 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080083
David Pursehouse819827a2020-02-12 15:20:19 +090084
Jonathan Nieder93719792015-03-17 11:29:58 -070085_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070086
87
Jonathan Nieder93719792015-03-17 11:29:58 -070088def _ProjectHooks():
89 """List the hooks present in the 'hooks' directory.
90
91 These hooks are project hooks and are copied to the '.git/hooks' directory
92 of all subprojects.
93
94 This function caches the list of hooks (based on the contents of the
95 'repo/hooks' directory) on the first call.
96
97 Returns:
98 A list of absolute paths to all of the files in the hooks directory.
99 """
100 global _project_hook_list
101 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700102 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700103 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700104 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700105 return _project_hook_list
106
107
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700108class DownloadedChange(object):
109 _commit_cache = None
110
111 def __init__(self, project, base, change_id, ps_id, commit):
112 self.project = project
113 self.base = base
114 self.change_id = change_id
115 self.ps_id = ps_id
116 self.commit = commit
117
118 @property
119 def commits(self):
120 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700121 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
122 '--abbrev-commit',
123 '--pretty=oneline',
124 '--reverse',
125 '--date-order',
126 not_rev(self.base),
127 self.commit,
128 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700129 return self._commit_cache
130
131
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700132class ReviewableBranch(object):
133 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400134 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135
136 def __init__(self, project, branch, base):
137 self.project = project
138 self.branch = branch
139 self.base = base
140
141 @property
142 def name(self):
143 return self.branch.name
144
145 @property
146 def commits(self):
147 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400148 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
149 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
150 try:
151 self._commit_cache = self.project.bare_git.rev_list(*args)
152 except GitError:
153 # We weren't able to probe the commits for this branch. Was it tracking
154 # a branch that no longer exists? If so, return no commits. Otherwise,
155 # rethrow the error as we don't know what's going on.
156 if self.base_exists:
157 raise
158
159 self._commit_cache = []
160
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700161 return self._commit_cache
162
163 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800164 def unabbrev_commits(self):
165 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700166 for commit in self.project.bare_git.rev_list(not_rev(self.base),
167 R_HEADS + self.name,
168 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800169 r[commit[0:8]] = commit
170 return r
171
172 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700173 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700174 return self.project.bare_git.log('--pretty=format:%cd',
175 '-n', '1',
176 R_HEADS + self.name,
177 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178
Mike Frysinger6da17752019-09-11 18:43:17 -0400179 @property
180 def base_exists(self):
181 """Whether the branch we're tracking exists.
182
183 Normally it should, but sometimes branches we track can get deleted.
184 """
185 if self._base_exists is None:
186 try:
187 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
188 # If we're still here, the base branch exists.
189 self._base_exists = True
190 except GitError:
191 # If we failed to verify, the base branch doesn't exist.
192 self._base_exists = False
193
194 return self._base_exists
195
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700196 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500197 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700198 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500199 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500200 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200201 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700202 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200203 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200204 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800205 validate_certs=True,
206 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500207 self.project.UploadForReview(branch=self.name,
208 people=people,
209 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700210 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500211 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500212 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200213 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700214 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200215 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200216 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800217 validate_certs=validate_certs,
218 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700219
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700220 def GetPublishedRefs(self):
221 refs = {}
222 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700223 self.branch.remote.SshReviewUrl(self.project.UserEmail),
224 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700225 for line in output.split('\n'):
226 try:
227 (sha, ref) = line.split()
228 refs[sha] = ref
229 except ValueError:
230 pass
231
232 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700234
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700236
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500238 super().__init__(config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100239 self.project = self.printer('header', attr='bold')
240 self.branch = self.printer('header', attr='bold')
241 self.nobranch = self.printer('nobranch', fg='red')
242 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243
Anthony King7bdac712014-07-16 12:56:40 +0100244 self.added = self.printer('added', fg='green')
245 self.changed = self.printer('changed', fg='red')
246 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247
248
249class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700250
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500252 super().__init__(config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100253 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400254 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700256
Jack Neus6ea0cae2021-07-20 20:52:33 +0000257class Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700258
James W. Mills24c13082012-04-12 15:04:13 -0500259 def __init__(self, name, value, keep):
260 self.name = name
261 self.value = value
262 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700263
Jack Neus6ea0cae2021-07-20 20:52:33 +0000264 def __eq__(self, other):
265 if not isinstance(other, Annotation):
266 return False
267 return self.__dict__ == other.__dict__
268
269 def __lt__(self, other):
270 # This exists just so that lists of Annotation objects can be sorted, for
271 # use in comparisons.
272 if not isinstance(other, Annotation):
273 raise ValueError('comparison is not between two Annotation objects')
274 if self.name == other.name:
275 if self.value == other.value:
276 return self.keep < other.keep
277 return self.value < other.value
278 return self.name < other.name
279
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700280
Mike Frysingere6a202f2019-08-02 15:57:57 -0400281def _SafeExpandPath(base, subpath, skipfinal=False):
282 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700283
Mike Frysingere6a202f2019-08-02 15:57:57 -0400284 We make sure no intermediate symlinks are traversed, and that the final path
285 is not a special file (e.g. not a socket or fifo).
286
287 NB: We rely on a number of paths already being filtered out while parsing the
288 manifest. See the validation logic in manifest_xml.py for more details.
289 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500290 # Split up the path by its components. We can't use os.path.sep exclusively
291 # as some platforms (like Windows) will convert / to \ and that bypasses all
292 # our constructed logic here. Especially since manifest authors only use
293 # / in their paths.
294 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
295 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400296 if skipfinal:
297 # Whether the caller handles the final component itself.
298 finalpart = components.pop()
299
300 path = base
301 for part in components:
302 if part in {'.', '..'}:
303 raise ManifestInvalidPathError(
304 '%s: "%s" not allowed in paths' % (subpath, part))
305
306 path = os.path.join(path, part)
307 if platform_utils.islink(path):
308 raise ManifestInvalidPathError(
309 '%s: traversing symlinks not allow' % (path,))
310
311 if os.path.exists(path):
312 if not os.path.isfile(path) and not platform_utils.isdir(path):
313 raise ManifestInvalidPathError(
314 '%s: only regular files & directories allowed' % (path,))
315
316 if skipfinal:
317 path = os.path.join(path, finalpart)
318
319 return path
320
321
322class _CopyFile(object):
323 """Container for <copyfile> manifest element."""
324
325 def __init__(self, git_worktree, src, topdir, dest):
326 """Register a <copyfile> request.
327
328 Args:
329 git_worktree: Absolute path to the git project checkout.
330 src: Relative path under |git_worktree| of file to read.
331 topdir: Absolute path to the top of the repo client checkout.
332 dest: Relative path under |topdir| of file to write.
333 """
334 self.git_worktree = git_worktree
335 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700336 self.src = src
337 self.dest = dest
338
339 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400340 src = _SafeExpandPath(self.git_worktree, self.src)
341 dest = _SafeExpandPath(self.topdir, self.dest)
342
343 if platform_utils.isdir(src):
344 raise ManifestInvalidPathError(
345 '%s: copying from directory not supported' % (self.src,))
346 if platform_utils.isdir(dest):
347 raise ManifestInvalidPathError(
348 '%s: copying to directory not allowed' % (self.dest,))
349
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700350 # copy file if it does not exist or is out of date
351 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
352 try:
353 # remove existing file first, since it might be read-only
354 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800355 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400356 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200357 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700358 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200359 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700360 shutil.copy(src, dest)
361 # make the file read-only
362 mode = os.stat(dest)[stat.ST_MODE]
363 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
364 os.chmod(dest, mode)
365 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700366 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700367
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700368
Anthony King7bdac712014-07-16 12:56:40 +0100369class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400370 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700371
Mike Frysingere6a202f2019-08-02 15:57:57 -0400372 def __init__(self, git_worktree, src, topdir, dest):
373 """Register a <linkfile> request.
374
375 Args:
376 git_worktree: Absolute path to the git project checkout.
377 src: Target of symlink relative to path under |git_worktree|.
378 topdir: Absolute path to the top of the repo client checkout.
379 dest: Relative path under |topdir| of symlink to create.
380 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700381 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400382 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500383 self.src = src
384 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500385
Wink Saville4c426ef2015-06-03 08:05:17 -0700386 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500387 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700388 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500389 try:
390 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800391 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800392 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500393 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700394 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700395 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500396 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700397 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500398 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700399 _error('Cannot link file %s to %s', relSrc, absDest)
400
401 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400402 """Link the self.src & self.dest paths.
403
404 Handles wild cards on the src linking all of the files in the source in to
405 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700406 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500407 # Some people use src="." to create stable links to projects. Lets allow
408 # that but reject all other uses of "." to keep things simple.
409 if self.src == '.':
410 src = self.git_worktree
411 else:
412 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400413
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300414 if not glob.has_magic(src):
415 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400416 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
417 # dest & src are absolute paths at this point. Make sure the target of
418 # the symlink is relative in the context of the repo client checkout.
419 relpath = os.path.relpath(src, os.path.dirname(dest))
420 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700421 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400422 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300423 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400424 if os.path.exists(dest) and not platform_utils.isdir(dest):
425 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700426 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400427 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700428 # Create a releative path from source dir to destination dir
429 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400430 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700431
432 # Get the source file name
433 srcFile = os.path.basename(absSrcFile)
434
435 # Now form the final full paths to srcFile. They will be
436 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400437 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700438 relSrc = os.path.join(relSrcDir, srcFile)
439 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500440
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700441
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700442class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700443
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700444 def __init__(self,
445 name,
Anthony King7bdac712014-07-16 12:56:40 +0100446 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700447 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100448 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700449 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700450 orig_name=None,
451 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700452 self.name = name
453 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700454 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700455 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100456 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700457 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700458 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700459
Ian Kasprzak0286e312021-02-05 10:06:18 -0800460
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700461class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600462 # These objects can be shared between several working trees.
Mike Frysinger41289c62021-12-20 17:30:33 -0500463 shareable_dirs = ['hooks', 'objects', 'rr-cache']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700464
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700465 def __init__(self,
466 manifest,
467 name,
468 remote,
469 gitdir,
David James8d201162013-10-11 17:03:19 -0700470 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700471 worktree,
472 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700473 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800474 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100475 rebase=True,
476 groups=None,
477 sync_c=False,
478 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900479 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100480 clone_depth=None,
481 upstream=None,
482 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500483 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100484 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900485 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700486 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600487 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700488 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800489 """Init a Project object.
490
491 Args:
492 manifest: The XmlManifest object.
493 name: The `name` attribute of manifest.xml's project element.
494 remote: RemoteSpec object specifying its remote's properties.
495 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700496 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800497 worktree: Absolute path of git working tree.
498 relpath: Relative path of git working tree to repo's top directory.
499 revisionExpr: The `revision` attribute of manifest.xml's project element.
500 revisionId: git commit id for checking out.
501 rebase: The `rebase` attribute of manifest.xml's project element.
502 groups: The `groups` attribute of manifest.xml's project element.
503 sync_c: The `sync-c` attribute of manifest.xml's project element.
504 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900505 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800506 upstream: The `upstream` attribute of manifest.xml's project element.
507 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500508 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800509 is_derived: False if the project was explicitly defined in the manifest;
510 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400511 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900512 optimized_fetch: If True, when a project is set to a sha1 revision, only
513 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600514 retry_fetches: Retry remote fetches n times upon receiving transient error
515 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700516 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800517 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400518 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700519 self.name = name
520 self.remote = remote
Michael Kelly37c21c22020-06-13 02:10:40 -0700521 self.UpdatePaths(relpath, worktree, gitdir, objdir)
Michael Kelly2f3c3312020-07-21 19:40:38 -0700522 self.SetRevision(revisionExpr, revisionId=revisionId)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700523
Mike Pontillod3153822012-02-28 11:53:24 -0800524 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700525 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700526 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800527 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900528 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900529 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700530 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800531 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500532 # NB: Do not use this setting in __init__ to change behavior so that the
533 # manifest.git checkout can inspect & change it after instantiating. See
534 # the XmlManifest init code for more info.
535 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800536 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900537 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600538 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800539 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800540
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700541 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700542 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500543 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500544 self.annotations = []
Bryan Jacobsf609f912013-05-06 13:36:24 -0400545 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700546 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700547
Doug Anderson37282b42011-03-04 11:54:18 -0800548 # This will be filled in if a project is later identified to be the
549 # project containing repo hooks.
550 self.enabled_repo_hooks = []
551
LaMont Jonescc879a92021-11-18 22:40:18 +0000552 def RelPath(self, local=True):
553 """Return the path for the project relative to a manifest.
554
555 Args:
556 local: a boolean, if True, the path is relative to the local
557 (sub)manifest. If false, the path is relative to the
558 outermost manifest.
559 """
560 if local:
561 return self.relpath
562 return os.path.join(self.manifest.path_prefix, self.relpath)
563
Michael Kelly2f3c3312020-07-21 19:40:38 -0700564 def SetRevision(self, revisionExpr, revisionId=None):
565 """Set revisionId based on revision expression and id"""
566 self.revisionExpr = revisionExpr
567 if revisionId is None and revisionExpr and IsId(revisionExpr):
568 self.revisionId = self.revisionExpr
569 else:
570 self.revisionId = revisionId
571
Michael Kelly37c21c22020-06-13 02:10:40 -0700572 def UpdatePaths(self, relpath, worktree, gitdir, objdir):
573 """Update paths used by this project"""
574 self.gitdir = gitdir.replace('\\', '/')
575 self.objdir = objdir.replace('\\', '/')
576 if worktree:
577 self.worktree = os.path.normpath(worktree).replace('\\', '/')
578 else:
579 self.worktree = None
580 self.relpath = relpath
581
582 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
583 defaults=self.manifest.globalConfig)
584
585 if self.worktree:
586 self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
587 else:
588 self.work_git = None
589 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
590 self.bare_ref = GitRefs(self.gitdir)
591 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
592
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700593 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800594 def Derived(self):
595 return self.is_derived
596
597 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700598 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700599 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700600
601 @property
602 def CurrentBranch(self):
603 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400604
605 The branch name omits the 'refs/heads/' prefix.
606 None is returned if the project is on a detached HEAD, or if the work_git is
607 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700608 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400609 try:
610 b = self.work_git.GetHead()
611 except NoManifestException:
612 # If the local checkout is in a bad state, don't barf. Let the callers
613 # process this like the head is unreadable.
614 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700615 if b.startswith(R_HEADS):
616 return b[len(R_HEADS):]
617 return None
618
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700619 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500620 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
621 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
622 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200623
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700624 def IsDirty(self, consider_untracked=True):
625 """Is the working directory modified in some way?
626 """
627 self.work_git.update_index('-q',
628 '--unmerged',
629 '--ignore-missing',
630 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900631 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700632 return True
633 if self.work_git.DiffZ('diff-files'):
634 return True
635 if consider_untracked and self.work_git.LsOthers():
636 return True
637 return False
638
639 _userident_name = None
640 _userident_email = None
641
642 @property
643 def UserName(self):
644 """Obtain the user's personal name.
645 """
646 if self._userident_name is None:
647 self._LoadUserIdentity()
648 return self._userident_name
649
650 @property
651 def UserEmail(self):
652 """Obtain the user's email address. This is very likely
653 to be their Gerrit login.
654 """
655 if self._userident_email is None:
656 self._LoadUserIdentity()
657 return self._userident_email
658
659 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900660 u = self.bare_git.var('GIT_COMMITTER_IDENT')
661 m = re.compile("^(.*) <([^>]*)> ").match(u)
662 if m:
663 self._userident_name = m.group(1)
664 self._userident_email = m.group(2)
665 else:
666 self._userident_name = ''
667 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700668
669 def GetRemote(self, name):
670 """Get the configuration for a single remote.
671 """
672 return self.config.GetRemote(name)
673
674 def GetBranch(self, name):
675 """Get the configuration for a single branch.
676 """
677 return self.config.GetBranch(name)
678
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700679 def GetBranches(self):
680 """Get all existing local branches.
681 """
682 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900683 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700684 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700685
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530686 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700687 if name.startswith(R_HEADS):
688 name = name[len(R_HEADS):]
689 b = self.GetBranch(name)
690 b.current = name == current
691 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900692 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700693 heads[name] = b
694
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530695 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700696 if name.startswith(R_PUB):
697 name = name[len(R_PUB):]
698 b = heads.get(name)
699 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900700 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700701
702 return heads
703
Colin Cross5acde752012-03-28 20:15:45 -0700704 def MatchesGroups(self, manifest_groups):
705 """Returns true if the manifest groups specified at init should cause
706 this project to be synced.
707 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700708 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700709
710 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700711 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700712 manifest_groups: "-group1,group2"
713 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500714
715 The special manifest group "default" will match any project that
716 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700717 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500718 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700719 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700720 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500721 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700722
Conley Owens971de8e2012-04-16 10:36:08 -0700723 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700724 for group in expanded_manifest_groups:
725 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700726 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700727 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700728 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700729
Conley Owens971de8e2012-04-16 10:36:08 -0700730 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700731
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700732# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700733 def UncommitedFiles(self, get_all=True):
734 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700736 Args:
737 get_all: a boolean, if True - get information about all different
738 uncommitted files. If False - return as soon as any kind of
739 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500740 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700741 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500742 self.work_git.update_index('-q',
743 '--unmerged',
744 '--ignore-missing',
745 '--refresh')
746 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700747 details.append("rebase in progress")
748 if not get_all:
749 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500750
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700751 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
752 if changes:
753 details.extend(changes)
754 if not get_all:
755 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500756
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700757 changes = self.work_git.DiffZ('diff-files').keys()
758 if changes:
759 details.extend(changes)
760 if not get_all:
761 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500762
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700763 changes = self.work_git.LsOthers()
764 if changes:
765 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500766
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700767 return details
768
769 def HasChanges(self):
770 """Returns true if there are uncommitted changes.
771 """
772 if self.UncommitedFiles(get_all=False):
773 return True
774 else:
775 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500776
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600777 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700778 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200779
780 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200781 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600782 quiet: If True then only print the project name. Do not print
783 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700785 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700786 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200787 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700788 print(file=output_redir)
789 print('project %s/' % self.relpath, file=output_redir)
790 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791 return
792
793 self.work_git.update_index('-q',
794 '--unmerged',
795 '--ignore-missing',
796 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700797 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700798 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
799 df = self.work_git.DiffZ('diff-files')
800 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100801 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700802 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803
804 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700805 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200806 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700807 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700808
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600809 if quiet:
810 out.nl()
811 return 'DIRTY'
812
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700813 branch = self.CurrentBranch
814 if branch is None:
815 out.nobranch('(*** NO BRANCH ***)')
816 else:
817 out.branch('branch %s', branch)
818 out.nl()
819
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700820 if rb:
821 out.important('prior sync failed; rebase still in progress')
822 out.nl()
823
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824 paths = list()
825 paths.extend(di.keys())
826 paths.extend(df.keys())
827 paths.extend(do)
828
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530829 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900830 try:
831 i = di[p]
832 except KeyError:
833 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900835 try:
836 f = df[p]
837 except KeyError:
838 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200839
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900840 if i:
841 i_status = i.status.upper()
842 else:
843 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900845 if f:
846 f_status = f.status.lower()
847 else:
848 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849
850 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800851 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700852 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853 else:
854 line = ' %s%s\t%s' % (i_status, f_status, p)
855
856 if i and not f:
857 out.added('%s', line)
858 elif (i and f) or (not i and f):
859 out.changed('%s', line)
860 elif not i and not f:
861 out.untracked('%s', line)
862 else:
863 out.write('%s', line)
864 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200865
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700866 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500868 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 """Prints the status of the repository to stdout.
870 """
871 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500872 if output_redir:
873 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874 cmd = ['diff']
875 if out.is_on:
876 cmd.append('--color')
877 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300878 if absolute_paths:
879 cmd.append('--src-prefix=a/%s/' % self.relpath)
880 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400882 try:
883 p = GitCommand(self,
884 cmd,
885 capture_stdout=True,
886 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500887 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400888 except GitError as e:
889 out.nl()
890 out.project('project %s/' % self.relpath)
891 out.nl()
892 out.fail('%s', str(e))
893 out.nl()
894 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500895 if p.stdout:
896 out.nl()
897 out.project('project %s/' % self.relpath)
898 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500899 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400900 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700902# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900903 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904 """Was the branch published (uploaded) for code review?
905 If so, returns the SHA-1 hash of the last published
906 state for the branch.
907 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700908 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900909 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700910 try:
911 return self.bare_git.rev_parse(key)
912 except GitError:
913 return None
914 else:
915 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900916 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700917 except KeyError:
918 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919
David Pursehouse8a68ff92012-09-24 12:15:13 +0900920 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921 """Prunes any stale published refs.
922 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900923 if all_refs is None:
924 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925 heads = set()
926 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530927 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928 if name.startswith(R_HEADS):
929 heads.add(name)
930 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900931 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530933 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934 n = name[len(R_PUB):]
935 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900936 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700938 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 """List any branches which can be uploaded for review.
940 """
941 heads = {}
942 pubed = {}
943
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530944 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900946 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900948 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949
950 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530951 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900952 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700954 if selected_branch and branch != selected_branch:
955 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700956
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800957 rb = self.GetUploadableBranch(branch)
958 if rb:
959 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700960 return ready
961
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800962 def GetUploadableBranch(self, branch_name):
963 """Get a single uploadable branch, or None.
964 """
965 branch = self.GetBranch(branch_name)
966 base = branch.LocalMerge
967 if branch.LocalMerge:
968 rb = ReviewableBranch(self, branch, base)
969 if rb.commits:
970 return rb
971 return None
972
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700973 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100974 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500975 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700976 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500977 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500978 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200979 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700980 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200981 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200982 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800983 validate_certs=True,
984 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700985 """Uploads the named branch for code review.
986 """
987 if branch is None:
988 branch = self.CurrentBranch
989 if branch is None:
990 raise GitError('not currently on a branch')
991
992 branch = self.GetBranch(branch)
993 if not branch.LocalMerge:
994 raise GitError('branch %s does not track a remote' % branch.name)
995 if not branch.remote.review:
996 raise GitError('remote %s has no review url' % branch.remote.name)
997
Bryan Jacobsf609f912013-05-06 13:36:24 -0400998 if dest_branch is None:
999 dest_branch = self.dest_branch
1000 if dest_branch is None:
1001 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002 if not dest_branch.startswith(R_HEADS):
1003 dest_branch = R_HEADS + dest_branch
1004
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001005 if not branch.remote.projectname:
1006 branch.remote.projectname = self.name
1007 branch.remote.Save()
1008
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001009 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001010 if url is None:
1011 raise UploadError('review not configured')
1012 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001013 if dryrun:
1014 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001015
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001016 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001017 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001018
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001019 for push_option in (push_options or []):
1020 cmd.append('-o')
1021 cmd.append(push_option)
1022
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001023 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001024
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001025 if dest_branch.startswith(R_HEADS):
1026 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001027
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001028 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001029 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001030 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001031 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001032 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001033 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001034
David Pursehousef25a3702018-11-14 19:01:22 -08001035 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001036 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001037 if notify:
1038 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001039 if private:
1040 opts += ['private']
1041 if wip:
1042 opts += ['wip']
1043 if opts:
1044 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001045 cmd.append(ref_spec)
1046
Anthony King7bdac712014-07-16 12:56:40 +01001047 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001048 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001049
Mike Frysingerd7f86832020-11-19 19:18:46 -05001050 if not dryrun:
1051 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1052 self.bare_git.UpdateRef(R_PUB + branch.name,
1053 R_HEADS + branch.name,
1054 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001056# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001057 def _ExtractArchive(self, tarpath, path=None):
1058 """Extract the given tar on its current location
1059
1060 Args:
1061 - tarpath: The path to the actual tar file
1062
1063 """
1064 try:
1065 with tarfile.open(tarpath, 'r') as tar:
1066 tar.extractall(path=path)
1067 return True
1068 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001069 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001070 return False
1071
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001072 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001073 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001074 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001075 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001076 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001077 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001078 force_sync=False,
1079 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001080 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001081 archive=False,
1082 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001083 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001084 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001085 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001086 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001087 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001088 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089 """Perform only the network IO portion of the sync process.
1090 Local working directory/branch state is not affected.
1091 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001092 if archive and not isinstance(self, MetaProject):
1093 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001094 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001095 return False
1096
1097 name = self.relpath.replace('\\', '/')
1098 name = name.replace('/', '_')
1099 tarpath = '%s.tar' % name
1100 topdir = self.manifest.topdir
1101
1102 try:
1103 self._FetchArchive(tarpath, cwd=topdir)
1104 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001105 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001106 return False
1107
1108 # From now on, we only need absolute tarpath
1109 tarpath = os.path.join(topdir, tarpath)
1110
1111 if not self._ExtractArchive(tarpath, path=topdir):
1112 return False
1113 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001114 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001115 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001116 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001117 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001118 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001119
1120 # If the shared object dir already exists, don't try to rebootstrap with a
1121 # clone bundle download. We should have the majority of objects already.
1122 if clone_bundle and os.path.exists(self.objdir):
1123 clone_bundle = False
1124
Raman Tennetif32f2432021-04-12 20:57:25 -07001125 if self.name in partial_clone_exclude:
1126 clone_bundle = True
1127 clone_filter = None
1128
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001129 if is_new is None:
1130 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001131 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001132 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001133 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001134 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001136
1137 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001138 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001139 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001140 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001141 # This works for both absolute and relative alternate directories.
1142 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001143 except IOError:
1144 alt_dir = None
1145 else:
1146 alt_dir = None
1147
Mike Frysingere50b6a72020-02-19 01:45:48 -05001148 if (clone_bundle
1149 and alt_dir is None
1150 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001151 is_new = False
1152
Mike Frysinger73561142021-05-03 01:10:09 -04001153 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001154 if self.sync_c:
1155 current_branch_only = True
1156 elif not self.manifest._loaded:
1157 # Manifest cannot check defaults until it syncs.
1158 current_branch_only = False
1159 elif self.manifest.default.sync_c:
1160 current_branch_only = True
1161
Mike Frysingerd68ed632021-05-03 01:21:35 -04001162 if tags is None:
1163 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001164
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001165 if self.clone_depth:
1166 depth = self.clone_depth
1167 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001168 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001169
Mike Frysinger521d01b2020-02-17 01:51:49 -05001170 # See if we can skip the network fetch entirely.
1171 if not (optimized_fetch and
1172 (ID_RE.match(self.revisionExpr) and
1173 self._CheckForImmutableRevision())):
1174 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001175 initial=is_new,
1176 quiet=quiet, verbose=verbose, output_redir=output_redir,
1177 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001178 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001179 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001180 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001181 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001182 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001183
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001184 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001185 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001186 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001187 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001188 if os.path.exists(alternates_file):
1189 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001190 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1191 merge_output=bool(output_redir))
1192 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001193 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001194 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001195 return False
1196 platform_utils.remove(alternates_file)
1197
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001198 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001199 self._InitMRef()
1200 else:
1201 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001202 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1203 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001205
1206 def PostRepoUpgrade(self):
1207 self._InitHooks()
1208
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001209 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001210 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001211 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001212 for copyfile in self.copyfiles:
1213 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001214 for linkfile in self.linkfiles:
1215 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001216
Julien Camperguedd654222014-01-09 16:21:37 +01001217 def GetCommitRevisionId(self):
1218 """Get revisionId of a commit.
1219
1220 Use this method instead of GetRevisionId to get the id of the commit rather
1221 than the id of the current git object (for example, a tag)
1222
1223 """
1224 if not self.revisionExpr.startswith(R_TAGS):
1225 return self.GetRevisionId(self._allrefs)
1226
1227 try:
1228 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1229 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001230 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1231 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001232
David Pursehouse8a68ff92012-09-24 12:15:13 +09001233 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001234 if self.revisionId:
1235 return self.revisionId
1236
1237 rem = self.GetRemote(self.remote.name)
1238 rev = rem.ToLocal(self.revisionExpr)
1239
David Pursehouse8a68ff92012-09-24 12:15:13 +09001240 if all_refs is not None and rev in all_refs:
1241 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001242
1243 try:
1244 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1245 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001246 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1247 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001248
Raman Tenneti6a872c92021-01-14 19:17:50 -08001249 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001250 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001251 self.upstream = self.revisionExpr
1252
Raman Tenneti6a872c92021-01-14 19:17:50 -08001253 self.revisionId = revisionId
1254
Martin Kellye4e94d22017-03-21 16:05:12 -07001255 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001256 """Perform only the local IO portion of the sync process.
1257 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001259 if not os.path.exists(self.gitdir):
1260 syncbuf.fail(self,
1261 'Cannot checkout %s due to missing network sync; Run '
1262 '`repo sync -n %s` first.' %
1263 (self.name, self.name))
1264 return
1265
Martin Kellye4e94d22017-03-21 16:05:12 -07001266 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001267 all_refs = self.bare_ref.all
1268 self.CleanPublishedCache(all_refs)
1269 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001270
Mike Frysinger0458faa2021-03-10 23:35:44 -05001271 # Special case the root of the repo client checkout. Make sure it doesn't
1272 # contain files being checked out to dirs we don't allow.
1273 if self.relpath == '.':
1274 PROTECTED_PATHS = {'.repo'}
1275 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1276 bad_paths = paths & PROTECTED_PATHS
1277 if bad_paths:
1278 syncbuf.fail(self,
1279 'Refusing to checkout project that writes to protected '
1280 'paths: %s' % (', '.join(bad_paths),))
1281 return
1282
David Pursehouse1d947b32012-10-25 12:23:11 +09001283 def _doff():
1284 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001285 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001286
Martin Kellye4e94d22017-03-21 16:05:12 -07001287 def _dosubmodules():
1288 self._SyncSubmodules(quiet=True)
1289
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001290 head = self.work_git.GetHead()
1291 if head.startswith(R_HEADS):
1292 branch = head[len(R_HEADS):]
1293 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001294 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001295 except KeyError:
1296 head = None
1297 else:
1298 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001300 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 # Currently on a detached HEAD. The user is assumed to
1302 # not have any local modifications worth worrying about.
1303 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001304 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001305 syncbuf.fail(self, _PriorSyncFailedError())
1306 return
1307
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001308 if head == revid:
1309 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001310 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001311 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001312 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001313 # The copy/linkfile config may have changed.
1314 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001315 return
1316 else:
1317 lost = self._revlist(not_rev(revid), HEAD)
1318 if lost:
1319 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001320
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001322 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001323 if submodules:
1324 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001325 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001326 syncbuf.fail(self, e)
1327 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001328 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001329 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001331 if head == revid:
1332 # No changes; don't do anything further.
1333 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001334 # The copy/linkfile config may have changed.
1335 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001336 return
1337
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001338 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001339
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001340 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001341 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001342 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001343 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001344 syncbuf.info(self,
1345 "leaving %s; does not track upstream",
1346 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001347 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001348 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001349 if submodules:
1350 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001351 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001352 syncbuf.fail(self, e)
1353 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001354 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001355 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001356
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001357 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001358
1359 # See if we can perform a fast forward merge. This can happen if our
1360 # branch isn't in the exact same state as we last published.
1361 try:
1362 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1363 # Skip the published logic.
1364 pub = False
1365 except GitError:
1366 pub = self.WasPublished(branch.name, all_refs)
1367
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001369 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001370 if not_merged:
1371 if upstream_gain:
1372 # The user has published this branch and some of those
1373 # commits are not yet merged upstream. We do not want
1374 # to rewrite the published commits so we punt.
1375 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001376 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001377 "branch %s is published (but not merged) and is now "
1378 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001379 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001380 elif pub == head:
1381 # All published commits are merged, and thus we are a
1382 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001383 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001384 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001385 if submodules:
1386 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001387 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001388
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001389 # Examine the local commits not in the remote. Find the
1390 # last one attributed to this user, if any.
1391 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001392 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001393 last_mine = None
1394 cnt_mine = 0
1395 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001396 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001397 if committer_email == self.UserEmail:
1398 last_mine = commit_id
1399 cnt_mine += 1
1400
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001401 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001402 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403
1404 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001405 syncbuf.fail(self, _DirtyError())
1406 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001408 # If the upstream switched on us, warn the user.
1409 #
1410 if branch.merge != self.revisionExpr:
1411 if branch.merge and self.revisionExpr:
1412 syncbuf.info(self,
1413 'manifest switched %s...%s',
1414 branch.merge,
1415 self.revisionExpr)
1416 elif branch.merge:
1417 syncbuf.info(self,
1418 'manifest no longer tracks %s',
1419 branch.merge)
1420
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001421 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001422 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001423 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001424 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001425 syncbuf.info(self,
1426 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001427 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001428
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001429 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001430 if not ID_RE.match(self.revisionExpr):
1431 # in case of manifest sync the revisionExpr might be a SHA1
1432 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001433 if not branch.merge.startswith('refs/'):
1434 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001435 branch.Save()
1436
Mike Pontillod3153822012-02-28 11:53:24 -08001437 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001438 def _docopyandlink():
1439 self._CopyAndLinkFiles()
1440
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001441 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001442 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001443 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001444 if submodules:
1445 syncbuf.later2(self, _dosubmodules)
1446 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001447 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001448 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001449 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001450 if submodules:
1451 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001452 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001453 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001454 syncbuf.fail(self, e)
1455 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001456 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001457 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001458 if submodules:
1459 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001460
Mike Frysingere6a202f2019-08-02 15:57:57 -04001461 def AddCopyFile(self, src, dest, topdir):
1462 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001463
Mike Frysingere6a202f2019-08-02 15:57:57 -04001464 No filesystem changes occur here. Actual copying happens later on.
1465
1466 Paths should have basic validation run on them before being queued.
1467 Further checking will be handled when the actual copy happens.
1468 """
1469 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1470
1471 def AddLinkFile(self, src, dest, topdir):
1472 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1473
1474 No filesystem changes occur here. Actual linking happens later on.
1475
1476 Paths should have basic validation run on them before being queued.
1477 Further checking will be handled when the actual link happens.
1478 """
1479 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001480
James W. Mills24c13082012-04-12 15:04:13 -05001481 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001482 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001483
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001484 def DownloadPatchSet(self, change_id, patch_id):
1485 """Download a single patch set of a single change to FETCH_HEAD.
1486 """
1487 remote = self.GetRemote(self.remote.name)
1488
1489 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001490 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001491 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001492 if GitCommand(self, cmd, bare=True).Wait() != 0:
1493 return None
1494 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001495 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001496 change_id,
1497 patch_id,
1498 self.bare_git.rev_parse('FETCH_HEAD'))
1499
Mike Frysingerc0d18662020-02-19 19:19:18 -05001500 def DeleteWorktree(self, quiet=False, force=False):
1501 """Delete the source checkout and any other housekeeping tasks.
1502
1503 This currently leaves behind the internal .repo/ cache state. This helps
1504 when switching branches or manifest changes get reverted as we don't have
1505 to redownload all the git objects. But we should do some GC at some point.
1506
1507 Args:
1508 quiet: Whether to hide normal messages.
1509 force: Always delete tree even if dirty.
1510
1511 Returns:
1512 True if the worktree was completely cleaned out.
1513 """
1514 if self.IsDirty():
1515 if force:
1516 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1517 (self.relpath,), file=sys.stderr)
1518 else:
1519 print('error: %s: Cannot remove project: uncommitted changes are '
1520 'present.\n' % (self.relpath,), file=sys.stderr)
1521 return False
1522
1523 if not quiet:
1524 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1525
1526 # Unlock and delink from the main worktree. We don't use git's worktree
1527 # remove because it will recursively delete projects -- we handle that
1528 # ourselves below. https://crbug.com/git/48
1529 if self.use_git_worktrees:
1530 needle = platform_utils.realpath(self.gitdir)
1531 # Find the git worktree commondir under .repo/worktrees/.
1532 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1533 assert output.startswith('worktree '), output
1534 commondir = output[9:]
1535 # Walk each of the git worktrees to see where they point.
1536 configs = os.path.join(commondir, 'worktrees')
1537 for name in os.listdir(configs):
1538 gitdir = os.path.join(configs, name, 'gitdir')
1539 with open(gitdir) as fp:
1540 relpath = fp.read().strip()
1541 # Resolve the checkout path and see if it matches this project.
1542 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1543 if fullpath == needle:
1544 platform_utils.rmtree(os.path.join(configs, name))
1545
1546 # Delete the .git directory first, so we're less likely to have a partially
1547 # working git repository around. There shouldn't be any git projects here,
1548 # so rmtree works.
1549
1550 # Try to remove plain files first in case of git worktrees. If this fails
1551 # for any reason, we'll fall back to rmtree, and that'll display errors if
1552 # it can't remove things either.
1553 try:
1554 platform_utils.remove(self.gitdir)
1555 except OSError:
1556 pass
1557 try:
1558 platform_utils.rmtree(self.gitdir)
1559 except OSError as e:
1560 if e.errno != errno.ENOENT:
1561 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1562 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1563 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1564 return False
1565
1566 # Delete everything under the worktree, except for directories that contain
1567 # another git project.
1568 dirs_to_remove = []
1569 failed = False
1570 for root, dirs, files in platform_utils.walk(self.worktree):
1571 for f in files:
1572 path = os.path.join(root, f)
1573 try:
1574 platform_utils.remove(path)
1575 except OSError as e:
1576 if e.errno != errno.ENOENT:
1577 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1578 failed = True
1579 dirs[:] = [d for d in dirs
1580 if not os.path.lexists(os.path.join(root, d, '.git'))]
1581 dirs_to_remove += [os.path.join(root, d) for d in dirs
1582 if os.path.join(root, d) not in dirs_to_remove]
1583 for d in reversed(dirs_to_remove):
1584 if platform_utils.islink(d):
1585 try:
1586 platform_utils.remove(d)
1587 except OSError as e:
1588 if e.errno != errno.ENOENT:
1589 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1590 failed = True
1591 elif not platform_utils.listdir(d):
1592 try:
1593 platform_utils.rmdir(d)
1594 except OSError as e:
1595 if e.errno != errno.ENOENT:
1596 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1597 failed = True
1598 if failed:
1599 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1600 file=sys.stderr)
1601 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1602 return False
1603
1604 # Try deleting parent dirs if they are empty.
1605 path = self.worktree
1606 while path != self.manifest.topdir:
1607 try:
1608 platform_utils.rmdir(path)
1609 except OSError as e:
1610 if e.errno != errno.ENOENT:
1611 break
1612 path = os.path.dirname(path)
1613
1614 return True
1615
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001616# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001617 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001618 """Create a new branch off the manifest's revision.
1619 """
Simran Basib9a1b732015-08-20 12:19:28 -07001620 if not branch_merge:
1621 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001622 head = self.work_git.GetHead()
1623 if head == (R_HEADS + name):
1624 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001625
David Pursehouse8a68ff92012-09-24 12:15:13 +09001626 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001627 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001628 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001629 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001630 capture_stdout=True,
1631 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001632
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001633 branch = self.GetBranch(name)
1634 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001635 branch.merge = branch_merge
1636 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1637 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001638
1639 if revision is None:
1640 revid = self.GetRevisionId(all_refs)
1641 else:
1642 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001643
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001644 if head.startswith(R_HEADS):
1645 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001646 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001647 except KeyError:
1648 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001649 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001650 ref = R_HEADS + name
1651 self.work_git.update_ref(ref, revid)
1652 self.work_git.symbolic_ref(HEAD, ref)
1653 branch.Save()
1654 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001655
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001656 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001657 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001658 capture_stdout=True,
1659 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001660 branch.Save()
1661 return True
1662 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001663
Wink Saville02d79452009-04-10 13:01:24 -07001664 def CheckoutBranch(self, name):
1665 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001666
1667 Args:
1668 name: The name of the branch to checkout.
1669
1670 Returns:
1671 True if the checkout succeeded; False if it didn't; None if the branch
1672 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001673 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001674 rev = R_HEADS + name
1675 head = self.work_git.GetHead()
1676 if head == rev:
1677 # Already on the branch
1678 #
1679 return True
Wink Saville02d79452009-04-10 13:01:24 -07001680
David Pursehouse8a68ff92012-09-24 12:15:13 +09001681 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001682 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001683 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001684 except KeyError:
1685 # Branch does not exist in this project
1686 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001687 return None
Wink Saville02d79452009-04-10 13:01:24 -07001688
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001689 if head.startswith(R_HEADS):
1690 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001691 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001692 except KeyError:
1693 head = None
1694
1695 if head == revid:
1696 # Same revision; just update HEAD to point to the new
1697 # target branch, but otherwise take no other action.
1698 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001699 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1700 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001701 return True
1702
1703 return GitCommand(self,
1704 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001705 capture_stdout=True,
1706 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001707
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001708 def AbandonBranch(self, name):
1709 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001710
1711 Args:
1712 name: The name of the branch to abandon.
1713
1714 Returns:
1715 True if the abandon succeeded; False if it didn't; None if the branch
1716 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001717 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001718 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001719 all_refs = self.bare_ref.all
1720 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001721 # Doesn't exist
1722 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001723
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001724 head = self.work_git.GetHead()
1725 if head == rev:
1726 # We can't destroy the branch while we are sitting
1727 # on it. Switch to a detached HEAD.
1728 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001729 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001730
David Pursehouse8a68ff92012-09-24 12:15:13 +09001731 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001732 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001733 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001734 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001735 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001736
1737 return GitCommand(self,
1738 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001739 capture_stdout=True,
1740 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001741
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001742 def PruneHeads(self):
1743 """Prune any topic branches already merged into upstream.
1744 """
1745 cb = self.CurrentBranch
1746 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001747 left = self._allrefs
1748 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001749 if name.startswith(R_HEADS):
1750 name = name[len(R_HEADS):]
1751 if cb is None or name != cb:
1752 kill.append(name)
1753
Mike Frysingera3794e92021-03-11 23:24:01 -05001754 # Minor optimization: If there's nothing to prune, then don't try to read
1755 # any project state.
1756 if not kill and not cb:
1757 return []
1758
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001759 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001760 if cb is not None \
1761 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001762 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001763 self.work_git.DetachHead(HEAD)
1764 kill.append(cb)
1765
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001767 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769 try:
1770 self.bare_git.DetachHead(rev)
1771
1772 b = ['branch', '-d']
1773 b.extend(kill)
1774 b = GitCommand(self, b, bare=True,
1775 capture_stdout=True,
1776 capture_stderr=True)
1777 b.Wait()
1778 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001779 if ID_RE.match(old):
1780 self.bare_git.DetachHead(old)
1781 else:
1782 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001783 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001784
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001785 for branch in kill:
1786 if (R_HEADS + branch) not in left:
1787 self.CleanPublishedCache()
1788 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001789
1790 if cb and cb not in kill:
1791 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001792 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001793
1794 kept = []
1795 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001796 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001797 branch = self.GetBranch(branch)
1798 base = branch.LocalMerge
1799 if not base:
1800 base = rev
1801 kept.append(ReviewableBranch(self, branch, base))
1802 return kept
1803
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001804# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001805 def GetRegisteredSubprojects(self):
1806 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001807
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001808 def rec(subprojects):
1809 if not subprojects:
1810 return
1811 result.extend(subprojects)
1812 for p in subprojects:
1813 rec(p.subprojects)
1814 rec(self.subprojects)
1815 return result
1816
1817 def _GetSubmodules(self):
1818 # Unfortunately we cannot call `git submodule status --recursive` here
1819 # because the working tree might not exist yet, and it cannot be used
1820 # without a working tree in its current implementation.
1821
1822 def get_submodules(gitdir, rev):
1823 # Parse .gitmodules for submodule sub_paths and sub_urls
1824 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1825 if not sub_paths:
1826 return []
1827 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1828 # revision of submodule repository
1829 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1830 submodules = []
1831 for sub_path, sub_url in zip(sub_paths, sub_urls):
1832 try:
1833 sub_rev = sub_revs[sub_path]
1834 except KeyError:
1835 # Ignore non-exist submodules
1836 continue
1837 submodules.append((sub_rev, sub_path, sub_url))
1838 return submodules
1839
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001840 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1841 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001842
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001843 def parse_gitmodules(gitdir, rev):
1844 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1845 try:
Anthony King7bdac712014-07-16 12:56:40 +01001846 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1847 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001848 except GitError:
1849 return [], []
1850 if p.Wait() != 0:
1851 return [], []
1852
1853 gitmodules_lines = []
1854 fd, temp_gitmodules_path = tempfile.mkstemp()
1855 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001856 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001857 os.close(fd)
1858 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001859 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1860 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001861 if p.Wait() != 0:
1862 return [], []
1863 gitmodules_lines = p.stdout.split('\n')
1864 except GitError:
1865 return [], []
1866 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001867 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001868
1869 names = set()
1870 paths = {}
1871 urls = {}
1872 for line in gitmodules_lines:
1873 if not line:
1874 continue
1875 m = re_path.match(line)
1876 if m:
1877 names.add(m.group(1))
1878 paths[m.group(1)] = m.group(2)
1879 continue
1880 m = re_url.match(line)
1881 if m:
1882 names.add(m.group(1))
1883 urls[m.group(1)] = m.group(2)
1884 continue
1885 names = sorted(names)
1886 return ([paths.get(name, '') for name in names],
1887 [urls.get(name, '') for name in names])
1888
1889 def git_ls_tree(gitdir, rev, paths):
1890 cmd = ['ls-tree', rev, '--']
1891 cmd.extend(paths)
1892 try:
Anthony King7bdac712014-07-16 12:56:40 +01001893 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1894 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001895 except GitError:
1896 return []
1897 if p.Wait() != 0:
1898 return []
1899 objects = {}
1900 for line in p.stdout.split('\n'):
1901 if not line.strip():
1902 continue
1903 object_rev, object_path = line.split()[2:4]
1904 objects[object_path] = object_rev
1905 return objects
1906
1907 try:
1908 rev = self.GetRevisionId()
1909 except GitError:
1910 return []
1911 return get_submodules(self.gitdir, rev)
1912
1913 def GetDerivedSubprojects(self):
1914 result = []
1915 if not self.Exists:
1916 # If git repo does not exist yet, querying its submodules will
1917 # mess up its states; so return here.
1918 return result
1919 for rev, path, url in self._GetSubmodules():
1920 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001921 relpath, worktree, gitdir, objdir = \
1922 self.manifest.GetSubprojectPaths(self, name, path)
1923 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001924 if project:
1925 result.extend(project.GetDerivedSubprojects())
1926 continue
David James8d201162013-10-11 17:03:19 -07001927
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001928 if url.startswith('..'):
1929 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001930 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001931 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001932 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001933 review=self.remote.review,
1934 revision=self.remote.revision)
1935 subproject = Project(manifest=self.manifest,
1936 name=name,
1937 remote=remote,
1938 gitdir=gitdir,
1939 objdir=objdir,
1940 worktree=worktree,
1941 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001942 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001943 revisionId=rev,
1944 rebase=self.rebase,
1945 groups=self.groups,
1946 sync_c=self.sync_c,
1947 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001948 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001949 parent=self,
1950 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001951 result.append(subproject)
1952 result.extend(subproject.GetDerivedSubprojects())
1953 return result
1954
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001955# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001956 def EnableRepositoryExtension(self, key, value='true', version=1):
1957 """Enable git repository extension |key| with |value|.
1958
1959 Args:
1960 key: The extension to enabled. Omit the "extensions." prefix.
1961 value: The value to use for the extension.
1962 version: The minimum git repository version needed.
1963 """
1964 # Make sure the git repo version is new enough already.
1965 found_version = self.config.GetInt('core.repositoryFormatVersion')
1966 if found_version is None:
1967 found_version = 0
1968 if found_version < version:
1969 self.config.SetString('core.repositoryFormatVersion', str(version))
1970
1971 # Enable the extension!
1972 self.config.SetString('extensions.%s' % (key,), value)
1973
Mike Frysinger50a81de2020-09-06 15:51:21 -04001974 def ResolveRemoteHead(self, name=None):
1975 """Find out what the default branch (HEAD) points to.
1976
1977 Normally this points to refs/heads/master, but projects are moving to main.
1978 Support whatever the server uses rather than hardcoding "master" ourselves.
1979 """
1980 if name is None:
1981 name = self.remote.name
1982
1983 # The output will look like (NB: tabs are separators):
1984 # ref: refs/heads/master HEAD
1985 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1986 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1987
1988 for line in output.splitlines():
1989 lhs, rhs = line.split('\t', 1)
1990 if rhs == 'HEAD' and lhs.startswith('ref:'):
1991 return lhs[4:].strip()
1992
1993 return None
1994
Zac Livingstone4332262017-06-16 08:56:09 -06001995 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001996 try:
1997 # if revision (sha or tag) is not present then following function
1998 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08001999 self.bare_git.rev_list('-1', '--missing=allow-any',
2000 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00002001 if self.upstream:
2002 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
2003 self.bare_git.rev_list('-1', '--missing=allow-any',
2004 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002005 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002006 return True
2007 except GitError:
2008 # There is no such persistent revision. We have to fetch it.
2009 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002010
Julien Campergue335f5ef2013-10-16 11:02:35 +02002011 def _FetchArchive(self, tarpath, cwd=None):
2012 cmd = ['archive', '-v', '-o', tarpath]
2013 cmd.append('--remote=%s' % self.remote.url)
2014 cmd.append('--prefix=%s/' % self.relpath)
2015 cmd.append(self.revisionExpr)
2016
2017 command = GitCommand(self, cmd, cwd=cwd,
2018 capture_stdout=True,
2019 capture_stderr=True)
2020
2021 if command.Wait() != 0:
2022 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2023
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002024 def _RemoteFetch(self, name=None,
2025 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002026 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002027 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002028 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002029 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002030 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002031 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002032 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002033 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002034 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002035 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002036 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002037 clone_filter=None,
2038 retry_fetches=2,
2039 retry_sleep_initial_sec=4.0,
2040 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002041 is_sha1 = False
2042 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002043 # The depth should not be used when fetching to a mirror because
2044 # it will result in a shallow repository that cannot be cloned or
2045 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002046 # The repo project should also never be synced with partial depth.
2047 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2048 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002049
Shawn Pearce69e04d82014-01-29 12:48:54 -08002050 if depth:
2051 current_branch_only = True
2052
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002053 if ID_RE.match(self.revisionExpr) is not None:
2054 is_sha1 = True
2055
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002056 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002057 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002058 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002059 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002060 elif self.upstream and self.upstream.startswith(R_TAGS):
2061 # This is a tag and its commit id should never change.
2062 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002063
2064 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002065 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002066 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002067 print('Skipped fetching project %s (already have persistent ref)'
2068 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002069 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002070 if is_sha1 and not depth:
2071 # When syncing a specific commit and --depth is not set:
2072 # * if upstream is explicitly specified and is not a sha1, fetch only
2073 # upstream as users expect only upstream to be fetch.
2074 # Note: The commit might not be in upstream in which case the sync
2075 # will fail.
2076 # * otherwise, fetch all branches to make sure we end up with the
2077 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002078 if self.upstream:
2079 current_branch_only = not ID_RE.match(self.upstream)
2080 else:
2081 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002082
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002083 if not name:
2084 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002085
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002086 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002087 if not remote.PreConnectFetch(ssh_proxy):
2088 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002089
Shawn O. Pearce88443382010-10-08 10:02:09 +02002090 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002091 if alt_dir and 'objects' == os.path.basename(alt_dir):
2092 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002093 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002094
David Pursehouse8a68ff92012-09-24 12:15:13 +09002095 all_refs = self.bare_ref.all
2096 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002097 tmp = set()
2098
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302099 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002100 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002101 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002102 all_refs[r] = ref_id
2103 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002104 continue
2105
David Pursehouse8a68ff92012-09-24 12:15:13 +09002106 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002107 continue
2108
David Pursehouse8a68ff92012-09-24 12:15:13 +09002109 r = 'refs/_alt/%s' % ref_id
2110 all_refs[r] = ref_id
2111 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002112 tmp.add(r)
2113
heping3d7bbc92017-04-12 19:51:47 +08002114 tmp_packed_lines = []
2115 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002116
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302117 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002118 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002119 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002120 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002121 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002122
heping3d7bbc92017-04-12 19:51:47 +08002123 tmp_packed = ''.join(tmp_packed_lines)
2124 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002125 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002126 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002127 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002128
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002129 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002130
Xin Li745be2e2019-06-03 11:24:30 -07002131 if clone_filter:
2132 git_require((2, 19, 0), fail=True, msg='partial clones')
2133 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002134 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002135
Conley Owensf97e8382015-01-21 11:12:46 -08002136 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002137 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002138 else:
2139 # If this repo has shallow objects, then we don't know which refs have
2140 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2141 # do this with projects that don't have shallow objects, since it is less
2142 # efficient.
2143 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2144 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002145
Mike Frysinger4847e052020-02-22 00:07:35 -05002146 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002147 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002148 if not quiet and sys.stdout.isatty():
2149 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002150 if not self.worktree:
2151 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002152 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002153
Mike Frysingere57f1142019-03-18 21:27:54 -04002154 if force_sync:
2155 cmd.append('--force')
2156
David Pursehouse74cfd272015-10-14 10:50:15 +09002157 if prune:
2158 cmd.append('--prune')
2159
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002160 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002161
Kuang-che Wu6856f982019-11-25 12:37:55 +08002162 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002163 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002164 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002165 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002166 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002167 spec.append('tag')
2168 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002169
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302170 if self.manifest.IsMirror and not current_branch_only:
2171 branch = None
2172 else:
2173 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002174 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002175 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002176 # Shallow checkout of a specific commit, fetch from that commit and not
2177 # the heads only as the commit might be deeper in the history.
2178 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002179 if self.upstream:
2180 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002181 else:
2182 if is_sha1:
2183 branch = self.upstream
2184 if branch is not None and branch.strip():
2185 if not branch.startswith('refs/'):
2186 branch = R_HEADS + branch
2187 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2188
2189 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2190 # whole repo.
2191 if self.manifest.IsMirror and not spec:
2192 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2193
2194 # If using depth then we should not get all the tags since they may
2195 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002196 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002197 cmd.append('--no-tags')
2198 else:
2199 cmd.append('--tags')
2200 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2201
Conley Owens80b87fe2014-05-09 17:13:44 -07002202 cmd.extend(spec)
2203
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002204 # At least one retry minimum due to git remote prune.
2205 retry_fetches = max(retry_fetches, 2)
2206 retry_cur_sleep = retry_sleep_initial_sec
2207 ok = prune_tried = False
2208 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002209 gitcmd = GitCommand(
2210 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2211 ssh_proxy=ssh_proxy,
2212 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002213 if gitcmd.stdout and not quiet and output_redir:
2214 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002215 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002216 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002217 ok = True
2218 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002219
2220 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002221 elif (gitcmd.stdout and
2222 'error:' in gitcmd.stdout and
2223 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002224 # Fallthru to sleep+retry logic at the bottom.
2225 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002226
Mike Frysinger6823bc22021-04-15 02:06:28 -04002227 # Try to prune remote branches once in case there are conflicts.
2228 # For example, if the remote had refs/heads/upstream, but deleted that and
2229 # now has refs/heads/upstream/foo.
2230 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002231 'error:' in gitcmd.stdout and
2232 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002233 not prune_tried):
2234 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002235 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002236 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002237 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002238 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002239 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002240 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002241 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002242 continue
Brian Harring14a66742012-09-28 20:21:57 -07002243 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002244 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2245 # in sha1 mode, we just tried sync'ing from the upstream field; it
2246 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002247 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002248 elif ret < 0:
2249 # Git died with a signal, exit immediately
2250 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002251
2252 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002253 if not verbose and gitcmd.stdout:
2254 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002255 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002256 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2257 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002258 time.sleep(retry_cur_sleep)
2259 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2260 MAXIMUM_RETRY_SLEEP_SEC)
2261 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2262 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002263
2264 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002265 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002266 if old_packed != '':
2267 _lwrite(packed_refs, old_packed)
2268 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002269 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002270 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002271
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002272 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002273 # We just synced the upstream given branch; verify we
2274 # got what we wanted, else trigger a second run of all
2275 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002276 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002277 # Sync the current branch only with depth set to None.
2278 # We always pass depth=None down to avoid infinite recursion.
2279 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002280 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002281 current_branch_only=current_branch_only and depth,
2282 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002283 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002284
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002285 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002286
Mike Frysingere50b6a72020-02-19 01:45:48 -05002287 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002288 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002289 return False
2290
2291 remote = self.GetRemote(self.remote.name)
2292 bundle_url = remote.url + '/clone.bundle'
2293 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002294 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2295 'persistent-http',
2296 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002297 return False
2298
2299 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2300 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2301
2302 exist_dst = os.path.exists(bundle_dst)
2303 exist_tmp = os.path.exists(bundle_tmp)
2304
2305 if not initial and not exist_dst and not exist_tmp:
2306 return False
2307
2308 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002309 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2310 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002311 if not exist_dst:
2312 return False
2313
2314 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002315 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002316 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002317 if not quiet and sys.stdout.isatty():
2318 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002319 if not self.worktree:
2320 cmd.append('--update-head-ok')
2321 cmd.append(bundle_dst)
2322 for f in remote.fetch:
2323 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002324 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002325
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002326 ok = GitCommand(
2327 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002328 platform_utils.remove(bundle_dst, missing_ok=True)
2329 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002330 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002331
Mike Frysingere50b6a72020-02-19 01:45:48 -05002332 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002333 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002334
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002335 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002336 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002337 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002338 if os.path.exists(tmpPath):
2339 size = os.stat(tmpPath).st_size
2340 if size >= 1024:
2341 cmd += ['--continue-at', '%d' % (size,)]
2342 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002343 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002344 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002345 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002346 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002347 if proxy:
2348 cmd += ['--proxy', proxy]
2349 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2350 cmd += ['--proxy', os.environ['http_proxy']]
2351 if srcUrl.startswith('persistent-https'):
2352 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2353 elif srcUrl.startswith('persistent-http'):
2354 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002355 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002356
Dave Borowitz137d0132015-01-02 11:12:54 -08002357 if IsTrace():
2358 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002359 if verbose:
2360 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2361 stdout = None if verbose else subprocess.PIPE
2362 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002363 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002364 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002365 except OSError:
2366 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002367
Mike Frysingere50b6a72020-02-19 01:45:48 -05002368 (output, _) = proc.communicate()
2369 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002370
Dave Borowitz137d0132015-01-02 11:12:54 -08002371 if curlret == 22:
2372 # From curl man page:
2373 # 22: HTTP page not retrieved. The requested url was not found or
2374 # returned another error with the HTTP error code being 400 or above.
2375 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002376 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002377 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2378 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002379 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002380 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002381 elif curlret and not verbose and output:
2382 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002383
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002384 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002385 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002386 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002387 return True
2388 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002389 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002390 return False
2391 else:
2392 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002393
Kris Giesingc8d882a2014-12-23 13:02:32 -08002394 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002395 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002396 with open(path, 'rb') as f:
2397 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002398 return True
2399 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002400 if not quiet:
2401 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002402 return False
2403 except OSError:
2404 return False
2405
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002406 def _Checkout(self, rev, quiet=False):
2407 cmd = ['checkout']
2408 if quiet:
2409 cmd.append('-q')
2410 cmd.append(rev)
2411 cmd.append('--')
2412 if GitCommand(self, cmd).Wait() != 0:
2413 if self._allrefs:
2414 raise GitError('%s checkout %s ' % (self.name, rev))
2415
Mike Frysinger915fda12020-03-22 12:15:20 -04002416 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002417 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002418 if ffonly:
2419 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002420 if record_origin:
2421 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002422 cmd.append(rev)
2423 cmd.append('--')
2424 if GitCommand(self, cmd).Wait() != 0:
2425 if self._allrefs:
2426 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2427
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302428 def _LsRemote(self, refs):
2429 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302430 p = GitCommand(self, cmd, capture_stdout=True)
2431 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002432 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302433 return None
2434
Anthony King7bdac712014-07-16 12:56:40 +01002435 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002436 cmd = ['revert']
2437 cmd.append('--no-edit')
2438 cmd.append(rev)
2439 cmd.append('--')
2440 if GitCommand(self, cmd).Wait() != 0:
2441 if self._allrefs:
2442 raise GitError('%s revert %s ' % (self.name, rev))
2443
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002444 def _ResetHard(self, rev, quiet=True):
2445 cmd = ['reset', '--hard']
2446 if quiet:
2447 cmd.append('-q')
2448 cmd.append(rev)
2449 if GitCommand(self, cmd).Wait() != 0:
2450 raise GitError('%s reset --hard %s ' % (self.name, rev))
2451
Martin Kellye4e94d22017-03-21 16:05:12 -07002452 def _SyncSubmodules(self, quiet=True):
2453 cmd = ['submodule', 'update', '--init', '--recursive']
2454 if quiet:
2455 cmd.append('-q')
2456 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002457 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002458
Anthony King7bdac712014-07-16 12:56:40 +01002459 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002460 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002461 if onto is not None:
2462 cmd.extend(['--onto', onto])
2463 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002464 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002465 raise GitError('%s rebase %s ' % (self.name, upstream))
2466
Pierre Tardy3d125942012-05-04 12:18:12 +02002467 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002468 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002469 if ffonly:
2470 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002471 if GitCommand(self, cmd).Wait() != 0:
2472 raise GitError('%s merge %s ' % (self.name, head))
2473
David Pursehousee8ace262020-02-13 12:41:15 +09002474 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002475 init_git_dir = not os.path.exists(self.gitdir)
2476 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002477 try:
2478 # Initialize the bare repository, which contains all of the objects.
2479 if init_obj_dir:
2480 os.makedirs(self.objdir)
2481 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002482
Mike Frysinger333c0a42021-11-15 12:39:00 -05002483 self._UpdateHooks(quiet=quiet)
2484
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002485 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002486 # Enable per-worktree config file support if possible. This is more a
2487 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002488 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002489 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002490
Kevin Degib1a07b82015-07-27 13:33:43 -06002491 # If we have a separate directory to hold refs, initialize it as well.
2492 if self.objdir != self.gitdir:
2493 if init_git_dir:
2494 os.makedirs(self.gitdir)
2495
2496 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002497 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002498 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002499 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002500 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002501 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002502 print("Retrying clone after deleting %s" %
2503 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002504 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002505 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2506 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002507 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002508 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002509 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2510 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002511 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002512 raise e
2513 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002514
Kevin Degi384b3c52014-10-16 16:02:58 -06002515 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002516 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002517 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002518
LaMont Jonescc879a92021-11-18 22:40:18 +00002519 def _expanded_ref_dirs():
2520 """Iterate through the possible git reference directory paths."""
2521 name = self.name + '.git'
2522 yield mirror_git or os.path.join(ref_dir, name)
2523 for prefix in '', self.remote.name:
2524 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2525 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002526
LaMont Jonescc879a92021-11-18 22:40:18 +00002527 if ref_dir or mirror_git:
2528 found_ref_dir = None
2529 for path in _expanded_ref_dirs():
2530 if os.path.exists(path):
2531 found_ref_dir = path
2532 break
2533 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002534
Kevin Degib1a07b82015-07-27 13:33:43 -06002535 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002536 if not os.path.isabs(ref_dir):
2537 # The alternate directory is relative to the object database.
2538 ref_dir = os.path.relpath(ref_dir,
2539 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002540 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002541 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002542
Kevin Degib1a07b82015-07-27 13:33:43 -06002543 m = self.manifest.manifestProject.config
2544 for key in ['user.name', 'user.email']:
2545 if m.Has(key, include_defaults=False):
2546 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002547 if not self.manifest.EnableGitLfs:
2548 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2549 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002550 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002551 except Exception:
2552 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002553 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002554 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002555 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002556 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002557
David Pursehousee8ace262020-02-13 12:41:15 +09002558 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002559 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002560 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002561
David Pursehousee8ace262020-02-13 12:41:15 +09002562 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002563 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002564 if not os.path.exists(hooks):
2565 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002566
2567 # Delete sample hooks. They're noise.
2568 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002569 try:
2570 platform_utils.remove(hook, missing_ok=True)
2571 except PermissionError:
2572 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002573
Jonathan Nieder93719792015-03-17 11:29:58 -07002574 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002575 name = os.path.basename(stock_hook)
2576
Victor Boivie65e0f352011-04-18 11:23:29 +02002577 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002578 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002579 # Don't install a Gerrit Code Review hook if this
2580 # project does not appear to use it for reviews.
2581 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002582 # Since the manifest project is one of those, but also
2583 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002584 continue
2585
2586 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002587 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002588 continue
2589 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002590 # If the files are the same, we'll leave it alone. We create symlinks
2591 # below by default but fallback to hardlinks if the OS blocks them.
2592 # So if we're here, it's probably because we made a hardlink below.
2593 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002594 if not quiet:
2595 _warn("%s: Not replacing locally modified %s hook",
2596 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002597 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002598 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002599 platform_utils.symlink(
2600 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002601 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002602 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002603 try:
2604 os.link(stock_hook, dst)
2605 except OSError:
2606 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002607 else:
2608 raise
2609
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002610 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002611 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002612 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002613 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002614 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002615 remote.review = self.remote.review
2616 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002617
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002618 if self.worktree:
2619 remote.ResetFetch(mirror=False)
2620 else:
2621 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002622 remote.Save()
2623
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002624 def _InitMRef(self):
2625 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002626 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002627 # Set up the m/ space to point to the worktree-specific ref space.
2628 # We'll update the worktree-specific ref space on each checkout.
2629 ref = R_M + self.manifest.branch
2630 if not self.bare_ref.symref(ref):
2631 self.bare_git.symbolic_ref(
2632 '-m', 'redirecting to worktree scope',
2633 ref, R_WORKTREE_M + self.manifest.branch)
2634
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002635 # We can't update this ref with git worktrees until it exists.
2636 # We'll wait until the initial checkout to set it.
2637 if not os.path.exists(self.worktree):
2638 return
2639
2640 base = R_WORKTREE_M
2641 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002642
2643 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002644 else:
2645 base = R_M
2646 active_git = self.bare_git
2647
2648 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002649
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002650 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002651 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002652
Remy Böhmer1469c282020-12-15 18:49:02 +01002653 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002654 cur = self.bare_ref.symref(ref)
2655
2656 if self.revisionId:
2657 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2658 msg = 'manifest set to %s' % self.revisionId
2659 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002660 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002661 else:
2662 remote = self.GetRemote(self.remote.name)
2663 dst = remote.ToLocal(self.revisionExpr)
2664 if cur != dst:
2665 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002666 if detach:
2667 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2668 else:
2669 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002670
Mike Frysingerc72bd842021-11-14 03:58:00 -05002671 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002672 # Git worktrees don't use symlinks to share at all.
2673 if self.use_git_worktrees:
2674 return
2675
Mike Frysingerd33dce02021-12-20 18:16:33 -05002676 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002677 # Try to self-heal a bit in simple cases.
2678 dst_path = os.path.join(destdir, name)
2679 src_path = os.path.join(srcdir, name)
2680
Mike Frysingered4f2112020-02-11 23:06:29 -05002681 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002682 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002683 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002684 # Fail if the links are pointing to the wrong place
2685 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002686 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002687 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002688 'work tree. If you\'re comfortable with the '
2689 'possibility of losing the work tree\'s git metadata,'
2690 ' use `repo sync --force-sync {0}` to '
2691 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002692
Mike Frysingerc72bd842021-11-14 03:58:00 -05002693 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002694 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2695
2696 Args:
2697 gitdir: The bare git repository. Must already be initialized.
2698 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002699 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2700 This saves you the effort of initializing |dotgit| yourself.
2701 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002702 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002703 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002704
2705 to_copy = []
2706 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002707 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002708
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002709 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002710 for name in set(to_copy).union(to_symlink):
2711 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002712 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002713 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002714
Kevin Degi384b3c52014-10-16 16:02:58 -06002715 if os.path.lexists(dst):
2716 continue
David James8d201162013-10-11 17:03:19 -07002717
2718 # If the source dir doesn't exist, create an empty dir.
2719 if name in symlink_dirs and not os.path.lexists(src):
2720 os.makedirs(src)
2721
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002722 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002723 platform_utils.symlink(
2724 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002725 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002726 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002727 shutil.copytree(src, dst)
2728 elif os.path.isfile(src):
2729 shutil.copy(src, dst)
2730
David James8d201162013-10-11 17:03:19 -07002731 except OSError as e:
2732 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002733 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002734 else:
2735 raise
2736
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002737 def _InitGitWorktree(self):
2738 """Init the project using git worktrees."""
2739 self.bare_git.worktree('prune')
2740 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2741 self.worktree, self.GetRevisionId())
2742
2743 # Rewrite the internal state files to use relative paths between the
2744 # checkouts & worktrees.
2745 dotgit = os.path.join(self.worktree, '.git')
2746 with open(dotgit, 'r') as fp:
2747 # Figure out the checkout->worktree path.
2748 setting = fp.read()
2749 assert setting.startswith('gitdir:')
2750 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002751 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2752 # of file permissions. Delete it and recreate it from scratch to avoid.
2753 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002754 # Use relative path from checkout->worktree & maintain Unix line endings
2755 # on all OS's to match git behavior.
2756 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002757 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2758 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002759 # Use relative path from worktree->checkout & maintain Unix line endings
2760 # on all OS's to match git behavior.
2761 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002762 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2763
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002764 self._InitMRef()
2765
Martin Kellye4e94d22017-03-21 16:05:12 -07002766 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002767 """Setup the worktree .git path.
2768
2769 This is the user-visible path like src/foo/.git/.
2770
2771 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2772 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2773
2774 Older checkouts had .git/ directories. If we see that, migrate it.
2775
2776 This also handles changes in the manifest. Maybe this project was backed
2777 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2778 the path we point to under .repo/projects/ to match.
2779 """
2780 dotgit = os.path.join(self.worktree, '.git')
2781
2782 # If using an old layout style (a directory), migrate it.
2783 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2784 self._MigrateOldWorkTreeGitDir(dotgit)
2785
2786 init_dotgit = not os.path.exists(dotgit)
2787 if self.use_git_worktrees:
2788 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002789 self._InitGitWorktree()
2790 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002791 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002792 if not init_dotgit:
2793 # See if the project has changed.
2794 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2795 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002796
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002797 if init_dotgit or not os.path.exists(dotgit):
2798 os.makedirs(self.worktree, exist_ok=True)
2799 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002800
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002801 if init_dotgit:
2802 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002803
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002804 # Finish checking out the worktree.
2805 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2806 if GitCommand(self, cmd).Wait() != 0:
2807 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002808
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002809 if submodules:
2810 self._SyncSubmodules(quiet=True)
2811 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002812
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002813 @classmethod
2814 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2815 """Migrate the old worktree .git/ dir style to a symlink.
2816
2817 This logic specifically only uses state from |dotgit| to figure out where to
2818 move content and not |self|. This way if the backing project also changed
2819 places, we only do the .git/ dir to .git symlink migration here. The path
2820 updates will happen independently.
2821 """
2822 # Figure out where in .repo/projects/ it's pointing to.
2823 if not os.path.islink(os.path.join(dotgit, 'refs')):
2824 raise GitError(f'{dotgit}: unsupported checkout state')
2825 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2826
2827 # Remove known symlink paths that exist in .repo/projects/.
2828 KNOWN_LINKS = {
2829 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2830 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2831 }
2832 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2833 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002834 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2835 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002836 }
2837
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002838 # First see if we'd succeed before starting the migration.
2839 unknown_paths = []
2840 for name in platform_utils.listdir(dotgit):
2841 # Ignore all temporary/backup names. These are common with vim & emacs.
2842 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2843 continue
2844
2845 dotgit_path = os.path.join(dotgit, name)
2846 if name in KNOWN_LINKS:
2847 if not platform_utils.islink(dotgit_path):
2848 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2849 else:
2850 gitdir_path = os.path.join(gitdir, name)
2851 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2852 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2853 if unknown_paths:
2854 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2855
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002856 # Now walk the paths and sync the .git/ to .repo/projects/.
2857 for name in platform_utils.listdir(dotgit):
2858 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002859
2860 # Ignore all temporary/backup names. These are common with vim & emacs.
2861 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2862 platform_utils.remove(dotgit_path)
2863 elif name in KNOWN_LINKS:
2864 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002865 else:
2866 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002867 platform_utils.remove(gitdir_path, missing_ok=True)
2868 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002869
2870 # Now that the dir should be empty, clear it out, and symlink it over.
2871 platform_utils.rmdir(dotgit)
2872 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002873
Renaud Paquay788e9622017-01-27 11:41:12 -08002874 def _get_symlink_error_message(self):
2875 if platform_utils.isWindows():
2876 return ('Unable to create symbolic link. Please re-run the command as '
2877 'Administrator, or see '
2878 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2879 'for other options.')
2880 return 'filesystem must support symlinks'
2881
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002882 def _revlist(self, *args, **kw):
2883 a = []
2884 a.extend(args)
2885 a.append('--')
2886 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002887
2888 @property
2889 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002890 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002891
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002892 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002893 """Get logs between two revisions of this project."""
2894 comp = '..'
2895 if rev1:
2896 revs = [rev1]
2897 if rev2:
2898 revs.extend([comp, rev2])
2899 cmd = ['log', ''.join(revs)]
2900 out = DiffColoring(self.config)
2901 if out.is_on and color:
2902 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002903 if pretty_format is not None:
2904 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002905 if oneline:
2906 cmd.append('--oneline')
2907
2908 try:
2909 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2910 if log.Wait() == 0:
2911 return log.stdout
2912 except GitError:
2913 # worktree may not exist if groups changed for example. In that case,
2914 # try in gitdir instead.
2915 if not os.path.exists(self.worktree):
2916 return self.bare_git.log(*cmd[1:])
2917 else:
2918 raise
2919 return None
2920
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002921 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2922 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002923 """Get the list of logs from this revision to given revisionId"""
2924 logs = {}
2925 selfId = self.GetRevisionId(self._allrefs)
2926 toId = toProject.GetRevisionId(toProject._allrefs)
2927
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002928 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2929 pretty_format=pretty_format)
2930 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2931 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002932 return logs
2933
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002934 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002935
David James8d201162013-10-11 17:03:19 -07002936 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002937 self._project = project
2938 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002939 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002940
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002941 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2942 def __getstate__(self):
2943 return (self._project, self._bare, self._gitdir)
2944
2945 def __setstate__(self, state):
2946 self._project, self._bare, self._gitdir = state
2947
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002948 def LsOthers(self):
2949 p = GitCommand(self._project,
2950 ['ls-files',
2951 '-z',
2952 '--others',
2953 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002954 bare=False,
David James8d201162013-10-11 17:03:19 -07002955 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002956 capture_stdout=True,
2957 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002958 if p.Wait() == 0:
2959 out = p.stdout
2960 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002961 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002962 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002963 return []
2964
2965 def DiffZ(self, name, *args):
2966 cmd = [name]
2967 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002968 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002969 cmd.extend(args)
2970 p = GitCommand(self._project,
2971 cmd,
David James8d201162013-10-11 17:03:19 -07002972 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002973 bare=False,
2974 capture_stdout=True,
2975 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002976 p.Wait()
2977 r = {}
2978 out = p.stdout
2979 if out:
2980 out = iter(out[:-1].split('\0'))
2981 while out:
2982 try:
2983 info = next(out)
2984 path = next(out)
2985 except StopIteration:
2986 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002987
Mike Frysinger84230002021-02-16 17:08:35 -05002988 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002989
Mike Frysinger84230002021-02-16 17:08:35 -05002990 def __init__(self, path, omode, nmode, oid, nid, state):
2991 self.path = path
2992 self.src_path = None
2993 self.old_mode = omode
2994 self.new_mode = nmode
2995 self.old_id = oid
2996 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002997
Mike Frysinger84230002021-02-16 17:08:35 -05002998 if len(state) == 1:
2999 self.status = state
3000 self.level = None
3001 else:
3002 self.status = state[:1]
3003 self.level = state[1:]
3004 while self.level.startswith('0'):
3005 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003006
Mike Frysinger84230002021-02-16 17:08:35 -05003007 info = info[1:].split(' ')
3008 info = _Info(path, *info)
3009 if info.status in ('R', 'C'):
3010 info.src_path = info.path
3011 info.path = next(out)
3012 r[info.path] = info
3013 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003014
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003015 def GetDotgitPath(self, subpath=None):
3016 """Return the full path to the .git dir.
3017
3018 As a convenience, append |subpath| if provided.
3019 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003020 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003021 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003022 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003023 dotgit = os.path.join(self._project.worktree, '.git')
3024 if os.path.isfile(dotgit):
3025 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3026 with open(dotgit) as fp:
3027 setting = fp.read()
3028 assert setting.startswith('gitdir:')
3029 gitdir = setting.split(':', 1)[1].strip()
3030 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3031
3032 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3033
3034 def GetHead(self):
3035 """Return the ref that HEAD points to."""
3036 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003037 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003038 with open(path) as fd:
3039 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003040 except IOError as e:
3041 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003042 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303043 line = line.decode()
3044 except AttributeError:
3045 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003046 if line.startswith('ref: '):
3047 return line[5:-1]
3048 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003049
3050 def SetHead(self, ref, message=None):
3051 cmdv = []
3052 if message is not None:
3053 cmdv.extend(['-m', message])
3054 cmdv.append(HEAD)
3055 cmdv.append(ref)
3056 self.symbolic_ref(*cmdv)
3057
3058 def DetachHead(self, new, message=None):
3059 cmdv = ['--no-deref']
3060 if message is not None:
3061 cmdv.extend(['-m', message])
3062 cmdv.append(HEAD)
3063 cmdv.append(new)
3064 self.update_ref(*cmdv)
3065
3066 def UpdateRef(self, name, new, old=None,
3067 message=None,
3068 detach=False):
3069 cmdv = []
3070 if message is not None:
3071 cmdv.extend(['-m', message])
3072 if detach:
3073 cmdv.append('--no-deref')
3074 cmdv.append(name)
3075 cmdv.append(new)
3076 if old is not None:
3077 cmdv.append(old)
3078 self.update_ref(*cmdv)
3079
3080 def DeleteRef(self, name, old=None):
3081 if not old:
3082 old = self.rev_parse(name)
3083 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003084 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003085
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003086 def rev_list(self, *args, **kw):
3087 if 'format' in kw:
3088 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3089 else:
3090 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003091 cmdv.extend(args)
3092 p = GitCommand(self._project,
3093 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003094 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003095 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003096 capture_stdout=True,
3097 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003098 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003099 raise GitError('%s rev-list %s: %s' %
3100 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003101 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003102
3103 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003104 """Allow arbitrary git commands using pythonic syntax.
3105
3106 This allows you to do things like:
3107 git_obj.rev_parse('HEAD')
3108
3109 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3110 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003111 Any other positional arguments will be passed to the git command, and the
3112 following keyword arguments are supported:
3113 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003114
3115 Args:
3116 name: The name of the git command to call. Any '_' characters will
3117 be replaced with '-'.
3118
3119 Returns:
3120 A callable object that will try to call git with the named command.
3121 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003122 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003123
Dave Borowitz091f8932012-10-23 17:01:04 -07003124 def runner(*args, **kwargs):
3125 cmdv = []
3126 config = kwargs.pop('config', None)
3127 for k in kwargs:
3128 raise TypeError('%s() got an unexpected keyword argument %r'
3129 % (name, k))
3130 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303131 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003132 cmdv.append('-c')
3133 cmdv.append('%s=%s' % (k, v))
3134 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003135 cmdv.extend(args)
3136 p = GitCommand(self._project,
3137 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003138 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003139 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003140 capture_stdout=True,
3141 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003142 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003143 raise GitError('%s %s: %s' %
3144 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003145 r = p.stdout
3146 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3147 return r[:-1]
3148 return r
3149 return runner
3150
3151
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003152class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003153
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003154 def __str__(self):
3155 return 'prior sync failed; rebase still in progress'
3156
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003157
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003158class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003159
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003160 def __str__(self):
3161 return 'contains uncommitted changes'
3162
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003163
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003164class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003165
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003166 def __init__(self, project, text):
3167 self.project = project
3168 self.text = text
3169
3170 def Print(self, syncbuf):
3171 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3172 syncbuf.out.nl()
3173
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003174
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003175class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003176
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003177 def __init__(self, project, why):
3178 self.project = project
3179 self.why = why
3180
3181 def Print(self, syncbuf):
3182 syncbuf.out.fail('error: %s/: %s',
3183 self.project.relpath,
3184 str(self.why))
3185 syncbuf.out.nl()
3186
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003187
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003188class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003189
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003190 def __init__(self, project, action):
3191 self.project = project
3192 self.action = action
3193
3194 def Run(self, syncbuf):
3195 out = syncbuf.out
3196 out.project('project %s/', self.project.relpath)
3197 out.nl()
3198 try:
3199 self.action()
3200 out.nl()
3201 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003202 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003203 out.nl()
3204 return False
3205
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003206
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003207class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003208
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003209 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003210 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003211 self.project = self.printer('header', attr='bold')
3212 self.info = self.printer('info')
3213 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003214
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003215
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003216class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003217
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003218 def __init__(self, config, detach_head=False):
3219 self._messages = []
3220 self._failures = []
3221 self._later_queue1 = []
3222 self._later_queue2 = []
3223
3224 self.out = _SyncColoring(config)
3225 self.out.redirect(sys.stderr)
3226
3227 self.detach_head = detach_head
3228 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003229 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003230
3231 def info(self, project, fmt, *args):
3232 self._messages.append(_InfoMessage(project, fmt % args))
3233
3234 def fail(self, project, err=None):
3235 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003236 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003237
3238 def later1(self, project, what):
3239 self._later_queue1.append(_Later(project, what))
3240
3241 def later2(self, project, what):
3242 self._later_queue2.append(_Later(project, what))
3243
3244 def Finish(self):
3245 self._PrintMessages()
3246 self._RunLater()
3247 self._PrintMessages()
3248 return self.clean
3249
David Rileye0684ad2017-04-05 00:02:59 -07003250 def Recently(self):
3251 recent_clean = self.recent_clean
3252 self.recent_clean = True
3253 return recent_clean
3254
3255 def _MarkUnclean(self):
3256 self.clean = False
3257 self.recent_clean = False
3258
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003259 def _RunLater(self):
3260 for q in ['_later_queue1', '_later_queue2']:
3261 if not self._RunQueue(q):
3262 return
3263
3264 def _RunQueue(self, queue):
3265 for m in getattr(self, queue):
3266 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003267 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003268 return False
3269 setattr(self, queue, [])
3270 return True
3271
3272 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003273 if self._messages or self._failures:
3274 if os.isatty(2):
3275 self.out.write(progress.CSI_ERASE_LINE)
3276 self.out.write('\r')
3277
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003278 for m in self._messages:
3279 m.Print(self)
3280 for m in self._failures:
3281 m.Print(self)
3282
3283 self._messages = []
3284 self._failures = []
3285
3286
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003287class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003288 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003289
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003290 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003291 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003292 manifest=manifest,
3293 name=name,
3294 gitdir=gitdir,
3295 objdir=gitdir,
3296 worktree=worktree,
3297 remote=RemoteSpec('origin'),
3298 relpath='.repo/%s' % name,
3299 revisionExpr='refs/heads/master',
3300 revisionId=None,
3301 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003302
3303 def PreSync(self):
3304 if self.Exists:
3305 cb = self.CurrentBranch
3306 if cb:
3307 base = self.GetBranch(cb).merge
3308 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003309 self.revisionExpr = base
3310 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003311
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003312 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003313 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003314 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003315 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003316 return False
3317
David Pursehouse8a68ff92012-09-24 12:15:13 +09003318 all_refs = self.bare_ref.all
3319 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003320 head = self.work_git.GetHead()
3321 if head.startswith(R_HEADS):
3322 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003323 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003324 except KeyError:
3325 head = None
3326
3327 if revid == head:
3328 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003329 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003330 return True
3331 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003332
3333
3334class RepoProject(MetaProject):
3335 """The MetaProject for repo itself."""
3336
3337 @property
3338 def LastFetch(self):
3339 try:
3340 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3341 return os.path.getmtime(fh)
3342 except OSError:
3343 return 0
3344
3345class ManifestProject(MetaProject):
3346 """The MetaProject for manifests."""
3347
3348 def MetaBranchSwitch(self, submodules=False):
3349 """Prepare for manifest branch switch."""
3350
3351 # detach and delete manifest branch, allowing a new
3352 # branch to take over
3353 syncbuf = SyncBuffer(self.config, detach_head=True)
3354 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3355 syncbuf.Finish()
3356
3357 return GitCommand(self,
3358 ['update-ref', '-d', 'refs/heads/default'],
3359 capture_stdout=True,
3360 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003361
3362 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003363 def standalone_manifest_url(self):
3364 """The URL of the standalone manifest, or None."""
LaMont Jones55ee3042022-04-06 17:10:21 +00003365 return self.config.GetString('manifest.standalone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003366
3367 @property
3368 def manifest_groups(self):
3369 """The manifest groups string."""
3370 return self.config.GetString('manifest.groups')
3371
3372 @property
3373 def reference(self):
3374 """The --reference for this manifest."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003375 return self.config.GetString('repo.reference')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003376
3377 @property
3378 def dissociate(self):
3379 """Whether to dissociate."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003380 return self.config.GetBoolean('repo.dissociate')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003381
3382 @property
3383 def archive(self):
3384 """Whether we use archive."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003385 return self.config.GetBoolean('repo.archive')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003386
3387 @property
3388 def mirror(self):
3389 """Whether we use mirror."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003390 return self.config.GetBoolean('repo.mirror')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003391
3392 @property
3393 def use_worktree(self):
3394 """Whether we use worktree."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003395 return self.config.GetBoolean('repo.worktree')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003396
3397 @property
3398 def clone_bundle(self):
3399 """Whether we use clone_bundle."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003400 return self.config.GetBoolean('repo.clonebundle')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003401
3402 @property
3403 def submodules(self):
3404 """Whether we use submodules."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003405 return self.config.GetBoolean('repo.submodules')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003406
3407 @property
3408 def git_lfs(self):
3409 """Whether we use git_lfs."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003410 return self.config.GetBoolean('repo.git-lfs')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003411
3412 @property
3413 def use_superproject(self):
3414 """Whether we use superproject."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003415 return self.config.GetBoolean('repo.superproject')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003416
3417 @property
3418 def partial_clone(self):
3419 """Whether this is a partial clone."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003420 return self.config.GetBoolean('repo.partialclone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003421
3422 @property
3423 def depth(self):
3424 """Partial clone depth."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003425 return self.config.GetString('repo.depth')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003426
3427 @property
3428 def clone_filter(self):
3429 """The clone filter."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003430 return self.config.GetString('repo.clonefilter')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003431
3432 @property
3433 def partial_clone_exclude(self):
3434 """Partial clone exclude string"""
LaMont Jones4ada0432022-04-14 15:10:43 +00003435 return self.config.GetBoolean('repo.partialcloneexclude')
3436
3437 @property
3438 def manifest_platform(self):
3439 """The --platform argument from `repo init`."""
3440 return self.config.GetString('manifest.platform')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003441
3442 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003443 def _platform_name(self):
3444 """Return the name of the platform."""
3445 return platform.system().lower()
3446
3447 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003448 standalone_manifest=False, groups='', mirror=False, reference='',
3449 dissociate=False, worktree=False, submodules=False, archive=False,
3450 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003451 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3452 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jones55ee3042022-04-06 17:10:21 +00003453 git_event_log=None, platform='', manifest_name='default.xml',
3454 tags='', this_manifest_only=False, outer_manifest=True):
LaMont Jones9b03f152022-03-29 23:01:18 +00003455 """Sync the manifest and all submanifests.
3456
3457 Args:
3458 manifest_url: a string, the URL of the manifest project.
3459 manifest_branch: a string, the manifest branch to use.
3460 standalone_manifest: a boolean, whether to store the manifest as a static
3461 file.
3462 groups: a string, restricts the checkout to projects with the specified
3463 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003464 mirror: a boolean, whether to create a mirror of the remote repository.
3465 reference: a string, location of a repo instance to use as a reference.
3466 dissociate: a boolean, whether to dissociate from reference mirrors after
3467 clone.
3468 worktree: a boolean, whether to use git-worktree to manage projects.
3469 submodules: a boolean, whether sync submodules associated with the
3470 manifest project.
3471 archive: a boolean, whether to checkout each project as an archive. See
3472 git-archive.
3473 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003474 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003475 clone_filter: a string, filter to use with partial_clone.
3476 partial_clone_exclude : a string, comma-delimeted list of project namess
3477 to exclude from partial clone.
3478 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3479 git_lfs: a boolean, whether to enable git LFS support.
3480 use_superproject: a boolean, whether to use the manifest superproject to
3481 sync projects.
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.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003485 platform: a string, restrict the checkout to projects with the specified
3486 platform group.
LaMont Jones55ee3042022-04-06 17:10:21 +00003487 git_event_log: an EventLog, for git tracing.
LaMont Jones4ada0432022-04-14 15:10:43 +00003488 tags: a boolean, whether to fetch tags.
LaMont Jones409407a2022-04-05 21:21:56 +00003489 manifest_name: a string, the name of the manifest file to use.
3490 this_manifest_only: a boolean, whether to only operate on the current sub
3491 manifest.
3492 outer_manifest: a boolean, whether to start at the outermost manifest.
LaMont Jones9b03f152022-03-29 23:01:18 +00003493
3494 Returns:
3495 a boolean, whether the sync was successful.
3496 """
3497 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3498
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003499 groups = groups or 'default'
3500 platform = platform or 'auto'
LaMont Jones55ee3042022-04-06 17:10:21 +00003501 git_event_log = git_event_log or EventLog()
LaMont Jones409407a2022-04-05 21:21:56 +00003502 if outer_manifest and self.manifest.is_submanifest:
3503 # In a multi-manifest checkout, use the outer manifest unless we are told
3504 # not to.
3505 return self.client.outer_manifest.manifestProject.Sync(
3506 manifest_url=manifest_url,
3507 manifest_branch=manifest_branch,
3508 standalone_manifest=standalone_manifest,
3509 groups=groups,
3510 platform=platform,
3511 mirror=mirror,
3512 dissociate=dissociate,
3513 reference=reference,
3514 worktree=worktree,
3515 submodules=submodules,
3516 archive=archive,
3517 partial_clone=partial_clone,
3518 clone_filter=clone_filter,
3519 partial_clone_exclude=partial_clone_exclude,
3520 clone_bundle=clone_bundle,
3521 git_lfs=git_lfs,
3522 use_superproject=use_superproject,
3523 verbose=verbose,
3524 current_branch_only=current_branch_only,
3525 tags=tags,
3526 depth=depth,
LaMont Jones55ee3042022-04-06 17:10:21 +00003527 git_event_log=git_event_log,
LaMont Jones409407a2022-04-05 21:21:56 +00003528 manifest_name=manifest_name,
3529 this_manifest_only=this_manifest_only,
3530 outer_manifest=False)
3531
LaMont Jones9b03f152022-03-29 23:01:18 +00003532 # If repo has already been initialized, we take -u with the absence of
3533 # --standalone-manifest to mean "transition to a standard repo set up",
3534 # which necessitates starting fresh.
3535 # If --standalone-manifest is set, we always tear everything down and start
3536 # anew.
3537 if self.Exists:
3538 was_standalone_manifest = self.config.GetString('manifest.standalone')
3539 if was_standalone_manifest and not manifest_url:
3540 print('fatal: repo was initialized with a standlone manifest, '
3541 'cannot be re-initialized without --manifest-url/-u')
3542 return False
3543
3544 if standalone_manifest or (was_standalone_manifest and manifest_url):
3545 self.config.ClearCache()
3546 if self.gitdir and os.path.exists(self.gitdir):
3547 platform_utils.rmtree(self.gitdir)
3548 if self.worktree and os.path.exists(self.worktree):
3549 platform_utils.rmtree(self.worktree)
3550
3551 is_new = not self.Exists
3552 if is_new:
3553 if not manifest_url:
3554 print('fatal: manifest url is required.', file=sys.stderr)
3555 return False
3556
LaMont Jones409407a2022-04-05 21:21:56 +00003557 if verbose:
LaMont Jones9b03f152022-03-29 23:01:18 +00003558 print('Downloading manifest from %s' %
3559 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3560 file=sys.stderr)
3561
3562 # The manifest project object doesn't keep track of the path on the
3563 # server where this git is located, so let's save that here.
3564 mirrored_manifest_git = None
3565 if reference:
3566 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3567 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3568 if not mirrored_manifest_git.endswith(".git"):
3569 mirrored_manifest_git += ".git"
3570 if not os.path.exists(mirrored_manifest_git):
3571 mirrored_manifest_git = os.path.join(reference,
3572 '.repo/manifests.git')
3573
3574 self._InitGitDir(mirror_git=mirrored_manifest_git)
3575
3576 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3577 # still do much of the manifests.git set up, but will avoid actual syncs to
3578 # a remote.
3579 if standalone_manifest:
3580 self.config.SetString('manifest.standalone', manifest_url)
3581 elif not manifest_url and not manifest_branch:
3582 # If -u is set and --standalone-manifest is not, then we're not in
3583 # standalone mode. Otherwise, use config to infer what we were in the last
3584 # init.
3585 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3586 if not standalone_manifest:
3587 self.config.SetString('manifest.standalone', None)
3588
3589 self._ConfigureDepth(depth)
3590
3591 # Set the remote URL before the remote branch as we might need it below.
3592 if manifest_url:
3593 r = self.GetRemote(self.remote.name)
3594 r.url = manifest_url
3595 r.ResetFetch()
3596 r.Save()
3597
3598 if not standalone_manifest:
3599 if manifest_branch:
3600 if manifest_branch == 'HEAD':
3601 manifest_branch = self.ResolveRemoteHead()
3602 if manifest_branch is None:
3603 print('fatal: unable to resolve HEAD', file=sys.stderr)
3604 return False
3605 self.revisionExpr = manifest_branch
3606 else:
3607 if is_new:
3608 default_branch = self.ResolveRemoteHead()
3609 if default_branch is None:
3610 # If the remote doesn't have HEAD configured, default to master.
3611 default_branch = 'refs/heads/master'
3612 self.revisionExpr = default_branch
3613 else:
3614 self.PreSync()
3615
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003616 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003617 all_platforms = ['linux', 'darwin', 'windows']
3618 platformize = lambda x: 'platform-' + x
3619 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003620 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003621 groups.append(platformize(self._platform_name))
3622 elif platform == 'all':
3623 groups.extend(map(platformize, all_platforms))
3624 elif platform in all_platforms:
3625 groups.append(platformize(platform))
3626 elif platform != 'none':
3627 print('fatal: invalid platform flag', file=sys.stderr)
3628 return False
LaMont Jones4ada0432022-04-14 15:10:43 +00003629 self.config.SetString('manifest.platform', platform)
LaMont Jones9b03f152022-03-29 23:01:18 +00003630
3631 groups = [x for x in groups if x]
3632 groupstr = ','.join(groups)
3633 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3634 groupstr = None
3635 self.config.SetString('manifest.groups', groupstr)
3636
3637 if reference:
3638 self.config.SetString('repo.reference', reference)
3639
3640 if dissociate:
3641 self.config.SetBoolean('repo.dissociate', dissociate)
3642
3643 if worktree:
3644 if mirror:
3645 print('fatal: --mirror and --worktree are incompatible',
3646 file=sys.stderr)
3647 return False
3648 if submodules:
3649 print('fatal: --submodules and --worktree are incompatible',
3650 file=sys.stderr)
3651 return False
3652 self.config.SetBoolean('repo.worktree', worktree)
3653 if is_new:
3654 self.use_git_worktrees = True
3655 print('warning: --worktree is experimental!', file=sys.stderr)
3656
3657 if archive:
3658 if is_new:
3659 self.config.SetBoolean('repo.archive', archive)
3660 else:
3661 print('fatal: --archive is only supported when initializing a new '
3662 'workspace.', file=sys.stderr)
3663 print('Either delete the .repo folder in this workspace, or initialize '
3664 'in another location.', file=sys.stderr)
3665 return False
3666
3667 if mirror:
3668 if is_new:
3669 self.config.SetBoolean('repo.mirror', mirror)
3670 else:
3671 print('fatal: --mirror is only supported when initializing a new '
3672 'workspace.', file=sys.stderr)
3673 print('Either delete the .repo folder in this workspace, or initialize '
3674 'in another location.', file=sys.stderr)
3675 return False
3676
3677 if partial_clone is not None:
3678 if mirror:
3679 print('fatal: --mirror and --partial-clone are mutually exclusive',
3680 file=sys.stderr)
3681 return False
3682 self.config.SetBoolean('repo.partialclone', partial_clone)
3683 if clone_filter:
3684 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jones55ee3042022-04-06 17:10:21 +00003685 elif self.partial_clone:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003686 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003687 else:
3688 clone_filter = None
3689
3690 if partial_clone_exclude is not None:
3691 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3692
3693 if clone_bundle is None:
3694 clone_bundle = False if partial_clone else True
3695 else:
3696 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3697
3698 if submodules:
3699 self.config.SetBoolean('repo.submodules', submodules)
3700
3701 if git_lfs is not None:
3702 if git_lfs:
3703 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3704
3705 self.config.SetBoolean('repo.git-lfs', git_lfs)
3706 if not is_new:
3707 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3708 ' Existing projects will require manual updates.\n', file=sys.stderr)
3709
3710 if use_superproject is not None:
3711 self.config.SetBoolean('repo.superproject', use_superproject)
3712
LaMont Jones0165e202022-04-27 17:34:42 +00003713 if not standalone_manifest:
3714 if not self.Sync_NetworkHalf(
3715 is_new=is_new, quiet=not verbose, verbose=verbose,
3716 clone_bundle=clone_bundle, current_branch_only=current_branch_only,
3717 tags=tags, submodules=submodules, clone_filter=clone_filter,
3718 partial_clone_exclude=self.manifest.PartialCloneExclude):
3719 r = self.GetRemote(self.remote.name)
3720 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
LaMont Jones9b03f152022-03-29 23:01:18 +00003721
LaMont Jones0165e202022-04-27 17:34:42 +00003722 # Better delete the manifest git dir if we created it; otherwise next
3723 # time (when user fixes problems) we won't go through the "is_new" logic.
3724 if is_new:
3725 platform_utils.rmtree(self.gitdir)
LaMont Jones9b03f152022-03-29 23:01:18 +00003726 return False
3727
LaMont Jones0165e202022-04-27 17:34:42 +00003728 if manifest_branch:
3729 self.MetaBranchSwitch(submodules=submodules)
3730
3731 syncbuf = SyncBuffer(self.config)
3732 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3733 syncbuf.Finish()
3734
3735 if is_new or self.CurrentBranch is None:
3736 if not self.StartBranch('default'):
3737 print('fatal: cannot create default in manifest', file=sys.stderr)
3738 return False
3739
3740 if not manifest_name:
3741 print('fatal: manifest name (-m) is required.', file=sys.stderr)
3742 return False
3743
3744 elif is_new:
3745 # This is a new standalone manifest.
3746 manifest_name = 'default.xml'
3747 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3748 dest = os.path.join(self.worktree, manifest_name)
3749 os.makedirs(os.path.dirname(dest), exist_ok=True)
3750 with open(dest, 'wb') as f:
3751 f.write(manifest_data)
LaMont Jones409407a2022-04-05 21:21:56 +00003752
3753 try:
3754 self.manifest.Link(manifest_name)
3755 except ManifestParseError as e:
3756 print("fatal: manifest '%s' not available" % manifest_name,
3757 file=sys.stderr)
3758 print('fatal: %s' % str(e), file=sys.stderr)
3759 return False
3760
LaMont Jones55ee3042022-04-06 17:10:21 +00003761 if not this_manifest_only:
3762 for submanifest in self.manifest.submanifests.values():
LaMont Jonesb90a4222022-04-14 15:00:09 +00003763 spec = submanifest.ToSubmanifestSpec()
LaMont Jones55ee3042022-04-06 17:10:21 +00003764 submanifest.repo_client.manifestProject.Sync(
3765 manifest_url=spec.manifestUrl,
3766 manifest_branch=spec.revision,
3767 standalone_manifest=standalone_manifest,
3768 groups=self.manifest_groups,
3769 platform=platform,
3770 mirror=mirror,
3771 dissociate=dissociate,
3772 reference=reference,
3773 worktree=worktree,
3774 submodules=submodules,
3775 archive=archive,
3776 partial_clone=partial_clone,
3777 clone_filter=clone_filter,
3778 partial_clone_exclude=partial_clone_exclude,
3779 clone_bundle=clone_bundle,
3780 git_lfs=git_lfs,
3781 use_superproject=use_superproject,
3782 verbose=verbose,
3783 current_branch_only=current_branch_only,
3784 tags=tags,
3785 depth=depth,
3786 git_event_log=git_event_log,
3787 manifest_name=spec.manifestName,
3788 this_manifest_only=False,
3789 outer_manifest=False,
3790 )
LaMont Jones409407a2022-04-05 21:21:56 +00003791
LaMont Jones55ee3042022-04-06 17:10:21 +00003792 # Lastly, clone the superproject(s).
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003793 if self.manifest.manifestProject.use_superproject:
3794 sync_result = Superproject(
3795 self.manifest, self.manifest.repodir, git_event_log, quiet=not verbose).Sync()
3796 if not sync_result.success:
3797 print('warning: git update of superproject for '
3798 f'{self.manifest.path_prefix} failed, repo sync will not use '
3799 'superproject to fetch source; while this error is not fatal, '
3800 'and you can continue to run repo sync, please run repo init '
3801 'with the --no-use-superproject option to stop seeing this '
3802 'warning', file=sys.stderr)
3803 if sync_result.fatal and use_superproject is not None:
3804 return False
LaMont Jones409407a2022-04-05 21:21:56 +00003805
LaMont Jones9b03f152022-03-29 23:01:18 +00003806 return True
3807
3808 def _ConfigureDepth(self, depth):
3809 """Configure the depth we'll sync down.
3810
3811 Args:
3812 depth: an int, how deep of a partial clone to create.
3813 """
3814 # Opt.depth will be non-None if user actually passed --depth to repo init.
3815 if depth is not None:
3816 if depth > 0:
3817 # Positive values will set the depth.
3818 depth = str(depth)
3819 else:
3820 # Negative numbers will clear the depth; passing None to SetString
3821 # will do that.
3822 depth = None
3823
3824 # We store the depth in the main manifest project.
3825 self.config.SetString('repo.depth', depth)