blob: 2641c445679a7daf5a75c75d15f643bf16616585 [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 """
LaMont Jones501733c2022-04-20 16:42:32 +0000718 default_groups = self.manifest.default_groups or ['default']
719 expanded_manifest_groups = manifest_groups or default_groups
Conley Owensbb1b5f52012-08-13 13:11:18 -0700720 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700721 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500722 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700723
Conley Owens971de8e2012-04-16 10:36:08 -0700724 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700725 for group in expanded_manifest_groups:
726 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700727 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700728 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700729 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700730
Conley Owens971de8e2012-04-16 10:36:08 -0700731 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700733# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700734 def UncommitedFiles(self, get_all=True):
735 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700737 Args:
738 get_all: a boolean, if True - get information about all different
739 uncommitted files. If False - return as soon as any kind of
740 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500741 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700742 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500743 self.work_git.update_index('-q',
744 '--unmerged',
745 '--ignore-missing',
746 '--refresh')
747 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700748 details.append("rebase in progress")
749 if not get_all:
750 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500751
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700752 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
753 if changes:
754 details.extend(changes)
755 if not get_all:
756 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500757
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700758 changes = self.work_git.DiffZ('diff-files').keys()
759 if changes:
760 details.extend(changes)
761 if not get_all:
762 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500763
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700764 changes = self.work_git.LsOthers()
765 if changes:
766 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500767
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700768 return details
769
770 def HasChanges(self):
771 """Returns true if there are uncommitted changes.
772 """
773 if self.UncommitedFiles(get_all=False):
774 return True
775 else:
776 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500777
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600778 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700779 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200780
781 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200782 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600783 quiet: If True then only print the project name. Do not print
784 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700785 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700786 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700787 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200788 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700789 print(file=output_redir)
790 print('project %s/' % self.relpath, file=output_redir)
791 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700792 return
793
794 self.work_git.update_index('-q',
795 '--unmerged',
796 '--ignore-missing',
797 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700798 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700799 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
800 df = self.work_git.DiffZ('diff-files')
801 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100802 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700803 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700804
805 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700806 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200807 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700808 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600810 if quiet:
811 out.nl()
812 return 'DIRTY'
813
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814 branch = self.CurrentBranch
815 if branch is None:
816 out.nobranch('(*** NO BRANCH ***)')
817 else:
818 out.branch('branch %s', branch)
819 out.nl()
820
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700821 if rb:
822 out.important('prior sync failed; rebase still in progress')
823 out.nl()
824
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700825 paths = list()
826 paths.extend(di.keys())
827 paths.extend(df.keys())
828 paths.extend(do)
829
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530830 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900831 try:
832 i = di[p]
833 except KeyError:
834 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900836 try:
837 f = df[p]
838 except KeyError:
839 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200840
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900841 if i:
842 i_status = i.status.upper()
843 else:
844 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900846 if f:
847 f_status = f.status.lower()
848 else:
849 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700850
851 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800852 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700853 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854 else:
855 line = ' %s%s\t%s' % (i_status, f_status, p)
856
857 if i and not f:
858 out.added('%s', line)
859 elif (i and f) or (not i and f):
860 out.changed('%s', line)
861 elif not i and not f:
862 out.untracked('%s', line)
863 else:
864 out.write('%s', line)
865 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200866
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700867 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500869 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700870 """Prints the status of the repository to stdout.
871 """
872 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500873 if output_redir:
874 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700875 cmd = ['diff']
876 if out.is_on:
877 cmd.append('--color')
878 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300879 if absolute_paths:
880 cmd.append('--src-prefix=a/%s/' % self.relpath)
881 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700882 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400883 try:
884 p = GitCommand(self,
885 cmd,
886 capture_stdout=True,
887 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500888 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400889 except GitError as e:
890 out.nl()
891 out.project('project %s/' % self.relpath)
892 out.nl()
893 out.fail('%s', str(e))
894 out.nl()
895 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500896 if p.stdout:
897 out.nl()
898 out.project('project %s/' % self.relpath)
899 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500900 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400901 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700903# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900904 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905 """Was the branch published (uploaded) for code review?
906 If so, returns the SHA-1 hash of the last published
907 state for the branch.
908 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700909 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900910 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700911 try:
912 return self.bare_git.rev_parse(key)
913 except GitError:
914 return None
915 else:
916 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900917 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700918 except KeyError:
919 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920
David Pursehouse8a68ff92012-09-24 12:15:13 +0900921 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 """Prunes any stale published refs.
923 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900924 if all_refs is None:
925 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926 heads = set()
927 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530928 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929 if name.startswith(R_HEADS):
930 heads.add(name)
931 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900932 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530934 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700935 n = name[len(R_PUB):]
936 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900937 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700938
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700939 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700940 """List any branches which can be uploaded for review.
941 """
942 heads = {}
943 pubed = {}
944
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530945 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900947 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900949 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950
951 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530952 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900953 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700955 if selected_branch and branch != selected_branch:
956 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800958 rb = self.GetUploadableBranch(branch)
959 if rb:
960 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700961 return ready
962
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800963 def GetUploadableBranch(self, branch_name):
964 """Get a single uploadable branch, or None.
965 """
966 branch = self.GetBranch(branch_name)
967 base = branch.LocalMerge
968 if branch.LocalMerge:
969 rb = ReviewableBranch(self, branch, base)
970 if rb.commits:
971 return rb
972 return None
973
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700974 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100975 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500976 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700977 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500978 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500979 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200980 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700981 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200982 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200983 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800984 validate_certs=True,
985 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 """Uploads the named branch for code review.
987 """
988 if branch is None:
989 branch = self.CurrentBranch
990 if branch is None:
991 raise GitError('not currently on a branch')
992
993 branch = self.GetBranch(branch)
994 if not branch.LocalMerge:
995 raise GitError('branch %s does not track a remote' % branch.name)
996 if not branch.remote.review:
997 raise GitError('remote %s has no review url' % branch.remote.name)
998
Bryan Jacobsf609f912013-05-06 13:36:24 -0400999 if dest_branch is None:
1000 dest_branch = self.dest_branch
1001 if dest_branch is None:
1002 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003 if not dest_branch.startswith(R_HEADS):
1004 dest_branch = R_HEADS + dest_branch
1005
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001006 if not branch.remote.projectname:
1007 branch.remote.projectname = self.name
1008 branch.remote.Save()
1009
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001010 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001011 if url is None:
1012 raise UploadError('review not configured')
1013 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001014 if dryrun:
1015 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001016
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001017 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001018 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001019
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001020 for push_option in (push_options or []):
1021 cmd.append('-o')
1022 cmd.append(push_option)
1023
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001024 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001025
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001026 if dest_branch.startswith(R_HEADS):
1027 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001028
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001029 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001030 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001031 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001032 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001033 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001034 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001035
David Pursehousef25a3702018-11-14 19:01:22 -08001036 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001037 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001038 if notify:
1039 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001040 if private:
1041 opts += ['private']
1042 if wip:
1043 opts += ['wip']
1044 if opts:
1045 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001046 cmd.append(ref_spec)
1047
Anthony King7bdac712014-07-16 12:56:40 +01001048 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001049 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001050
Mike Frysingerd7f86832020-11-19 19:18:46 -05001051 if not dryrun:
1052 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1053 self.bare_git.UpdateRef(R_PUB + branch.name,
1054 R_HEADS + branch.name,
1055 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001057# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001058 def _ExtractArchive(self, tarpath, path=None):
1059 """Extract the given tar on its current location
1060
1061 Args:
1062 - tarpath: The path to the actual tar file
1063
1064 """
1065 try:
1066 with tarfile.open(tarpath, 'r') as tar:
1067 tar.extractall(path=path)
1068 return True
1069 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001070 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001071 return False
1072
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001073 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001074 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001075 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001076 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001077 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001078 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001079 force_sync=False,
1080 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001081 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001082 archive=False,
1083 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001084 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001085 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001086 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001087 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001088 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001089 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 """Perform only the network IO portion of the sync process.
1091 Local working directory/branch state is not affected.
1092 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001093 if archive and not isinstance(self, MetaProject):
1094 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001095 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001096 return False
1097
1098 name = self.relpath.replace('\\', '/')
1099 name = name.replace('/', '_')
1100 tarpath = '%s.tar' % name
1101 topdir = self.manifest.topdir
1102
1103 try:
1104 self._FetchArchive(tarpath, cwd=topdir)
1105 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001106 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001107 return False
1108
1109 # From now on, we only need absolute tarpath
1110 tarpath = os.path.join(topdir, tarpath)
1111
1112 if not self._ExtractArchive(tarpath, path=topdir):
1113 return False
1114 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001115 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001116 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001117 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001118 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001119 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001120
1121 # If the shared object dir already exists, don't try to rebootstrap with a
1122 # clone bundle download. We should have the majority of objects already.
1123 if clone_bundle and os.path.exists(self.objdir):
1124 clone_bundle = False
1125
Raman Tennetif32f2432021-04-12 20:57:25 -07001126 if self.name in partial_clone_exclude:
1127 clone_bundle = True
1128 clone_filter = None
1129
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001130 if is_new is None:
1131 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001132 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001133 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001134 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001135 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001137
1138 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001139 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001140 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001141 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001142 # This works for both absolute and relative alternate directories.
1143 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001144 except IOError:
1145 alt_dir = None
1146 else:
1147 alt_dir = None
1148
Mike Frysingere50b6a72020-02-19 01:45:48 -05001149 if (clone_bundle
1150 and alt_dir is None
1151 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001152 is_new = False
1153
Mike Frysinger73561142021-05-03 01:10:09 -04001154 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001155 if self.sync_c:
1156 current_branch_only = True
1157 elif not self.manifest._loaded:
1158 # Manifest cannot check defaults until it syncs.
1159 current_branch_only = False
1160 elif self.manifest.default.sync_c:
1161 current_branch_only = True
1162
Mike Frysingerd68ed632021-05-03 01:21:35 -04001163 if tags is None:
1164 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001165
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001166 if self.clone_depth:
1167 depth = self.clone_depth
1168 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001169 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001170
Mike Frysinger521d01b2020-02-17 01:51:49 -05001171 # See if we can skip the network fetch entirely.
1172 if not (optimized_fetch and
1173 (ID_RE.match(self.revisionExpr) and
1174 self._CheckForImmutableRevision())):
1175 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001176 initial=is_new,
1177 quiet=quiet, verbose=verbose, output_redir=output_redir,
1178 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001179 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001180 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001181 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001182 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001183 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001184
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001185 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001186 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001187 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001188 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001189 if os.path.exists(alternates_file):
1190 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001191 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1192 merge_output=bool(output_redir))
1193 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001194 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001195 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001196 return False
1197 platform_utils.remove(alternates_file)
1198
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001199 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001200 self._InitMRef()
1201 else:
1202 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001203 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1204 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001205 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001206
1207 def PostRepoUpgrade(self):
1208 self._InitHooks()
1209
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001210 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001211 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001212 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001213 for copyfile in self.copyfiles:
1214 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001215 for linkfile in self.linkfiles:
1216 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001217
Julien Camperguedd654222014-01-09 16:21:37 +01001218 def GetCommitRevisionId(self):
1219 """Get revisionId of a commit.
1220
1221 Use this method instead of GetRevisionId to get the id of the commit rather
1222 than the id of the current git object (for example, a tag)
1223
1224 """
1225 if not self.revisionExpr.startswith(R_TAGS):
1226 return self.GetRevisionId(self._allrefs)
1227
1228 try:
1229 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1230 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001231 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1232 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001233
David Pursehouse8a68ff92012-09-24 12:15:13 +09001234 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001235 if self.revisionId:
1236 return self.revisionId
1237
1238 rem = self.GetRemote(self.remote.name)
1239 rev = rem.ToLocal(self.revisionExpr)
1240
David Pursehouse8a68ff92012-09-24 12:15:13 +09001241 if all_refs is not None and rev in all_refs:
1242 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001243
1244 try:
1245 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1246 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001247 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1248 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001249
Raman Tenneti6a872c92021-01-14 19:17:50 -08001250 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001251 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001252 self.upstream = self.revisionExpr
1253
Raman Tenneti6a872c92021-01-14 19:17:50 -08001254 self.revisionId = revisionId
1255
Martin Kellye4e94d22017-03-21 16:05:12 -07001256 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001257 """Perform only the local IO portion of the sync process.
1258 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001259 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001260 if not os.path.exists(self.gitdir):
1261 syncbuf.fail(self,
1262 'Cannot checkout %s due to missing network sync; Run '
1263 '`repo sync -n %s` first.' %
1264 (self.name, self.name))
1265 return
1266
Martin Kellye4e94d22017-03-21 16:05:12 -07001267 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001268 all_refs = self.bare_ref.all
1269 self.CleanPublishedCache(all_refs)
1270 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001271
Mike Frysinger0458faa2021-03-10 23:35:44 -05001272 # Special case the root of the repo client checkout. Make sure it doesn't
1273 # contain files being checked out to dirs we don't allow.
1274 if self.relpath == '.':
1275 PROTECTED_PATHS = {'.repo'}
1276 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1277 bad_paths = paths & PROTECTED_PATHS
1278 if bad_paths:
1279 syncbuf.fail(self,
1280 'Refusing to checkout project that writes to protected '
1281 'paths: %s' % (', '.join(bad_paths),))
1282 return
1283
David Pursehouse1d947b32012-10-25 12:23:11 +09001284 def _doff():
1285 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001286 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001287
Martin Kellye4e94d22017-03-21 16:05:12 -07001288 def _dosubmodules():
1289 self._SyncSubmodules(quiet=True)
1290
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001291 head = self.work_git.GetHead()
1292 if head.startswith(R_HEADS):
1293 branch = head[len(R_HEADS):]
1294 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001295 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001296 except KeyError:
1297 head = None
1298 else:
1299 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001301 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302 # Currently on a detached HEAD. The user is assumed to
1303 # not have any local modifications worth worrying about.
1304 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001305 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001306 syncbuf.fail(self, _PriorSyncFailedError())
1307 return
1308
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001309 if head == revid:
1310 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001311 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001312 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001313 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001314 # The copy/linkfile config may have changed.
1315 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001316 return
1317 else:
1318 lost = self._revlist(not_rev(revid), HEAD)
1319 if lost:
1320 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001321
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001323 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001324 if submodules:
1325 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001326 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001327 syncbuf.fail(self, e)
1328 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001329 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001330 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001331
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001332 if head == revid:
1333 # No changes; don't do anything further.
1334 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001335 # The copy/linkfile config may have changed.
1336 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001337 return
1338
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001339 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001340
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001341 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001343 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001345 syncbuf.info(self,
1346 "leaving %s; does not track upstream",
1347 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001348 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001349 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001350 if submodules:
1351 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001352 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001353 syncbuf.fail(self, e)
1354 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001355 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001356 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001358 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001359
1360 # See if we can perform a fast forward merge. This can happen if our
1361 # branch isn't in the exact same state as we last published.
1362 try:
1363 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1364 # Skip the published logic.
1365 pub = False
1366 except GitError:
1367 pub = self.WasPublished(branch.name, all_refs)
1368
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001369 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001370 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001371 if not_merged:
1372 if upstream_gain:
1373 # The user has published this branch and some of those
1374 # commits are not yet merged upstream. We do not want
1375 # to rewrite the published commits so we punt.
1376 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001377 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001378 "branch %s is published (but not merged) and is now "
1379 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001380 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001381 elif pub == head:
1382 # All published commits are merged, and thus we are a
1383 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001384 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001385 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001386 if submodules:
1387 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001388 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001390 # Examine the local commits not in the remote. Find the
1391 # last one attributed to this user, if any.
1392 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001393 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001394 last_mine = None
1395 cnt_mine = 0
1396 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001397 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001398 if committer_email == self.UserEmail:
1399 last_mine = commit_id
1400 cnt_mine += 1
1401
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001402 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001403 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001404
1405 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001406 syncbuf.fail(self, _DirtyError())
1407 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001408
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001409 # If the upstream switched on us, warn the user.
1410 #
1411 if branch.merge != self.revisionExpr:
1412 if branch.merge and self.revisionExpr:
1413 syncbuf.info(self,
1414 'manifest switched %s...%s',
1415 branch.merge,
1416 self.revisionExpr)
1417 elif branch.merge:
1418 syncbuf.info(self,
1419 'manifest no longer tracks %s',
1420 branch.merge)
1421
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001422 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001423 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001424 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001425 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001426 syncbuf.info(self,
1427 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001428 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001429
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001430 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001431 if not ID_RE.match(self.revisionExpr):
1432 # in case of manifest sync the revisionExpr might be a SHA1
1433 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001434 if not branch.merge.startswith('refs/'):
1435 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001436 branch.Save()
1437
Mike Pontillod3153822012-02-28 11:53:24 -08001438 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001439 def _docopyandlink():
1440 self._CopyAndLinkFiles()
1441
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001442 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001443 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001444 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001445 if submodules:
1446 syncbuf.later2(self, _dosubmodules)
1447 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001448 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001449 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001450 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001451 if submodules:
1452 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001453 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001454 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001455 syncbuf.fail(self, e)
1456 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001457 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001458 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001459 if submodules:
1460 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001461
Mike Frysingere6a202f2019-08-02 15:57:57 -04001462 def AddCopyFile(self, src, dest, topdir):
1463 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001464
Mike Frysingere6a202f2019-08-02 15:57:57 -04001465 No filesystem changes occur here. Actual copying happens later on.
1466
1467 Paths should have basic validation run on them before being queued.
1468 Further checking will be handled when the actual copy happens.
1469 """
1470 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1471
1472 def AddLinkFile(self, src, dest, topdir):
1473 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1474
1475 No filesystem changes occur here. Actual linking happens later on.
1476
1477 Paths should have basic validation run on them before being queued.
1478 Further checking will be handled when the actual link happens.
1479 """
1480 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001481
James W. Mills24c13082012-04-12 15:04:13 -05001482 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001483 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001484
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001485 def DownloadPatchSet(self, change_id, patch_id):
1486 """Download a single patch set of a single change to FETCH_HEAD.
1487 """
1488 remote = self.GetRemote(self.remote.name)
1489
1490 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001491 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001492 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001493 if GitCommand(self, cmd, bare=True).Wait() != 0:
1494 return None
1495 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001496 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001497 change_id,
1498 patch_id,
1499 self.bare_git.rev_parse('FETCH_HEAD'))
1500
Mike Frysingerc0d18662020-02-19 19:19:18 -05001501 def DeleteWorktree(self, quiet=False, force=False):
1502 """Delete the source checkout and any other housekeeping tasks.
1503
1504 This currently leaves behind the internal .repo/ cache state. This helps
1505 when switching branches or manifest changes get reverted as we don't have
1506 to redownload all the git objects. But we should do some GC at some point.
1507
1508 Args:
1509 quiet: Whether to hide normal messages.
1510 force: Always delete tree even if dirty.
1511
1512 Returns:
1513 True if the worktree was completely cleaned out.
1514 """
1515 if self.IsDirty():
1516 if force:
1517 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1518 (self.relpath,), file=sys.stderr)
1519 else:
1520 print('error: %s: Cannot remove project: uncommitted changes are '
1521 'present.\n' % (self.relpath,), file=sys.stderr)
1522 return False
1523
1524 if not quiet:
1525 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1526
1527 # Unlock and delink from the main worktree. We don't use git's worktree
1528 # remove because it will recursively delete projects -- we handle that
1529 # ourselves below. https://crbug.com/git/48
1530 if self.use_git_worktrees:
1531 needle = platform_utils.realpath(self.gitdir)
1532 # Find the git worktree commondir under .repo/worktrees/.
1533 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1534 assert output.startswith('worktree '), output
1535 commondir = output[9:]
1536 # Walk each of the git worktrees to see where they point.
1537 configs = os.path.join(commondir, 'worktrees')
1538 for name in os.listdir(configs):
1539 gitdir = os.path.join(configs, name, 'gitdir')
1540 with open(gitdir) as fp:
1541 relpath = fp.read().strip()
1542 # Resolve the checkout path and see if it matches this project.
1543 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1544 if fullpath == needle:
1545 platform_utils.rmtree(os.path.join(configs, name))
1546
1547 # Delete the .git directory first, so we're less likely to have a partially
1548 # working git repository around. There shouldn't be any git projects here,
1549 # so rmtree works.
1550
1551 # Try to remove plain files first in case of git worktrees. If this fails
1552 # for any reason, we'll fall back to rmtree, and that'll display errors if
1553 # it can't remove things either.
1554 try:
1555 platform_utils.remove(self.gitdir)
1556 except OSError:
1557 pass
1558 try:
1559 platform_utils.rmtree(self.gitdir)
1560 except OSError as e:
1561 if e.errno != errno.ENOENT:
1562 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1563 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1564 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1565 return False
1566
1567 # Delete everything under the worktree, except for directories that contain
1568 # another git project.
1569 dirs_to_remove = []
1570 failed = False
1571 for root, dirs, files in platform_utils.walk(self.worktree):
1572 for f in files:
1573 path = os.path.join(root, f)
1574 try:
1575 platform_utils.remove(path)
1576 except OSError as e:
1577 if e.errno != errno.ENOENT:
1578 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1579 failed = True
1580 dirs[:] = [d for d in dirs
1581 if not os.path.lexists(os.path.join(root, d, '.git'))]
1582 dirs_to_remove += [os.path.join(root, d) for d in dirs
1583 if os.path.join(root, d) not in dirs_to_remove]
1584 for d in reversed(dirs_to_remove):
1585 if platform_utils.islink(d):
1586 try:
1587 platform_utils.remove(d)
1588 except OSError as e:
1589 if e.errno != errno.ENOENT:
1590 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1591 failed = True
1592 elif not platform_utils.listdir(d):
1593 try:
1594 platform_utils.rmdir(d)
1595 except OSError as e:
1596 if e.errno != errno.ENOENT:
1597 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1598 failed = True
1599 if failed:
1600 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1601 file=sys.stderr)
1602 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1603 return False
1604
1605 # Try deleting parent dirs if they are empty.
1606 path = self.worktree
1607 while path != self.manifest.topdir:
1608 try:
1609 platform_utils.rmdir(path)
1610 except OSError as e:
1611 if e.errno != errno.ENOENT:
1612 break
1613 path = os.path.dirname(path)
1614
1615 return True
1616
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001617# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001618 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001619 """Create a new branch off the manifest's revision.
1620 """
Simran Basib9a1b732015-08-20 12:19:28 -07001621 if not branch_merge:
1622 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001623 head = self.work_git.GetHead()
1624 if head == (R_HEADS + name):
1625 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001626
David Pursehouse8a68ff92012-09-24 12:15:13 +09001627 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001628 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001629 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001630 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001631 capture_stdout=True,
1632 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001633
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001634 branch = self.GetBranch(name)
1635 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001636 branch.merge = branch_merge
1637 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1638 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001639
1640 if revision is None:
1641 revid = self.GetRevisionId(all_refs)
1642 else:
1643 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001644
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001645 if head.startswith(R_HEADS):
1646 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001647 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001648 except KeyError:
1649 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001650 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001651 ref = R_HEADS + name
1652 self.work_git.update_ref(ref, revid)
1653 self.work_git.symbolic_ref(HEAD, ref)
1654 branch.Save()
1655 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001656
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001657 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001658 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001659 capture_stdout=True,
1660 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001661 branch.Save()
1662 return True
1663 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001664
Wink Saville02d79452009-04-10 13:01:24 -07001665 def CheckoutBranch(self, name):
1666 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001667
1668 Args:
1669 name: The name of the branch to checkout.
1670
1671 Returns:
1672 True if the checkout succeeded; False if it didn't; None if the branch
1673 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001674 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001675 rev = R_HEADS + name
1676 head = self.work_git.GetHead()
1677 if head == rev:
1678 # Already on the branch
1679 #
1680 return True
Wink Saville02d79452009-04-10 13:01:24 -07001681
David Pursehouse8a68ff92012-09-24 12:15:13 +09001682 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001683 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001684 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001685 except KeyError:
1686 # Branch does not exist in this project
1687 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001688 return None
Wink Saville02d79452009-04-10 13:01:24 -07001689
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001690 if head.startswith(R_HEADS):
1691 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001692 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001693 except KeyError:
1694 head = None
1695
1696 if head == revid:
1697 # Same revision; just update HEAD to point to the new
1698 # target branch, but otherwise take no other action.
1699 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001700 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1701 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001702 return True
1703
1704 return GitCommand(self,
1705 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001706 capture_stdout=True,
1707 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001708
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001709 def AbandonBranch(self, name):
1710 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001711
1712 Args:
1713 name: The name of the branch to abandon.
1714
1715 Returns:
1716 True if the abandon succeeded; False if it didn't; None if the branch
1717 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001718 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001719 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001720 all_refs = self.bare_ref.all
1721 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001722 # Doesn't exist
1723 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001724
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001725 head = self.work_git.GetHead()
1726 if head == rev:
1727 # We can't destroy the branch while we are sitting
1728 # on it. Switch to a detached HEAD.
1729 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001730 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001731
David Pursehouse8a68ff92012-09-24 12:15:13 +09001732 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001733 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001734 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001735 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001736 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001737
1738 return GitCommand(self,
1739 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001740 capture_stdout=True,
1741 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001742
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001743 def PruneHeads(self):
1744 """Prune any topic branches already merged into upstream.
1745 """
1746 cb = self.CurrentBranch
1747 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001748 left = self._allrefs
1749 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001750 if name.startswith(R_HEADS):
1751 name = name[len(R_HEADS):]
1752 if cb is None or name != cb:
1753 kill.append(name)
1754
Mike Frysingera3794e92021-03-11 23:24:01 -05001755 # Minor optimization: If there's nothing to prune, then don't try to read
1756 # any project state.
1757 if not kill and not cb:
1758 return []
1759
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001760 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001761 if cb is not None \
1762 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001763 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001764 self.work_git.DetachHead(HEAD)
1765 kill.append(cb)
1766
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001767 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001768 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001770 try:
1771 self.bare_git.DetachHead(rev)
1772
1773 b = ['branch', '-d']
1774 b.extend(kill)
1775 b = GitCommand(self, b, bare=True,
1776 capture_stdout=True,
1777 capture_stderr=True)
1778 b.Wait()
1779 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001780 if ID_RE.match(old):
1781 self.bare_git.DetachHead(old)
1782 else:
1783 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001784 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001785
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001786 for branch in kill:
1787 if (R_HEADS + branch) not in left:
1788 self.CleanPublishedCache()
1789 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001790
1791 if cb and cb not in kill:
1792 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001793 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001794
1795 kept = []
1796 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001797 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001798 branch = self.GetBranch(branch)
1799 base = branch.LocalMerge
1800 if not base:
1801 base = rev
1802 kept.append(ReviewableBranch(self, branch, base))
1803 return kept
1804
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001805# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001806 def GetRegisteredSubprojects(self):
1807 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001808
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001809 def rec(subprojects):
1810 if not subprojects:
1811 return
1812 result.extend(subprojects)
1813 for p in subprojects:
1814 rec(p.subprojects)
1815 rec(self.subprojects)
1816 return result
1817
1818 def _GetSubmodules(self):
1819 # Unfortunately we cannot call `git submodule status --recursive` here
1820 # because the working tree might not exist yet, and it cannot be used
1821 # without a working tree in its current implementation.
1822
1823 def get_submodules(gitdir, rev):
1824 # Parse .gitmodules for submodule sub_paths and sub_urls
1825 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1826 if not sub_paths:
1827 return []
1828 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1829 # revision of submodule repository
1830 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1831 submodules = []
1832 for sub_path, sub_url in zip(sub_paths, sub_urls):
1833 try:
1834 sub_rev = sub_revs[sub_path]
1835 except KeyError:
1836 # Ignore non-exist submodules
1837 continue
1838 submodules.append((sub_rev, sub_path, sub_url))
1839 return submodules
1840
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001841 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1842 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001843
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001844 def parse_gitmodules(gitdir, rev):
1845 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1846 try:
Anthony King7bdac712014-07-16 12:56:40 +01001847 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1848 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001849 except GitError:
1850 return [], []
1851 if p.Wait() != 0:
1852 return [], []
1853
1854 gitmodules_lines = []
1855 fd, temp_gitmodules_path = tempfile.mkstemp()
1856 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001857 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001858 os.close(fd)
1859 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001860 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1861 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001862 if p.Wait() != 0:
1863 return [], []
1864 gitmodules_lines = p.stdout.split('\n')
1865 except GitError:
1866 return [], []
1867 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001868 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001869
1870 names = set()
1871 paths = {}
1872 urls = {}
1873 for line in gitmodules_lines:
1874 if not line:
1875 continue
1876 m = re_path.match(line)
1877 if m:
1878 names.add(m.group(1))
1879 paths[m.group(1)] = m.group(2)
1880 continue
1881 m = re_url.match(line)
1882 if m:
1883 names.add(m.group(1))
1884 urls[m.group(1)] = m.group(2)
1885 continue
1886 names = sorted(names)
1887 return ([paths.get(name, '') for name in names],
1888 [urls.get(name, '') for name in names])
1889
1890 def git_ls_tree(gitdir, rev, paths):
1891 cmd = ['ls-tree', rev, '--']
1892 cmd.extend(paths)
1893 try:
Anthony King7bdac712014-07-16 12:56:40 +01001894 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1895 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001896 except GitError:
1897 return []
1898 if p.Wait() != 0:
1899 return []
1900 objects = {}
1901 for line in p.stdout.split('\n'):
1902 if not line.strip():
1903 continue
1904 object_rev, object_path = line.split()[2:4]
1905 objects[object_path] = object_rev
1906 return objects
1907
1908 try:
1909 rev = self.GetRevisionId()
1910 except GitError:
1911 return []
1912 return get_submodules(self.gitdir, rev)
1913
1914 def GetDerivedSubprojects(self):
1915 result = []
1916 if not self.Exists:
1917 # If git repo does not exist yet, querying its submodules will
1918 # mess up its states; so return here.
1919 return result
1920 for rev, path, url in self._GetSubmodules():
1921 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001922 relpath, worktree, gitdir, objdir = \
1923 self.manifest.GetSubprojectPaths(self, name, path)
1924 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001925 if project:
1926 result.extend(project.GetDerivedSubprojects())
1927 continue
David James8d201162013-10-11 17:03:19 -07001928
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001929 if url.startswith('..'):
1930 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001931 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001932 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001933 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001934 review=self.remote.review,
1935 revision=self.remote.revision)
1936 subproject = Project(manifest=self.manifest,
1937 name=name,
1938 remote=remote,
1939 gitdir=gitdir,
1940 objdir=objdir,
1941 worktree=worktree,
1942 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001943 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001944 revisionId=rev,
1945 rebase=self.rebase,
1946 groups=self.groups,
1947 sync_c=self.sync_c,
1948 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001949 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001950 parent=self,
1951 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001952 result.append(subproject)
1953 result.extend(subproject.GetDerivedSubprojects())
1954 return result
1955
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001956# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001957 def EnableRepositoryExtension(self, key, value='true', version=1):
1958 """Enable git repository extension |key| with |value|.
1959
1960 Args:
1961 key: The extension to enabled. Omit the "extensions." prefix.
1962 value: The value to use for the extension.
1963 version: The minimum git repository version needed.
1964 """
1965 # Make sure the git repo version is new enough already.
1966 found_version = self.config.GetInt('core.repositoryFormatVersion')
1967 if found_version is None:
1968 found_version = 0
1969 if found_version < version:
1970 self.config.SetString('core.repositoryFormatVersion', str(version))
1971
1972 # Enable the extension!
1973 self.config.SetString('extensions.%s' % (key,), value)
1974
Mike Frysinger50a81de2020-09-06 15:51:21 -04001975 def ResolveRemoteHead(self, name=None):
1976 """Find out what the default branch (HEAD) points to.
1977
1978 Normally this points to refs/heads/master, but projects are moving to main.
1979 Support whatever the server uses rather than hardcoding "master" ourselves.
1980 """
1981 if name is None:
1982 name = self.remote.name
1983
1984 # The output will look like (NB: tabs are separators):
1985 # ref: refs/heads/master HEAD
1986 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1987 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1988
1989 for line in output.splitlines():
1990 lhs, rhs = line.split('\t', 1)
1991 if rhs == 'HEAD' and lhs.startswith('ref:'):
1992 return lhs[4:].strip()
1993
1994 return None
1995
Zac Livingstone4332262017-06-16 08:56:09 -06001996 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001997 try:
1998 # if revision (sha or tag) is not present then following function
1999 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002000 self.bare_git.rev_list('-1', '--missing=allow-any',
2001 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00002002 if self.upstream:
2003 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
2004 self.bare_git.rev_list('-1', '--missing=allow-any',
2005 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002006 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002007 return True
2008 except GitError:
2009 # There is no such persistent revision. We have to fetch it.
2010 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002011
Julien Campergue335f5ef2013-10-16 11:02:35 +02002012 def _FetchArchive(self, tarpath, cwd=None):
2013 cmd = ['archive', '-v', '-o', tarpath]
2014 cmd.append('--remote=%s' % self.remote.url)
2015 cmd.append('--prefix=%s/' % self.relpath)
2016 cmd.append(self.revisionExpr)
2017
2018 command = GitCommand(self, cmd, cwd=cwd,
2019 capture_stdout=True,
2020 capture_stderr=True)
2021
2022 if command.Wait() != 0:
2023 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2024
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002025 def _RemoteFetch(self, name=None,
2026 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002027 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002028 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002029 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002030 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002031 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002032 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002033 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002034 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002035 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002036 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002037 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002038 clone_filter=None,
2039 retry_fetches=2,
2040 retry_sleep_initial_sec=4.0,
2041 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002042 is_sha1 = False
2043 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002044 # The depth should not be used when fetching to a mirror because
2045 # it will result in a shallow repository that cannot be cloned or
2046 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002047 # The repo project should also never be synced with partial depth.
2048 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2049 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002050
Shawn Pearce69e04d82014-01-29 12:48:54 -08002051 if depth:
2052 current_branch_only = True
2053
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002054 if ID_RE.match(self.revisionExpr) is not None:
2055 is_sha1 = True
2056
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002057 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002058 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002059 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002060 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002061 elif self.upstream and self.upstream.startswith(R_TAGS):
2062 # This is a tag and its commit id should never change.
2063 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002064
2065 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002066 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002067 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002068 print('Skipped fetching project %s (already have persistent ref)'
2069 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002070 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002071 if is_sha1 and not depth:
2072 # When syncing a specific commit and --depth is not set:
2073 # * if upstream is explicitly specified and is not a sha1, fetch only
2074 # upstream as users expect only upstream to be fetch.
2075 # Note: The commit might not be in upstream in which case the sync
2076 # will fail.
2077 # * otherwise, fetch all branches to make sure we end up with the
2078 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002079 if self.upstream:
2080 current_branch_only = not ID_RE.match(self.upstream)
2081 else:
2082 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002083
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002084 if not name:
2085 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002086
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002087 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002088 if not remote.PreConnectFetch(ssh_proxy):
2089 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002090
Shawn O. Pearce88443382010-10-08 10:02:09 +02002091 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002092 if alt_dir and 'objects' == os.path.basename(alt_dir):
2093 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002094 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002095
David Pursehouse8a68ff92012-09-24 12:15:13 +09002096 all_refs = self.bare_ref.all
2097 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002098 tmp = set()
2099
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302100 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002101 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002102 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002103 all_refs[r] = ref_id
2104 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002105 continue
2106
David Pursehouse8a68ff92012-09-24 12:15:13 +09002107 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002108 continue
2109
David Pursehouse8a68ff92012-09-24 12:15:13 +09002110 r = 'refs/_alt/%s' % ref_id
2111 all_refs[r] = ref_id
2112 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002113 tmp.add(r)
2114
heping3d7bbc92017-04-12 19:51:47 +08002115 tmp_packed_lines = []
2116 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002117
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302118 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002119 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002120 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002121 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002122 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002123
heping3d7bbc92017-04-12 19:51:47 +08002124 tmp_packed = ''.join(tmp_packed_lines)
2125 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002126 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002127 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002128 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002129
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002130 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002131
Xin Li745be2e2019-06-03 11:24:30 -07002132 if clone_filter:
2133 git_require((2, 19, 0), fail=True, msg='partial clones')
2134 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002135 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002136
Conley Owensf97e8382015-01-21 11:12:46 -08002137 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002138 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002139 else:
2140 # If this repo has shallow objects, then we don't know which refs have
2141 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2142 # do this with projects that don't have shallow objects, since it is less
2143 # efficient.
2144 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2145 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002146
Mike Frysinger4847e052020-02-22 00:07:35 -05002147 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002148 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002149 if not quiet and sys.stdout.isatty():
2150 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002151 if not self.worktree:
2152 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002153 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002154
Mike Frysingere57f1142019-03-18 21:27:54 -04002155 if force_sync:
2156 cmd.append('--force')
2157
David Pursehouse74cfd272015-10-14 10:50:15 +09002158 if prune:
2159 cmd.append('--prune')
2160
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002161 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002162
Kuang-che Wu6856f982019-11-25 12:37:55 +08002163 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002164 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002165 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002166 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002167 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002168 spec.append('tag')
2169 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002170
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302171 if self.manifest.IsMirror and not current_branch_only:
2172 branch = None
2173 else:
2174 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002175 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002176 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002177 # Shallow checkout of a specific commit, fetch from that commit and not
2178 # the heads only as the commit might be deeper in the history.
2179 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002180 if self.upstream:
2181 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002182 else:
2183 if is_sha1:
2184 branch = self.upstream
2185 if branch is not None and branch.strip():
2186 if not branch.startswith('refs/'):
2187 branch = R_HEADS + branch
2188 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2189
2190 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2191 # whole repo.
2192 if self.manifest.IsMirror and not spec:
2193 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2194
2195 # If using depth then we should not get all the tags since they may
2196 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002197 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002198 cmd.append('--no-tags')
2199 else:
2200 cmd.append('--tags')
2201 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2202
Conley Owens80b87fe2014-05-09 17:13:44 -07002203 cmd.extend(spec)
2204
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002205 # At least one retry minimum due to git remote prune.
2206 retry_fetches = max(retry_fetches, 2)
2207 retry_cur_sleep = retry_sleep_initial_sec
2208 ok = prune_tried = False
2209 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002210 gitcmd = GitCommand(
2211 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2212 ssh_proxy=ssh_proxy,
2213 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002214 if gitcmd.stdout and not quiet and output_redir:
2215 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002216 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002217 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002218 ok = True
2219 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002220
2221 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002222 elif (gitcmd.stdout and
2223 'error:' in gitcmd.stdout and
2224 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002225 # Fallthru to sleep+retry logic at the bottom.
2226 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002227
Mike Frysinger6823bc22021-04-15 02:06:28 -04002228 # Try to prune remote branches once in case there are conflicts.
2229 # For example, if the remote had refs/heads/upstream, but deleted that and
2230 # now has refs/heads/upstream/foo.
2231 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002232 'error:' in gitcmd.stdout and
2233 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002234 not prune_tried):
2235 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002236 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002237 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002238 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002239 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002240 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002241 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002242 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002243 continue
Brian Harring14a66742012-09-28 20:21:57 -07002244 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002245 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2246 # in sha1 mode, we just tried sync'ing from the upstream field; it
2247 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002248 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002249 elif ret < 0:
2250 # Git died with a signal, exit immediately
2251 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002252
2253 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002254 if not verbose and gitcmd.stdout:
2255 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002256 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002257 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2258 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002259 time.sleep(retry_cur_sleep)
2260 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2261 MAXIMUM_RETRY_SLEEP_SEC)
2262 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2263 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002264
2265 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002266 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002267 if old_packed != '':
2268 _lwrite(packed_refs, old_packed)
2269 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002270 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002271 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002272
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002273 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002274 # We just synced the upstream given branch; verify we
2275 # got what we wanted, else trigger a second run of all
2276 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002277 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002278 # Sync the current branch only with depth set to None.
2279 # We always pass depth=None down to avoid infinite recursion.
2280 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002281 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002282 current_branch_only=current_branch_only and depth,
2283 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002284 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002285
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002286 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002287
Mike Frysingere50b6a72020-02-19 01:45:48 -05002288 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002289 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002290 return False
2291
2292 remote = self.GetRemote(self.remote.name)
2293 bundle_url = remote.url + '/clone.bundle'
2294 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002295 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2296 'persistent-http',
2297 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002298 return False
2299
2300 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2301 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2302
2303 exist_dst = os.path.exists(bundle_dst)
2304 exist_tmp = os.path.exists(bundle_tmp)
2305
2306 if not initial and not exist_dst and not exist_tmp:
2307 return False
2308
2309 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002310 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2311 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002312 if not exist_dst:
2313 return False
2314
2315 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002316 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002317 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002318 if not quiet and sys.stdout.isatty():
2319 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002320 if not self.worktree:
2321 cmd.append('--update-head-ok')
2322 cmd.append(bundle_dst)
2323 for f in remote.fetch:
2324 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002325 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002326
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002327 ok = GitCommand(
2328 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002329 platform_utils.remove(bundle_dst, missing_ok=True)
2330 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002331 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002332
Mike Frysingere50b6a72020-02-19 01:45:48 -05002333 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002334 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002335
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002336 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002337 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002338 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002339 if os.path.exists(tmpPath):
2340 size = os.stat(tmpPath).st_size
2341 if size >= 1024:
2342 cmd += ['--continue-at', '%d' % (size,)]
2343 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002344 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002345 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002346 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002347 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002348 if proxy:
2349 cmd += ['--proxy', proxy]
2350 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2351 cmd += ['--proxy', os.environ['http_proxy']]
2352 if srcUrl.startswith('persistent-https'):
2353 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2354 elif srcUrl.startswith('persistent-http'):
2355 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002356 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002357
Dave Borowitz137d0132015-01-02 11:12:54 -08002358 if IsTrace():
2359 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002360 if verbose:
2361 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2362 stdout = None if verbose else subprocess.PIPE
2363 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002364 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002365 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002366 except OSError:
2367 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002368
Mike Frysingere50b6a72020-02-19 01:45:48 -05002369 (output, _) = proc.communicate()
2370 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002371
Dave Borowitz137d0132015-01-02 11:12:54 -08002372 if curlret == 22:
2373 # From curl man page:
2374 # 22: HTTP page not retrieved. The requested url was not found or
2375 # returned another error with the HTTP error code being 400 or above.
2376 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002377 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002378 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2379 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002380 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002381 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002382 elif curlret and not verbose and output:
2383 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002384
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002385 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002386 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002387 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002388 return True
2389 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002390 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002391 return False
2392 else:
2393 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002394
Kris Giesingc8d882a2014-12-23 13:02:32 -08002395 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002396 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002397 with open(path, 'rb') as f:
2398 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002399 return True
2400 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002401 if not quiet:
2402 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002403 return False
2404 except OSError:
2405 return False
2406
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002407 def _Checkout(self, rev, quiet=False):
2408 cmd = ['checkout']
2409 if quiet:
2410 cmd.append('-q')
2411 cmd.append(rev)
2412 cmd.append('--')
2413 if GitCommand(self, cmd).Wait() != 0:
2414 if self._allrefs:
2415 raise GitError('%s checkout %s ' % (self.name, rev))
2416
Mike Frysinger915fda12020-03-22 12:15:20 -04002417 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002418 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002419 if ffonly:
2420 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002421 if record_origin:
2422 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002423 cmd.append(rev)
2424 cmd.append('--')
2425 if GitCommand(self, cmd).Wait() != 0:
2426 if self._allrefs:
2427 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2428
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302429 def _LsRemote(self, refs):
2430 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302431 p = GitCommand(self, cmd, capture_stdout=True)
2432 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002433 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302434 return None
2435
Anthony King7bdac712014-07-16 12:56:40 +01002436 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002437 cmd = ['revert']
2438 cmd.append('--no-edit')
2439 cmd.append(rev)
2440 cmd.append('--')
2441 if GitCommand(self, cmd).Wait() != 0:
2442 if self._allrefs:
2443 raise GitError('%s revert %s ' % (self.name, rev))
2444
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002445 def _ResetHard(self, rev, quiet=True):
2446 cmd = ['reset', '--hard']
2447 if quiet:
2448 cmd.append('-q')
2449 cmd.append(rev)
2450 if GitCommand(self, cmd).Wait() != 0:
2451 raise GitError('%s reset --hard %s ' % (self.name, rev))
2452
Martin Kellye4e94d22017-03-21 16:05:12 -07002453 def _SyncSubmodules(self, quiet=True):
2454 cmd = ['submodule', 'update', '--init', '--recursive']
2455 if quiet:
2456 cmd.append('-q')
2457 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002458 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002459
Anthony King7bdac712014-07-16 12:56:40 +01002460 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002461 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002462 if onto is not None:
2463 cmd.extend(['--onto', onto])
2464 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002465 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002466 raise GitError('%s rebase %s ' % (self.name, upstream))
2467
Pierre Tardy3d125942012-05-04 12:18:12 +02002468 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002469 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002470 if ffonly:
2471 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002472 if GitCommand(self, cmd).Wait() != 0:
2473 raise GitError('%s merge %s ' % (self.name, head))
2474
David Pursehousee8ace262020-02-13 12:41:15 +09002475 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002476 init_git_dir = not os.path.exists(self.gitdir)
2477 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002478 try:
2479 # Initialize the bare repository, which contains all of the objects.
2480 if init_obj_dir:
2481 os.makedirs(self.objdir)
2482 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002483
Mike Frysinger333c0a42021-11-15 12:39:00 -05002484 self._UpdateHooks(quiet=quiet)
2485
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002486 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002487 # Enable per-worktree config file support if possible. This is more a
2488 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002489 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002490 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002491
Kevin Degib1a07b82015-07-27 13:33:43 -06002492 # If we have a separate directory to hold refs, initialize it as well.
2493 if self.objdir != self.gitdir:
2494 if init_git_dir:
2495 os.makedirs(self.gitdir)
2496
2497 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002498 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002499 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002500 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002501 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002502 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002503 print("Retrying clone after deleting %s" %
2504 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002505 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002506 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2507 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002508 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002509 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002510 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2511 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002512 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002513 raise e
2514 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002515
Kevin Degi384b3c52014-10-16 16:02:58 -06002516 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002517 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002518 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002519
LaMont Jonescc879a92021-11-18 22:40:18 +00002520 def _expanded_ref_dirs():
2521 """Iterate through the possible git reference directory paths."""
2522 name = self.name + '.git'
2523 yield mirror_git or os.path.join(ref_dir, name)
2524 for prefix in '', self.remote.name:
2525 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2526 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002527
LaMont Jonescc879a92021-11-18 22:40:18 +00002528 if ref_dir or mirror_git:
2529 found_ref_dir = None
2530 for path in _expanded_ref_dirs():
2531 if os.path.exists(path):
2532 found_ref_dir = path
2533 break
2534 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002535
Kevin Degib1a07b82015-07-27 13:33:43 -06002536 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002537 if not os.path.isabs(ref_dir):
2538 # The alternate directory is relative to the object database.
2539 ref_dir = os.path.relpath(ref_dir,
2540 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002541 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002542 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002543
Kevin Degib1a07b82015-07-27 13:33:43 -06002544 m = self.manifest.manifestProject.config
2545 for key in ['user.name', 'user.email']:
2546 if m.Has(key, include_defaults=False):
2547 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002548 if not self.manifest.EnableGitLfs:
2549 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2550 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002551 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002552 except Exception:
2553 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002554 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002555 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002556 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002557 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002558
David Pursehousee8ace262020-02-13 12:41:15 +09002559 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002560 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002561 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002562
David Pursehousee8ace262020-02-13 12:41:15 +09002563 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002564 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002565 if not os.path.exists(hooks):
2566 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002567
2568 # Delete sample hooks. They're noise.
2569 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002570 try:
2571 platform_utils.remove(hook, missing_ok=True)
2572 except PermissionError:
2573 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002574
Jonathan Nieder93719792015-03-17 11:29:58 -07002575 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002576 name = os.path.basename(stock_hook)
2577
Victor Boivie65e0f352011-04-18 11:23:29 +02002578 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002579 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002580 # Don't install a Gerrit Code Review hook if this
2581 # project does not appear to use it for reviews.
2582 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002583 # Since the manifest project is one of those, but also
2584 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002585 continue
2586
2587 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002588 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002589 continue
2590 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002591 # If the files are the same, we'll leave it alone. We create symlinks
2592 # below by default but fallback to hardlinks if the OS blocks them.
2593 # So if we're here, it's probably because we made a hardlink below.
2594 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002595 if not quiet:
2596 _warn("%s: Not replacing locally modified %s hook",
2597 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002598 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002599 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002600 platform_utils.symlink(
2601 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002602 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002603 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002604 try:
2605 os.link(stock_hook, dst)
2606 except OSError:
2607 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002608 else:
2609 raise
2610
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002611 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002612 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002613 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002614 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002615 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002616 remote.review = self.remote.review
2617 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002618
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002619 if self.worktree:
2620 remote.ResetFetch(mirror=False)
2621 else:
2622 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002623 remote.Save()
2624
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002625 def _InitMRef(self):
2626 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002627 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002628 # Set up the m/ space to point to the worktree-specific ref space.
2629 # We'll update the worktree-specific ref space on each checkout.
2630 ref = R_M + self.manifest.branch
2631 if not self.bare_ref.symref(ref):
2632 self.bare_git.symbolic_ref(
2633 '-m', 'redirecting to worktree scope',
2634 ref, R_WORKTREE_M + self.manifest.branch)
2635
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002636 # We can't update this ref with git worktrees until it exists.
2637 # We'll wait until the initial checkout to set it.
2638 if not os.path.exists(self.worktree):
2639 return
2640
2641 base = R_WORKTREE_M
2642 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002643
2644 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002645 else:
2646 base = R_M
2647 active_git = self.bare_git
2648
2649 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002650
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002651 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002652 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002653
Remy Böhmer1469c282020-12-15 18:49:02 +01002654 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002655 cur = self.bare_ref.symref(ref)
2656
2657 if self.revisionId:
2658 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2659 msg = 'manifest set to %s' % self.revisionId
2660 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002661 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002662 else:
2663 remote = self.GetRemote(self.remote.name)
2664 dst = remote.ToLocal(self.revisionExpr)
2665 if cur != dst:
2666 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002667 if detach:
2668 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2669 else:
2670 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002671
Mike Frysingerc72bd842021-11-14 03:58:00 -05002672 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002673 # Git worktrees don't use symlinks to share at all.
2674 if self.use_git_worktrees:
2675 return
2676
Mike Frysingerd33dce02021-12-20 18:16:33 -05002677 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002678 # Try to self-heal a bit in simple cases.
2679 dst_path = os.path.join(destdir, name)
2680 src_path = os.path.join(srcdir, name)
2681
Mike Frysingered4f2112020-02-11 23:06:29 -05002682 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002683 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002684 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002685 # Fail if the links are pointing to the wrong place
2686 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002687 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002688 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002689 'work tree. If you\'re comfortable with the '
2690 'possibility of losing the work tree\'s git metadata,'
2691 ' use `repo sync --force-sync {0}` to '
2692 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002693
Mike Frysingerc72bd842021-11-14 03:58:00 -05002694 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002695 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2696
2697 Args:
2698 gitdir: The bare git repository. Must already be initialized.
2699 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002700 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2701 This saves you the effort of initializing |dotgit| yourself.
2702 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002703 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002704 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002705
2706 to_copy = []
2707 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002708 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002709
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002710 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002711 for name in set(to_copy).union(to_symlink):
2712 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002713 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002714 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002715
Kevin Degi384b3c52014-10-16 16:02:58 -06002716 if os.path.lexists(dst):
2717 continue
David James8d201162013-10-11 17:03:19 -07002718
2719 # If the source dir doesn't exist, create an empty dir.
2720 if name in symlink_dirs and not os.path.lexists(src):
2721 os.makedirs(src)
2722
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002723 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002724 platform_utils.symlink(
2725 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002726 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002727 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002728 shutil.copytree(src, dst)
2729 elif os.path.isfile(src):
2730 shutil.copy(src, dst)
2731
David James8d201162013-10-11 17:03:19 -07002732 except OSError as e:
2733 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002734 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002735 else:
2736 raise
2737
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002738 def _InitGitWorktree(self):
2739 """Init the project using git worktrees."""
2740 self.bare_git.worktree('prune')
2741 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2742 self.worktree, self.GetRevisionId())
2743
2744 # Rewrite the internal state files to use relative paths between the
2745 # checkouts & worktrees.
2746 dotgit = os.path.join(self.worktree, '.git')
2747 with open(dotgit, 'r') as fp:
2748 # Figure out the checkout->worktree path.
2749 setting = fp.read()
2750 assert setting.startswith('gitdir:')
2751 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002752 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2753 # of file permissions. Delete it and recreate it from scratch to avoid.
2754 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002755 # Use relative path from checkout->worktree & maintain Unix line endings
2756 # on all OS's to match git behavior.
2757 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002758 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2759 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002760 # Use relative path from worktree->checkout & maintain Unix line endings
2761 # on all OS's to match git behavior.
2762 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002763 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2764
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002765 self._InitMRef()
2766
Martin Kellye4e94d22017-03-21 16:05:12 -07002767 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002768 """Setup the worktree .git path.
2769
2770 This is the user-visible path like src/foo/.git/.
2771
2772 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2773 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2774
2775 Older checkouts had .git/ directories. If we see that, migrate it.
2776
2777 This also handles changes in the manifest. Maybe this project was backed
2778 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2779 the path we point to under .repo/projects/ to match.
2780 """
2781 dotgit = os.path.join(self.worktree, '.git')
2782
2783 # If using an old layout style (a directory), migrate it.
2784 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2785 self._MigrateOldWorkTreeGitDir(dotgit)
2786
2787 init_dotgit = not os.path.exists(dotgit)
2788 if self.use_git_worktrees:
2789 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002790 self._InitGitWorktree()
2791 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002792 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002793 if not init_dotgit:
2794 # See if the project has changed.
2795 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2796 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002797
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002798 if init_dotgit or not os.path.exists(dotgit):
2799 os.makedirs(self.worktree, exist_ok=True)
2800 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002801
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002802 if init_dotgit:
2803 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002804
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002805 # Finish checking out the worktree.
2806 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2807 if GitCommand(self, cmd).Wait() != 0:
2808 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002809
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002810 if submodules:
2811 self._SyncSubmodules(quiet=True)
2812 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002813
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002814 @classmethod
2815 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2816 """Migrate the old worktree .git/ dir style to a symlink.
2817
2818 This logic specifically only uses state from |dotgit| to figure out where to
2819 move content and not |self|. This way if the backing project also changed
2820 places, we only do the .git/ dir to .git symlink migration here. The path
2821 updates will happen independently.
2822 """
2823 # Figure out where in .repo/projects/ it's pointing to.
2824 if not os.path.islink(os.path.join(dotgit, 'refs')):
2825 raise GitError(f'{dotgit}: unsupported checkout state')
2826 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2827
2828 # Remove known symlink paths that exist in .repo/projects/.
2829 KNOWN_LINKS = {
2830 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2831 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2832 }
2833 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2834 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002835 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2836 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002837 }
2838
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002839 # First see if we'd succeed before starting the migration.
2840 unknown_paths = []
2841 for name in platform_utils.listdir(dotgit):
2842 # Ignore all temporary/backup names. These are common with vim & emacs.
2843 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2844 continue
2845
2846 dotgit_path = os.path.join(dotgit, name)
2847 if name in KNOWN_LINKS:
2848 if not platform_utils.islink(dotgit_path):
2849 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2850 else:
2851 gitdir_path = os.path.join(gitdir, name)
2852 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2853 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2854 if unknown_paths:
2855 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2856
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002857 # Now walk the paths and sync the .git/ to .repo/projects/.
2858 for name in platform_utils.listdir(dotgit):
2859 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002860
2861 # Ignore all temporary/backup names. These are common with vim & emacs.
2862 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2863 platform_utils.remove(dotgit_path)
2864 elif name in KNOWN_LINKS:
2865 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002866 else:
2867 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002868 platform_utils.remove(gitdir_path, missing_ok=True)
2869 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002870
2871 # Now that the dir should be empty, clear it out, and symlink it over.
2872 platform_utils.rmdir(dotgit)
2873 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002874
Renaud Paquay788e9622017-01-27 11:41:12 -08002875 def _get_symlink_error_message(self):
2876 if platform_utils.isWindows():
2877 return ('Unable to create symbolic link. Please re-run the command as '
2878 'Administrator, or see '
2879 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2880 'for other options.')
2881 return 'filesystem must support symlinks'
2882
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002883 def _revlist(self, *args, **kw):
2884 a = []
2885 a.extend(args)
2886 a.append('--')
2887 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002888
2889 @property
2890 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002891 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002892
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002893 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002894 """Get logs between two revisions of this project."""
2895 comp = '..'
2896 if rev1:
2897 revs = [rev1]
2898 if rev2:
2899 revs.extend([comp, rev2])
2900 cmd = ['log', ''.join(revs)]
2901 out = DiffColoring(self.config)
2902 if out.is_on and color:
2903 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002904 if pretty_format is not None:
2905 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002906 if oneline:
2907 cmd.append('--oneline')
2908
2909 try:
2910 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2911 if log.Wait() == 0:
2912 return log.stdout
2913 except GitError:
2914 # worktree may not exist if groups changed for example. In that case,
2915 # try in gitdir instead.
2916 if not os.path.exists(self.worktree):
2917 return self.bare_git.log(*cmd[1:])
2918 else:
2919 raise
2920 return None
2921
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002922 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2923 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002924 """Get the list of logs from this revision to given revisionId"""
2925 logs = {}
2926 selfId = self.GetRevisionId(self._allrefs)
2927 toId = toProject.GetRevisionId(toProject._allrefs)
2928
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002929 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2930 pretty_format=pretty_format)
2931 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2932 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002933 return logs
2934
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002935 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002936
David James8d201162013-10-11 17:03:19 -07002937 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002938 self._project = project
2939 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002940 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002941
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002942 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2943 def __getstate__(self):
2944 return (self._project, self._bare, self._gitdir)
2945
2946 def __setstate__(self, state):
2947 self._project, self._bare, self._gitdir = state
2948
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002949 def LsOthers(self):
2950 p = GitCommand(self._project,
2951 ['ls-files',
2952 '-z',
2953 '--others',
2954 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002955 bare=False,
David James8d201162013-10-11 17:03:19 -07002956 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002957 capture_stdout=True,
2958 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002959 if p.Wait() == 0:
2960 out = p.stdout
2961 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002962 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002963 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002964 return []
2965
2966 def DiffZ(self, name, *args):
2967 cmd = [name]
2968 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002969 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002970 cmd.extend(args)
2971 p = GitCommand(self._project,
2972 cmd,
David James8d201162013-10-11 17:03:19 -07002973 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002974 bare=False,
2975 capture_stdout=True,
2976 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002977 p.Wait()
2978 r = {}
2979 out = p.stdout
2980 if out:
2981 out = iter(out[:-1].split('\0'))
2982 while out:
2983 try:
2984 info = next(out)
2985 path = next(out)
2986 except StopIteration:
2987 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002988
Mike Frysinger84230002021-02-16 17:08:35 -05002989 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002990
Mike Frysinger84230002021-02-16 17:08:35 -05002991 def __init__(self, path, omode, nmode, oid, nid, state):
2992 self.path = path
2993 self.src_path = None
2994 self.old_mode = omode
2995 self.new_mode = nmode
2996 self.old_id = oid
2997 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002998
Mike Frysinger84230002021-02-16 17:08:35 -05002999 if len(state) == 1:
3000 self.status = state
3001 self.level = None
3002 else:
3003 self.status = state[:1]
3004 self.level = state[1:]
3005 while self.level.startswith('0'):
3006 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003007
Mike Frysinger84230002021-02-16 17:08:35 -05003008 info = info[1:].split(' ')
3009 info = _Info(path, *info)
3010 if info.status in ('R', 'C'):
3011 info.src_path = info.path
3012 info.path = next(out)
3013 r[info.path] = info
3014 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003015
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003016 def GetDotgitPath(self, subpath=None):
3017 """Return the full path to the .git dir.
3018
3019 As a convenience, append |subpath| if provided.
3020 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003021 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003022 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003023 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003024 dotgit = os.path.join(self._project.worktree, '.git')
3025 if os.path.isfile(dotgit):
3026 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3027 with open(dotgit) as fp:
3028 setting = fp.read()
3029 assert setting.startswith('gitdir:')
3030 gitdir = setting.split(':', 1)[1].strip()
3031 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3032
3033 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3034
3035 def GetHead(self):
3036 """Return the ref that HEAD points to."""
3037 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003038 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003039 with open(path) as fd:
3040 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003041 except IOError as e:
3042 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003043 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303044 line = line.decode()
3045 except AttributeError:
3046 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003047 if line.startswith('ref: '):
3048 return line[5:-1]
3049 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003050
3051 def SetHead(self, ref, message=None):
3052 cmdv = []
3053 if message is not None:
3054 cmdv.extend(['-m', message])
3055 cmdv.append(HEAD)
3056 cmdv.append(ref)
3057 self.symbolic_ref(*cmdv)
3058
3059 def DetachHead(self, new, message=None):
3060 cmdv = ['--no-deref']
3061 if message is not None:
3062 cmdv.extend(['-m', message])
3063 cmdv.append(HEAD)
3064 cmdv.append(new)
3065 self.update_ref(*cmdv)
3066
3067 def UpdateRef(self, name, new, old=None,
3068 message=None,
3069 detach=False):
3070 cmdv = []
3071 if message is not None:
3072 cmdv.extend(['-m', message])
3073 if detach:
3074 cmdv.append('--no-deref')
3075 cmdv.append(name)
3076 cmdv.append(new)
3077 if old is not None:
3078 cmdv.append(old)
3079 self.update_ref(*cmdv)
3080
3081 def DeleteRef(self, name, old=None):
3082 if not old:
3083 old = self.rev_parse(name)
3084 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003085 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003086
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003087 def rev_list(self, *args, **kw):
3088 if 'format' in kw:
3089 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3090 else:
3091 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003092 cmdv.extend(args)
3093 p = GitCommand(self._project,
3094 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003095 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003096 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003097 capture_stdout=True,
3098 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003099 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003100 raise GitError('%s rev-list %s: %s' %
3101 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003102 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003103
3104 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003105 """Allow arbitrary git commands using pythonic syntax.
3106
3107 This allows you to do things like:
3108 git_obj.rev_parse('HEAD')
3109
3110 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3111 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003112 Any other positional arguments will be passed to the git command, and the
3113 following keyword arguments are supported:
3114 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003115
3116 Args:
3117 name: The name of the git command to call. Any '_' characters will
3118 be replaced with '-'.
3119
3120 Returns:
3121 A callable object that will try to call git with the named command.
3122 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003123 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003124
Dave Borowitz091f8932012-10-23 17:01:04 -07003125 def runner(*args, **kwargs):
3126 cmdv = []
3127 config = kwargs.pop('config', None)
3128 for k in kwargs:
3129 raise TypeError('%s() got an unexpected keyword argument %r'
3130 % (name, k))
3131 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303132 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003133 cmdv.append('-c')
3134 cmdv.append('%s=%s' % (k, v))
3135 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003136 cmdv.extend(args)
3137 p = GitCommand(self._project,
3138 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003139 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003140 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003141 capture_stdout=True,
3142 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003143 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003144 raise GitError('%s %s: %s' %
3145 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003146 r = p.stdout
3147 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3148 return r[:-1]
3149 return r
3150 return runner
3151
3152
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003153class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003154
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003155 def __str__(self):
3156 return 'prior sync failed; rebase still in progress'
3157
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003158
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003159class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003160
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003161 def __str__(self):
3162 return 'contains uncommitted changes'
3163
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003164
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003165class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003166
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003167 def __init__(self, project, text):
3168 self.project = project
3169 self.text = text
3170
3171 def Print(self, syncbuf):
3172 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3173 syncbuf.out.nl()
3174
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003175
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003176class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003177
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003178 def __init__(self, project, why):
3179 self.project = project
3180 self.why = why
3181
3182 def Print(self, syncbuf):
3183 syncbuf.out.fail('error: %s/: %s',
3184 self.project.relpath,
3185 str(self.why))
3186 syncbuf.out.nl()
3187
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003188
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003189class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003190
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003191 def __init__(self, project, action):
3192 self.project = project
3193 self.action = action
3194
3195 def Run(self, syncbuf):
3196 out = syncbuf.out
3197 out.project('project %s/', self.project.relpath)
3198 out.nl()
3199 try:
3200 self.action()
3201 out.nl()
3202 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003203 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003204 out.nl()
3205 return False
3206
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003207
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003208class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003209
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003210 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003211 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003212 self.project = self.printer('header', attr='bold')
3213 self.info = self.printer('info')
3214 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003215
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003216
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003217class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003218
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003219 def __init__(self, config, detach_head=False):
3220 self._messages = []
3221 self._failures = []
3222 self._later_queue1 = []
3223 self._later_queue2 = []
3224
3225 self.out = _SyncColoring(config)
3226 self.out.redirect(sys.stderr)
3227
3228 self.detach_head = detach_head
3229 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003230 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003231
3232 def info(self, project, fmt, *args):
3233 self._messages.append(_InfoMessage(project, fmt % args))
3234
3235 def fail(self, project, err=None):
3236 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003237 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003238
3239 def later1(self, project, what):
3240 self._later_queue1.append(_Later(project, what))
3241
3242 def later2(self, project, what):
3243 self._later_queue2.append(_Later(project, what))
3244
3245 def Finish(self):
3246 self._PrintMessages()
3247 self._RunLater()
3248 self._PrintMessages()
3249 return self.clean
3250
David Rileye0684ad2017-04-05 00:02:59 -07003251 def Recently(self):
3252 recent_clean = self.recent_clean
3253 self.recent_clean = True
3254 return recent_clean
3255
3256 def _MarkUnclean(self):
3257 self.clean = False
3258 self.recent_clean = False
3259
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003260 def _RunLater(self):
3261 for q in ['_later_queue1', '_later_queue2']:
3262 if not self._RunQueue(q):
3263 return
3264
3265 def _RunQueue(self, queue):
3266 for m in getattr(self, queue):
3267 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003268 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003269 return False
3270 setattr(self, queue, [])
3271 return True
3272
3273 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003274 if self._messages or self._failures:
3275 if os.isatty(2):
3276 self.out.write(progress.CSI_ERASE_LINE)
3277 self.out.write('\r')
3278
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003279 for m in self._messages:
3280 m.Print(self)
3281 for m in self._failures:
3282 m.Print(self)
3283
3284 self._messages = []
3285 self._failures = []
3286
3287
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003288class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003289 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003290
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003291 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003292 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003293 manifest=manifest,
3294 name=name,
3295 gitdir=gitdir,
3296 objdir=gitdir,
3297 worktree=worktree,
3298 remote=RemoteSpec('origin'),
3299 relpath='.repo/%s' % name,
3300 revisionExpr='refs/heads/master',
3301 revisionId=None,
3302 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003303
3304 def PreSync(self):
3305 if self.Exists:
3306 cb = self.CurrentBranch
3307 if cb:
3308 base = self.GetBranch(cb).merge
3309 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003310 self.revisionExpr = base
3311 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003312
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003313 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003314 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003315 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003316 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003317 return False
3318
David Pursehouse8a68ff92012-09-24 12:15:13 +09003319 all_refs = self.bare_ref.all
3320 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003321 head = self.work_git.GetHead()
3322 if head.startswith(R_HEADS):
3323 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003324 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003325 except KeyError:
3326 head = None
3327
3328 if revid == head:
3329 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003330 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003331 return True
3332 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003333
3334
3335class RepoProject(MetaProject):
3336 """The MetaProject for repo itself."""
3337
3338 @property
3339 def LastFetch(self):
3340 try:
3341 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3342 return os.path.getmtime(fh)
3343 except OSError:
3344 return 0
3345
3346class ManifestProject(MetaProject):
3347 """The MetaProject for manifests."""
3348
3349 def MetaBranchSwitch(self, submodules=False):
3350 """Prepare for manifest branch switch."""
3351
3352 # detach and delete manifest branch, allowing a new
3353 # branch to take over
3354 syncbuf = SyncBuffer(self.config, detach_head=True)
3355 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3356 syncbuf.Finish()
3357
3358 return GitCommand(self,
3359 ['update-ref', '-d', 'refs/heads/default'],
3360 capture_stdout=True,
3361 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003362
3363 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003364 def standalone_manifest_url(self):
3365 """The URL of the standalone manifest, or None."""
LaMont Jones55ee3042022-04-06 17:10:21 +00003366 return self.config.GetString('manifest.standalone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003367
3368 @property
3369 def manifest_groups(self):
3370 """The manifest groups string."""
3371 return self.config.GetString('manifest.groups')
3372
3373 @property
3374 def reference(self):
3375 """The --reference for this manifest."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003376 return self.config.GetString('repo.reference')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003377
3378 @property
3379 def dissociate(self):
3380 """Whether to dissociate."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003381 return self.config.GetBoolean('repo.dissociate')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003382
3383 @property
3384 def archive(self):
3385 """Whether we use archive."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003386 return self.config.GetBoolean('repo.archive')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003387
3388 @property
3389 def mirror(self):
3390 """Whether we use mirror."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003391 return self.config.GetBoolean('repo.mirror')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003392
3393 @property
3394 def use_worktree(self):
3395 """Whether we use worktree."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003396 return self.config.GetBoolean('repo.worktree')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003397
3398 @property
3399 def clone_bundle(self):
3400 """Whether we use clone_bundle."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003401 return self.config.GetBoolean('repo.clonebundle')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003402
3403 @property
3404 def submodules(self):
3405 """Whether we use submodules."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003406 return self.config.GetBoolean('repo.submodules')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003407
3408 @property
3409 def git_lfs(self):
3410 """Whether we use git_lfs."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003411 return self.config.GetBoolean('repo.git-lfs')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003412
3413 @property
3414 def use_superproject(self):
3415 """Whether we use superproject."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003416 return self.config.GetBoolean('repo.superproject')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003417
3418 @property
3419 def partial_clone(self):
3420 """Whether this is a partial clone."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003421 return self.config.GetBoolean('repo.partialclone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003422
3423 @property
3424 def depth(self):
3425 """Partial clone depth."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003426 return self.config.GetString('repo.depth')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003427
3428 @property
3429 def clone_filter(self):
3430 """The clone filter."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003431 return self.config.GetString('repo.clonefilter')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003432
3433 @property
3434 def partial_clone_exclude(self):
3435 """Partial clone exclude string"""
LaMont Jones4ada0432022-04-14 15:10:43 +00003436 return self.config.GetBoolean('repo.partialcloneexclude')
3437
3438 @property
3439 def manifest_platform(self):
3440 """The --platform argument from `repo init`."""
3441 return self.config.GetString('manifest.platform')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003442
3443 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003444 def _platform_name(self):
3445 """Return the name of the platform."""
3446 return platform.system().lower()
3447
3448 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003449 standalone_manifest=False, groups='', mirror=False, reference='',
3450 dissociate=False, worktree=False, submodules=False, archive=False,
3451 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003452 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3453 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jones55ee3042022-04-06 17:10:21 +00003454 git_event_log=None, platform='', manifest_name='default.xml',
3455 tags='', this_manifest_only=False, outer_manifest=True):
LaMont Jones9b03f152022-03-29 23:01:18 +00003456 """Sync the manifest and all submanifests.
3457
3458 Args:
3459 manifest_url: a string, the URL of the manifest project.
3460 manifest_branch: a string, the manifest branch to use.
3461 standalone_manifest: a boolean, whether to store the manifest as a static
3462 file.
3463 groups: a string, restricts the checkout to projects with the specified
3464 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003465 mirror: a boolean, whether to create a mirror of the remote repository.
3466 reference: a string, location of a repo instance to use as a reference.
3467 dissociate: a boolean, whether to dissociate from reference mirrors after
3468 clone.
3469 worktree: a boolean, whether to use git-worktree to manage projects.
3470 submodules: a boolean, whether sync submodules associated with the
3471 manifest project.
3472 archive: a boolean, whether to checkout each project as an archive. See
3473 git-archive.
3474 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003475 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003476 clone_filter: a string, filter to use with partial_clone.
3477 partial_clone_exclude : a string, comma-delimeted list of project namess
3478 to exclude from partial clone.
3479 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3480 git_lfs: a boolean, whether to enable git LFS support.
3481 use_superproject: a boolean, whether to use the manifest superproject to
3482 sync projects.
3483 verbose: a boolean, whether to show all output, rather than only errors.
3484 current_branch_only: a boolean, whether to only fetch the current manifest
3485 branch from the server.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003486 platform: a string, restrict the checkout to projects with the specified
3487 platform group.
LaMont Jones55ee3042022-04-06 17:10:21 +00003488 git_event_log: an EventLog, for git tracing.
LaMont Jones4ada0432022-04-14 15:10:43 +00003489 tags: a boolean, whether to fetch tags.
LaMont Jones409407a2022-04-05 21:21:56 +00003490 manifest_name: a string, the name of the manifest file to use.
3491 this_manifest_only: a boolean, whether to only operate on the current sub
3492 manifest.
3493 outer_manifest: a boolean, whether to start at the outermost manifest.
LaMont Jones9b03f152022-03-29 23:01:18 +00003494
3495 Returns:
3496 a boolean, whether the sync was successful.
3497 """
3498 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3499
LaMont Jones501733c2022-04-20 16:42:32 +00003500 groups = groups or self.manifest.GetDefaultGroupsStr(with_platform=False)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003501 platform = platform or 'auto'
LaMont Jones55ee3042022-04-06 17:10:21 +00003502 git_event_log = git_event_log or EventLog()
LaMont Jones409407a2022-04-05 21:21:56 +00003503 if outer_manifest and self.manifest.is_submanifest:
3504 # In a multi-manifest checkout, use the outer manifest unless we are told
3505 # not to.
3506 return self.client.outer_manifest.manifestProject.Sync(
3507 manifest_url=manifest_url,
3508 manifest_branch=manifest_branch,
3509 standalone_manifest=standalone_manifest,
3510 groups=groups,
3511 platform=platform,
3512 mirror=mirror,
3513 dissociate=dissociate,
3514 reference=reference,
3515 worktree=worktree,
3516 submodules=submodules,
3517 archive=archive,
3518 partial_clone=partial_clone,
3519 clone_filter=clone_filter,
3520 partial_clone_exclude=partial_clone_exclude,
3521 clone_bundle=clone_bundle,
3522 git_lfs=git_lfs,
3523 use_superproject=use_superproject,
3524 verbose=verbose,
3525 current_branch_only=current_branch_only,
3526 tags=tags,
3527 depth=depth,
LaMont Jones55ee3042022-04-06 17:10:21 +00003528 git_event_log=git_event_log,
LaMont Jones409407a2022-04-05 21:21:56 +00003529 manifest_name=manifest_name,
3530 this_manifest_only=this_manifest_only,
3531 outer_manifest=False)
3532
LaMont Jones9b03f152022-03-29 23:01:18 +00003533 # If repo has already been initialized, we take -u with the absence of
3534 # --standalone-manifest to mean "transition to a standard repo set up",
3535 # which necessitates starting fresh.
3536 # If --standalone-manifest is set, we always tear everything down and start
3537 # anew.
3538 if self.Exists:
3539 was_standalone_manifest = self.config.GetString('manifest.standalone')
3540 if was_standalone_manifest and not manifest_url:
3541 print('fatal: repo was initialized with a standlone manifest, '
3542 'cannot be re-initialized without --manifest-url/-u')
3543 return False
3544
3545 if standalone_manifest or (was_standalone_manifest and manifest_url):
3546 self.config.ClearCache()
3547 if self.gitdir and os.path.exists(self.gitdir):
3548 platform_utils.rmtree(self.gitdir)
3549 if self.worktree and os.path.exists(self.worktree):
3550 platform_utils.rmtree(self.worktree)
3551
3552 is_new = not self.Exists
3553 if is_new:
3554 if not manifest_url:
3555 print('fatal: manifest url is required.', file=sys.stderr)
3556 return False
3557
LaMont Jones409407a2022-04-05 21:21:56 +00003558 if verbose:
LaMont Jones9b03f152022-03-29 23:01:18 +00003559 print('Downloading manifest from %s' %
3560 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3561 file=sys.stderr)
3562
3563 # The manifest project object doesn't keep track of the path on the
3564 # server where this git is located, so let's save that here.
3565 mirrored_manifest_git = None
3566 if reference:
3567 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3568 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3569 if not mirrored_manifest_git.endswith(".git"):
3570 mirrored_manifest_git += ".git"
3571 if not os.path.exists(mirrored_manifest_git):
3572 mirrored_manifest_git = os.path.join(reference,
3573 '.repo/manifests.git')
3574
3575 self._InitGitDir(mirror_git=mirrored_manifest_git)
3576
3577 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3578 # still do much of the manifests.git set up, but will avoid actual syncs to
3579 # a remote.
3580 if standalone_manifest:
3581 self.config.SetString('manifest.standalone', manifest_url)
3582 elif not manifest_url and not manifest_branch:
3583 # If -u is set and --standalone-manifest is not, then we're not in
3584 # standalone mode. Otherwise, use config to infer what we were in the last
3585 # init.
3586 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3587 if not standalone_manifest:
3588 self.config.SetString('manifest.standalone', None)
3589
3590 self._ConfigureDepth(depth)
3591
3592 # Set the remote URL before the remote branch as we might need it below.
3593 if manifest_url:
3594 r = self.GetRemote(self.remote.name)
3595 r.url = manifest_url
3596 r.ResetFetch()
3597 r.Save()
3598
3599 if not standalone_manifest:
3600 if manifest_branch:
3601 if manifest_branch == 'HEAD':
3602 manifest_branch = self.ResolveRemoteHead()
3603 if manifest_branch is None:
3604 print('fatal: unable to resolve HEAD', file=sys.stderr)
3605 return False
3606 self.revisionExpr = manifest_branch
3607 else:
3608 if is_new:
3609 default_branch = self.ResolveRemoteHead()
3610 if default_branch is None:
3611 # If the remote doesn't have HEAD configured, default to master.
3612 default_branch = 'refs/heads/master'
3613 self.revisionExpr = default_branch
3614 else:
3615 self.PreSync()
3616
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003617 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003618 all_platforms = ['linux', 'darwin', 'windows']
3619 platformize = lambda x: 'platform-' + x
3620 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003621 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003622 groups.append(platformize(self._platform_name))
3623 elif platform == 'all':
3624 groups.extend(map(platformize, all_platforms))
3625 elif platform in all_platforms:
3626 groups.append(platformize(platform))
3627 elif platform != 'none':
3628 print('fatal: invalid platform flag', file=sys.stderr)
3629 return False
LaMont Jones4ada0432022-04-14 15:10:43 +00003630 self.config.SetString('manifest.platform', platform)
LaMont Jones9b03f152022-03-29 23:01:18 +00003631
3632 groups = [x for x in groups if x]
3633 groupstr = ','.join(groups)
3634 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3635 groupstr = None
3636 self.config.SetString('manifest.groups', groupstr)
3637
3638 if reference:
3639 self.config.SetString('repo.reference', reference)
3640
3641 if dissociate:
3642 self.config.SetBoolean('repo.dissociate', dissociate)
3643
3644 if worktree:
3645 if mirror:
3646 print('fatal: --mirror and --worktree are incompatible',
3647 file=sys.stderr)
3648 return False
3649 if submodules:
3650 print('fatal: --submodules and --worktree are incompatible',
3651 file=sys.stderr)
3652 return False
3653 self.config.SetBoolean('repo.worktree', worktree)
3654 if is_new:
3655 self.use_git_worktrees = True
3656 print('warning: --worktree is experimental!', file=sys.stderr)
3657
3658 if archive:
3659 if is_new:
3660 self.config.SetBoolean('repo.archive', archive)
3661 else:
3662 print('fatal: --archive is only supported when initializing a new '
3663 'workspace.', file=sys.stderr)
3664 print('Either delete the .repo folder in this workspace, or initialize '
3665 'in another location.', file=sys.stderr)
3666 return False
3667
3668 if mirror:
3669 if is_new:
3670 self.config.SetBoolean('repo.mirror', mirror)
3671 else:
3672 print('fatal: --mirror is only supported when initializing a new '
3673 'workspace.', file=sys.stderr)
3674 print('Either delete the .repo folder in this workspace, or initialize '
3675 'in another location.', file=sys.stderr)
3676 return False
3677
3678 if partial_clone is not None:
3679 if mirror:
3680 print('fatal: --mirror and --partial-clone are mutually exclusive',
3681 file=sys.stderr)
3682 return False
3683 self.config.SetBoolean('repo.partialclone', partial_clone)
3684 if clone_filter:
3685 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jones55ee3042022-04-06 17:10:21 +00003686 elif self.partial_clone:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003687 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003688 else:
3689 clone_filter = None
3690
3691 if partial_clone_exclude is not None:
3692 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3693
3694 if clone_bundle is None:
3695 clone_bundle = False if partial_clone else True
3696 else:
3697 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3698
3699 if submodules:
3700 self.config.SetBoolean('repo.submodules', submodules)
3701
3702 if git_lfs is not None:
3703 if git_lfs:
3704 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3705
3706 self.config.SetBoolean('repo.git-lfs', git_lfs)
3707 if not is_new:
3708 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3709 ' Existing projects will require manual updates.\n', file=sys.stderr)
3710
3711 if use_superproject is not None:
3712 self.config.SetBoolean('repo.superproject', use_superproject)
3713
LaMont Jones0165e202022-04-27 17:34:42 +00003714 if not standalone_manifest:
3715 if not self.Sync_NetworkHalf(
3716 is_new=is_new, quiet=not verbose, verbose=verbose,
3717 clone_bundle=clone_bundle, current_branch_only=current_branch_only,
3718 tags=tags, submodules=submodules, clone_filter=clone_filter,
3719 partial_clone_exclude=self.manifest.PartialCloneExclude):
3720 r = self.GetRemote(self.remote.name)
3721 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
LaMont Jones9b03f152022-03-29 23:01:18 +00003722
LaMont Jones0165e202022-04-27 17:34:42 +00003723 # Better delete the manifest git dir if we created it; otherwise next
3724 # time (when user fixes problems) we won't go through the "is_new" logic.
3725 if is_new:
3726 platform_utils.rmtree(self.gitdir)
LaMont Jones9b03f152022-03-29 23:01:18 +00003727 return False
3728
LaMont Jones0165e202022-04-27 17:34:42 +00003729 if manifest_branch:
3730 self.MetaBranchSwitch(submodules=submodules)
3731
3732 syncbuf = SyncBuffer(self.config)
3733 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3734 syncbuf.Finish()
3735
3736 if is_new or self.CurrentBranch is None:
3737 if not self.StartBranch('default'):
3738 print('fatal: cannot create default in manifest', file=sys.stderr)
3739 return False
3740
3741 if not manifest_name:
3742 print('fatal: manifest name (-m) is required.', file=sys.stderr)
3743 return False
3744
3745 elif is_new:
3746 # This is a new standalone manifest.
3747 manifest_name = 'default.xml'
3748 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3749 dest = os.path.join(self.worktree, manifest_name)
3750 os.makedirs(os.path.dirname(dest), exist_ok=True)
3751 with open(dest, 'wb') as f:
3752 f.write(manifest_data)
LaMont Jones409407a2022-04-05 21:21:56 +00003753
3754 try:
3755 self.manifest.Link(manifest_name)
3756 except ManifestParseError as e:
3757 print("fatal: manifest '%s' not available" % manifest_name,
3758 file=sys.stderr)
3759 print('fatal: %s' % str(e), file=sys.stderr)
3760 return False
3761
LaMont Jones55ee3042022-04-06 17:10:21 +00003762 if not this_manifest_only:
3763 for submanifest in self.manifest.submanifests.values():
LaMont Jonesb90a4222022-04-14 15:00:09 +00003764 spec = submanifest.ToSubmanifestSpec()
LaMont Jones55ee3042022-04-06 17:10:21 +00003765 submanifest.repo_client.manifestProject.Sync(
3766 manifest_url=spec.manifestUrl,
3767 manifest_branch=spec.revision,
3768 standalone_manifest=standalone_manifest,
3769 groups=self.manifest_groups,
3770 platform=platform,
3771 mirror=mirror,
3772 dissociate=dissociate,
3773 reference=reference,
3774 worktree=worktree,
3775 submodules=submodules,
3776 archive=archive,
3777 partial_clone=partial_clone,
3778 clone_filter=clone_filter,
3779 partial_clone_exclude=partial_clone_exclude,
3780 clone_bundle=clone_bundle,
3781 git_lfs=git_lfs,
3782 use_superproject=use_superproject,
3783 verbose=verbose,
3784 current_branch_only=current_branch_only,
3785 tags=tags,
3786 depth=depth,
3787 git_event_log=git_event_log,
3788 manifest_name=spec.manifestName,
3789 this_manifest_only=False,
3790 outer_manifest=False,
3791 )
LaMont Jones409407a2022-04-05 21:21:56 +00003792
LaMont Jones55ee3042022-04-06 17:10:21 +00003793 # Lastly, clone the superproject(s).
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003794 if self.manifest.manifestProject.use_superproject:
3795 sync_result = Superproject(
3796 self.manifest, self.manifest.repodir, git_event_log, quiet=not verbose).Sync()
3797 if not sync_result.success:
3798 print('warning: git update of superproject for '
3799 f'{self.manifest.path_prefix} failed, repo sync will not use '
3800 'superproject to fetch source; while this error is not fatal, '
3801 'and you can continue to run repo sync, please run repo init '
3802 'with the --no-use-superproject option to stop seeing this '
3803 'warning', file=sys.stderr)
3804 if sync_result.fatal and use_superproject is not None:
3805 return False
LaMont Jones409407a2022-04-05 21:21:56 +00003806
LaMont Jones9b03f152022-03-29 23:01:18 +00003807 return True
3808
3809 def _ConfigureDepth(self, depth):
3810 """Configure the depth we'll sync down.
3811
3812 Args:
3813 depth: an int, how deep of a partial clone to create.
3814 """
3815 # Opt.depth will be non-None if user actually passed --depth to repo init.
3816 if depth is not None:
3817 if depth > 0:
3818 # Positive values will set the depth.
3819 depth = str(depth)
3820 else:
3821 # Negative numbers will clear the depth; passing None to SetString
3822 # will do that.
3823 depth = None
3824
3825 # We store the depth in the main manifest project.
3826 self.config.SetString('repo.depth', depth)