blob: 8fed8f5ed2f79482a625e8be61d4f76cf7c97393 [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 Jonesff6b1da2022-06-01 21:03:34 +000036import git_superproject
LaMont Jones55ee3042022-04-06 17:10:21 +000037from git_trace2_event_log import EventLog
Remy Bohmer16c13282020-09-10 10:38:04 +020038from error import GitError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040039from error import ManifestInvalidRevisionError, ManifestInvalidPathError
LaMont Jones409407a2022-04-05 21:21:56 +000040from error import NoManifestException, ManifestParseError
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070041import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040042import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040043from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050045from 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 -070046
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070047
George Engelbrecht9bc283e2020-04-02 12:36:09 -060048# Maximum sleep time allowed during retries.
49MAXIMUM_RETRY_SLEEP_SEC = 3600.0
50# +-10% random jitter is added to each Fetches retry sleep duration.
51RETRY_JITTER_PERCENT = 0.1
52
Mike Frysinger1d00a7e2021-12-21 00:40:31 -050053# Whether to use alternates.
54# TODO(vapier): Remove knob once behavior is verified.
55_ALTERNATES = os.environ.get('REPO_USE_ALTERNATES') == '1'
George Engelbrecht9bc283e2020-04-02 12:36:09 -060056
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070057def _lwrite(path, content):
58 lock = '%s.lock' % path
59
Remy Bohmer169b0212020-11-21 10:57:52 +010060 # Maintain Unix line endings on all OS's to match git behavior.
61 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070063
64 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070065 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070066 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080067 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068 raise
69
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070070
Shawn O. Pearce48244782009-04-16 08:25:57 -070071def _error(fmt, *args):
72 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070073 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070074
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070075
David Pursehousef33929d2015-08-24 14:39:14 +090076def _warn(fmt, *args):
77 msg = fmt % args
78 print('warn: %s' % msg, file=sys.stderr)
79
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081def not_rev(r):
82 return '^' + r
83
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080085def sq(r):
86 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087
David Pursehouse819827a2020-02-12 15:20:19 +090088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700106 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700107 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700108 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400138 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139
140 def __init__(self, project, branch, base):
141 self.project = project
142 self.branch = branch
143 self.base = base
144
145 @property
146 def name(self):
147 return self.branch.name
148
149 @property
150 def commits(self):
151 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400152 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
153 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
154 try:
155 self._commit_cache = self.project.bare_git.rev_list(*args)
156 except GitError:
157 # We weren't able to probe the commits for this branch. Was it tracking
158 # a branch that no longer exists? If so, return no commits. Otherwise,
159 # rethrow the error as we don't know what's going on.
160 if self.base_exists:
161 raise
162
163 self._commit_cache = []
164
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 return self._commit_cache
166
167 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800168 def unabbrev_commits(self):
169 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700170 for commit in self.project.bare_git.rev_list(not_rev(self.base),
171 R_HEADS + self.name,
172 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800173 r[commit[0:8]] = commit
174 return r
175
176 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700178 return self.project.bare_git.log('--pretty=format:%cd',
179 '-n', '1',
180 R_HEADS + self.name,
181 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182
Mike Frysinger6da17752019-09-11 18:43:17 -0400183 @property
184 def base_exists(self):
185 """Whether the branch we're tracking exists.
186
187 Normally it should, but sometimes branches we track can get deleted.
188 """
189 if self._base_exists is None:
190 try:
191 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
192 # If we're still here, the base branch exists.
193 self._base_exists = True
194 except GitError:
195 # If we failed to verify, the base branch doesn't exist.
196 self._base_exists = False
197
198 return self._base_exists
199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700200 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500201 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500203 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500204 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200205 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700206 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200207 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200208 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800209 validate_certs=True,
210 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500211 self.project.UploadForReview(branch=self.name,
212 people=people,
213 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700214 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500215 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500216 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200217 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700218 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200219 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200220 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800221 validate_certs=validate_certs,
222 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700224 def GetPublishedRefs(self):
225 refs = {}
226 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700227 self.branch.remote.SshReviewUrl(self.project.UserEmail),
228 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700229 for line in output.split('\n'):
230 try:
231 (sha, ref) = line.split()
232 refs[sha] = ref
233 except ValueError:
234 pass
235
236 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500242 super().__init__(config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100243 self.project = self.printer('header', attr='bold')
244 self.branch = self.printer('header', attr='bold')
245 self.nobranch = self.printer('nobranch', fg='red')
246 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247
Anthony King7bdac712014-07-16 12:56:40 +0100248 self.added = self.printer('added', fg='green')
249 self.changed = self.printer('changed', fg='red')
250 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251
252
253class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700254
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500256 super().__init__(config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100257 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400258 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700260
Jack Neus6ea0cae2021-07-20 20:52:33 +0000261class Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
James W. Mills24c13082012-04-12 15:04:13 -0500263 def __init__(self, name, value, keep):
264 self.name = name
265 self.value = value
266 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700267
Jack Neus6ea0cae2021-07-20 20:52:33 +0000268 def __eq__(self, other):
269 if not isinstance(other, Annotation):
270 return False
271 return self.__dict__ == other.__dict__
272
273 def __lt__(self, other):
274 # This exists just so that lists of Annotation objects can be sorted, for
275 # use in comparisons.
276 if not isinstance(other, Annotation):
277 raise ValueError('comparison is not between two Annotation objects')
278 if self.name == other.name:
279 if self.value == other.value:
280 return self.keep < other.keep
281 return self.value < other.value
282 return self.name < other.name
283
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700284
Mike Frysingere6a202f2019-08-02 15:57:57 -0400285def _SafeExpandPath(base, subpath, skipfinal=False):
286 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700287
Mike Frysingere6a202f2019-08-02 15:57:57 -0400288 We make sure no intermediate symlinks are traversed, and that the final path
289 is not a special file (e.g. not a socket or fifo).
290
291 NB: We rely on a number of paths already being filtered out while parsing the
292 manifest. See the validation logic in manifest_xml.py for more details.
293 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500294 # Split up the path by its components. We can't use os.path.sep exclusively
295 # as some platforms (like Windows) will convert / to \ and that bypasses all
296 # our constructed logic here. Especially since manifest authors only use
297 # / in their paths.
298 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
299 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400300 if skipfinal:
301 # Whether the caller handles the final component itself.
302 finalpart = components.pop()
303
304 path = base
305 for part in components:
306 if part in {'.', '..'}:
307 raise ManifestInvalidPathError(
308 '%s: "%s" not allowed in paths' % (subpath, part))
309
310 path = os.path.join(path, part)
311 if platform_utils.islink(path):
312 raise ManifestInvalidPathError(
313 '%s: traversing symlinks not allow' % (path,))
314
315 if os.path.exists(path):
316 if not os.path.isfile(path) and not platform_utils.isdir(path):
317 raise ManifestInvalidPathError(
318 '%s: only regular files & directories allowed' % (path,))
319
320 if skipfinal:
321 path = os.path.join(path, finalpart)
322
323 return path
324
325
326class _CopyFile(object):
327 """Container for <copyfile> manifest element."""
328
329 def __init__(self, git_worktree, src, topdir, dest):
330 """Register a <copyfile> request.
331
332 Args:
333 git_worktree: Absolute path to the git project checkout.
334 src: Relative path under |git_worktree| of file to read.
335 topdir: Absolute path to the top of the repo client checkout.
336 dest: Relative path under |topdir| of file to write.
337 """
338 self.git_worktree = git_worktree
339 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700340 self.src = src
341 self.dest = dest
342
343 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400344 src = _SafeExpandPath(self.git_worktree, self.src)
345 dest = _SafeExpandPath(self.topdir, self.dest)
346
347 if platform_utils.isdir(src):
348 raise ManifestInvalidPathError(
349 '%s: copying from directory not supported' % (self.src,))
350 if platform_utils.isdir(dest):
351 raise ManifestInvalidPathError(
352 '%s: copying to directory not allowed' % (self.dest,))
353
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700354 # copy file if it does not exist or is out of date
355 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
356 try:
357 # remove existing file first, since it might be read-only
358 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800359 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400360 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200361 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700362 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200363 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700364 shutil.copy(src, dest)
365 # make the file read-only
366 mode = os.stat(dest)[stat.ST_MODE]
367 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
368 os.chmod(dest, mode)
369 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700370 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700371
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700372
Anthony King7bdac712014-07-16 12:56:40 +0100373class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400374 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700375
Mike Frysingere6a202f2019-08-02 15:57:57 -0400376 def __init__(self, git_worktree, src, topdir, dest):
377 """Register a <linkfile> request.
378
379 Args:
380 git_worktree: Absolute path to the git project checkout.
381 src: Target of symlink relative to path under |git_worktree|.
382 topdir: Absolute path to the top of the repo client checkout.
383 dest: Relative path under |topdir| of symlink to create.
384 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700385 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400386 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500387 self.src = src
388 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500389
Wink Saville4c426ef2015-06-03 08:05:17 -0700390 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500391 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700392 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500393 try:
394 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800395 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800396 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500397 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700398 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700399 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500400 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700401 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500402 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700403 _error('Cannot link file %s to %s', relSrc, absDest)
404
405 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400406 """Link the self.src & self.dest paths.
407
408 Handles wild cards on the src linking all of the files in the source in to
409 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700410 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500411 # Some people use src="." to create stable links to projects. Lets allow
412 # that but reject all other uses of "." to keep things simple.
413 if self.src == '.':
414 src = self.git_worktree
415 else:
416 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400417
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300418 if not glob.has_magic(src):
419 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400420 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
421 # dest & src are absolute paths at this point. Make sure the target of
422 # the symlink is relative in the context of the repo client checkout.
423 relpath = os.path.relpath(src, os.path.dirname(dest))
424 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700425 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400426 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300427 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400428 if os.path.exists(dest) and not platform_utils.isdir(dest):
429 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700430 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400431 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700432 # Create a releative path from source dir to destination dir
433 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400434 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700435
436 # Get the source file name
437 srcFile = os.path.basename(absSrcFile)
438
439 # Now form the final full paths to srcFile. They will be
440 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400441 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700442 relSrc = os.path.join(relSrcDir, srcFile)
443 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500444
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700445
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700446class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700447
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700448 def __init__(self,
449 name,
Anthony King7bdac712014-07-16 12:56:40 +0100450 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700451 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100452 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700453 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700454 orig_name=None,
455 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700456 self.name = name
457 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700458 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700459 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100460 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700461 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700462 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700463
Ian Kasprzak0286e312021-02-05 10:06:18 -0800464
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700465class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600466 # These objects can be shared between several working trees.
Mike Frysinger1d00a7e2021-12-21 00:40:31 -0500467 shareable_dirs = ['hooks', 'rr-cache']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700468
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700469 def __init__(self,
470 manifest,
471 name,
472 remote,
473 gitdir,
David James8d201162013-10-11 17:03:19 -0700474 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700475 worktree,
476 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700477 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800478 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100479 rebase=True,
480 groups=None,
481 sync_c=False,
482 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900483 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100484 clone_depth=None,
485 upstream=None,
486 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500487 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100488 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900489 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700490 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600491 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700492 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800493 """Init a Project object.
494
495 Args:
496 manifest: The XmlManifest object.
497 name: The `name` attribute of manifest.xml's project element.
498 remote: RemoteSpec object specifying its remote's properties.
499 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700500 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800501 worktree: Absolute path of git working tree.
502 relpath: Relative path of git working tree to repo's top directory.
503 revisionExpr: The `revision` attribute of manifest.xml's project element.
504 revisionId: git commit id for checking out.
505 rebase: The `rebase` attribute of manifest.xml's project element.
506 groups: The `groups` attribute of manifest.xml's project element.
507 sync_c: The `sync-c` attribute of manifest.xml's project element.
508 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900509 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800510 upstream: The `upstream` attribute of manifest.xml's project element.
511 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500512 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800513 is_derived: False if the project was explicitly defined in the manifest;
514 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400515 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900516 optimized_fetch: If True, when a project is set to a sha1 revision, only
517 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600518 retry_fetches: Retry remote fetches n times upon receiving transient error
519 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700520 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800521 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400522 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700523 self.name = name
524 self.remote = remote
Michael Kelly37c21c22020-06-13 02:10:40 -0700525 self.UpdatePaths(relpath, worktree, gitdir, objdir)
Michael Kelly2f3c3312020-07-21 19:40:38 -0700526 self.SetRevision(revisionExpr, revisionId=revisionId)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700527
Mike Pontillod3153822012-02-28 11:53:24 -0800528 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700529 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700530 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800531 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900532 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900533 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700534 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800535 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500536 # NB: Do not use this setting in __init__ to change behavior so that the
537 # manifest.git checkout can inspect & change it after instantiating. See
538 # the XmlManifest init code for more info.
539 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800540 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900541 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600542 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800543 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800544
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700545 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700546 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500547 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500548 self.annotations = []
Bryan Jacobsf609f912013-05-06 13:36:24 -0400549 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700550 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700551
Doug Anderson37282b42011-03-04 11:54:18 -0800552 # This will be filled in if a project is later identified to be the
553 # project containing repo hooks.
554 self.enabled_repo_hooks = []
555
LaMont Jonescc879a92021-11-18 22:40:18 +0000556 def RelPath(self, local=True):
557 """Return the path for the project relative to a manifest.
558
559 Args:
560 local: a boolean, if True, the path is relative to the local
561 (sub)manifest. If false, the path is relative to the
562 outermost manifest.
563 """
564 if local:
565 return self.relpath
566 return os.path.join(self.manifest.path_prefix, self.relpath)
567
Michael Kelly2f3c3312020-07-21 19:40:38 -0700568 def SetRevision(self, revisionExpr, revisionId=None):
569 """Set revisionId based on revision expression and id"""
570 self.revisionExpr = revisionExpr
571 if revisionId is None and revisionExpr and IsId(revisionExpr):
572 self.revisionId = self.revisionExpr
573 else:
574 self.revisionId = revisionId
575
Michael Kelly37c21c22020-06-13 02:10:40 -0700576 def UpdatePaths(self, relpath, worktree, gitdir, objdir):
577 """Update paths used by this project"""
578 self.gitdir = gitdir.replace('\\', '/')
579 self.objdir = objdir.replace('\\', '/')
580 if worktree:
581 self.worktree = os.path.normpath(worktree).replace('\\', '/')
582 else:
583 self.worktree = None
584 self.relpath = relpath
585
586 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
587 defaults=self.manifest.globalConfig)
588
589 if self.worktree:
590 self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
591 else:
592 self.work_git = None
593 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
594 self.bare_ref = GitRefs(self.gitdir)
595 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
596
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700597 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800598 def Derived(self):
599 return self.is_derived
600
601 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700602 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700603 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700604
605 @property
606 def CurrentBranch(self):
607 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400608
609 The branch name omits the 'refs/heads/' prefix.
610 None is returned if the project is on a detached HEAD, or if the work_git is
611 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700612 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400613 try:
614 b = self.work_git.GetHead()
615 except NoManifestException:
616 # If the local checkout is in a bad state, don't barf. Let the callers
617 # process this like the head is unreadable.
618 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700619 if b.startswith(R_HEADS):
620 return b[len(R_HEADS):]
621 return None
622
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700623 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500624 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
625 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
626 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200627
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700628 def IsDirty(self, consider_untracked=True):
629 """Is the working directory modified in some way?
630 """
631 self.work_git.update_index('-q',
632 '--unmerged',
633 '--ignore-missing',
634 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900635 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700636 return True
637 if self.work_git.DiffZ('diff-files'):
638 return True
639 if consider_untracked and self.work_git.LsOthers():
640 return True
641 return False
642
643 _userident_name = None
644 _userident_email = None
645
646 @property
647 def UserName(self):
648 """Obtain the user's personal name.
649 """
650 if self._userident_name is None:
651 self._LoadUserIdentity()
652 return self._userident_name
653
654 @property
655 def UserEmail(self):
656 """Obtain the user's email address. This is very likely
657 to be their Gerrit login.
658 """
659 if self._userident_email is None:
660 self._LoadUserIdentity()
661 return self._userident_email
662
663 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900664 u = self.bare_git.var('GIT_COMMITTER_IDENT')
665 m = re.compile("^(.*) <([^>]*)> ").match(u)
666 if m:
667 self._userident_name = m.group(1)
668 self._userident_email = m.group(2)
669 else:
670 self._userident_name = ''
671 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700672
673 def GetRemote(self, name):
674 """Get the configuration for a single remote.
675 """
676 return self.config.GetRemote(name)
677
678 def GetBranch(self, name):
679 """Get the configuration for a single branch.
680 """
681 return self.config.GetBranch(name)
682
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700683 def GetBranches(self):
684 """Get all existing local branches.
685 """
686 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900687 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700688 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700689
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530690 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700691 if name.startswith(R_HEADS):
692 name = name[len(R_HEADS):]
693 b = self.GetBranch(name)
694 b.current = name == current
695 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900696 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700697 heads[name] = b
698
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530699 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700700 if name.startswith(R_PUB):
701 name = name[len(R_PUB):]
702 b = heads.get(name)
703 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900704 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700705
706 return heads
707
Colin Cross5acde752012-03-28 20:15:45 -0700708 def MatchesGroups(self, manifest_groups):
709 """Returns true if the manifest groups specified at init should cause
710 this project to be synced.
711 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700712 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700713
714 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700715 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700716 manifest_groups: "-group1,group2"
717 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500718
719 The special manifest group "default" will match any project that
720 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700721 """
LaMont Jones501733c2022-04-20 16:42:32 +0000722 default_groups = self.manifest.default_groups or ['default']
723 expanded_manifest_groups = manifest_groups or default_groups
Conley Owensbb1b5f52012-08-13 13:11:18 -0700724 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700725 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500726 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700727
Conley Owens971de8e2012-04-16 10:36:08 -0700728 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700729 for group in expanded_manifest_groups:
730 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700731 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700732 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700733 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700734
Conley Owens971de8e2012-04-16 10:36:08 -0700735 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700737# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700738 def UncommitedFiles(self, get_all=True):
739 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700740
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700741 Args:
742 get_all: a boolean, if True - get information about all different
743 uncommitted files. If False - return as soon as any kind of
744 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500745 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700746 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500747 self.work_git.update_index('-q',
748 '--unmerged',
749 '--ignore-missing',
750 '--refresh')
751 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700752 details.append("rebase in progress")
753 if not get_all:
754 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500755
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700756 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
757 if changes:
758 details.extend(changes)
759 if not get_all:
760 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500761
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700762 changes = self.work_git.DiffZ('diff-files').keys()
763 if changes:
764 details.extend(changes)
765 if not get_all:
766 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500767
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700768 changes = self.work_git.LsOthers()
769 if changes:
770 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500771
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700772 return details
773
774 def HasChanges(self):
775 """Returns true if there are uncommitted changes.
776 """
777 if self.UncommitedFiles(get_all=False):
778 return True
779 else:
780 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500781
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600782 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700783 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200784
785 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200786 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600787 quiet: If True then only print the project name. Do not print
788 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700789 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700790 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700791 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200792 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700793 print(file=output_redir)
794 print('project %s/' % self.relpath, file=output_redir)
795 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700796 return
797
798 self.work_git.update_index('-q',
799 '--unmerged',
800 '--ignore-missing',
801 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700802 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
804 df = self.work_git.DiffZ('diff-files')
805 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100806 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700807 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700808
809 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700810 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200811 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700812 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700813
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600814 if quiet:
815 out.nl()
816 return 'DIRTY'
817
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700818 branch = self.CurrentBranch
819 if branch is None:
820 out.nobranch('(*** NO BRANCH ***)')
821 else:
822 out.branch('branch %s', branch)
823 out.nl()
824
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700825 if rb:
826 out.important('prior sync failed; rebase still in progress')
827 out.nl()
828
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829 paths = list()
830 paths.extend(di.keys())
831 paths.extend(df.keys())
832 paths.extend(do)
833
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530834 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900835 try:
836 i = di[p]
837 except KeyError:
838 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900840 try:
841 f = df[p]
842 except KeyError:
843 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200844
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900845 if i:
846 i_status = i.status.upper()
847 else:
848 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900850 if f:
851 f_status = f.status.lower()
852 else:
853 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854
855 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800856 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700857 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858 else:
859 line = ' %s%s\t%s' % (i_status, f_status, p)
860
861 if i and not f:
862 out.added('%s', line)
863 elif (i and f) or (not i and f):
864 out.changed('%s', line)
865 elif not i and not f:
866 out.untracked('%s', line)
867 else:
868 out.write('%s', line)
869 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200870
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700871 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500873 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874 """Prints the status of the repository to stdout.
875 """
876 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500877 if output_redir:
878 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879 cmd = ['diff']
880 if out.is_on:
881 cmd.append('--color')
882 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300883 if absolute_paths:
884 cmd.append('--src-prefix=a/%s/' % self.relpath)
885 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400887 try:
888 p = GitCommand(self,
889 cmd,
890 capture_stdout=True,
891 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500892 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400893 except GitError as e:
894 out.nl()
895 out.project('project %s/' % self.relpath)
896 out.nl()
897 out.fail('%s', str(e))
898 out.nl()
899 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500900 if p.stdout:
901 out.nl()
902 out.project('project %s/' % self.relpath)
903 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500904 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400905 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700907# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900908 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700909 """Was the branch published (uploaded) for code review?
910 If so, returns the SHA-1 hash of the last published
911 state for the branch.
912 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700913 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900914 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700915 try:
916 return self.bare_git.rev_parse(key)
917 except GitError:
918 return None
919 else:
920 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900921 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700922 except KeyError:
923 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924
David Pursehouse8a68ff92012-09-24 12:15:13 +0900925 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926 """Prunes any stale published refs.
927 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900928 if all_refs is None:
929 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 heads = set()
931 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530932 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933 if name.startswith(R_HEADS):
934 heads.add(name)
935 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900936 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530938 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 n = name[len(R_PUB):]
940 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900941 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700943 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944 """List any branches which can be uploaded for review.
945 """
946 heads = {}
947 pubed = {}
948
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530949 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900951 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900953 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954
955 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530956 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900957 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700959 if selected_branch and branch != selected_branch:
960 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700961
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800962 rb = self.GetUploadableBranch(branch)
963 if rb:
964 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700965 return ready
966
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800967 def GetUploadableBranch(self, branch_name):
968 """Get a single uploadable branch, or None.
969 """
970 branch = self.GetBranch(branch_name)
971 base = branch.LocalMerge
972 if branch.LocalMerge:
973 rb = ReviewableBranch(self, branch, base)
974 if rb.commits:
975 return rb
976 return None
977
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700978 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100979 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500980 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700981 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500982 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500983 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200984 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700985 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200986 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200987 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800988 validate_certs=True,
989 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990 """Uploads the named branch for code review.
991 """
992 if branch is None:
993 branch = self.CurrentBranch
994 if branch is None:
995 raise GitError('not currently on a branch')
996
997 branch = self.GetBranch(branch)
998 if not branch.LocalMerge:
999 raise GitError('branch %s does not track a remote' % branch.name)
1000 if not branch.remote.review:
1001 raise GitError('remote %s has no review url' % branch.remote.name)
1002
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001003 # Basic validity check on label syntax.
1004 for label in labels:
1005 if not re.match(r'^.+[+-][0-9]+$', label):
1006 raise UploadError(
1007 f'invalid label syntax "{label}": labels use forms like '
1008 'CodeReview+1 or Verified-1')
1009
Bryan Jacobsf609f912013-05-06 13:36:24 -04001010 if dest_branch is None:
1011 dest_branch = self.dest_branch
1012 if dest_branch is None:
1013 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001014 if not dest_branch.startswith(R_HEADS):
1015 dest_branch = R_HEADS + dest_branch
1016
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001017 if not branch.remote.projectname:
1018 branch.remote.projectname = self.name
1019 branch.remote.Save()
1020
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001021 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001022 if url is None:
1023 raise UploadError('review not configured')
1024 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001025 if dryrun:
1026 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001027
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001028 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001029 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001030
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001031 for push_option in (push_options or []):
1032 cmd.append('-o')
1033 cmd.append(push_option)
1034
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001035 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001036
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001037 if dest_branch.startswith(R_HEADS):
1038 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001039
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001040 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001041 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001042 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001043 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001044 opts += ['t=%s' % p for p in hashtags]
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001045 # NB: No need to encode labels as they've been validated above.
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001046 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001047
David Pursehousef25a3702018-11-14 19:01:22 -08001048 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001049 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001050 if notify:
1051 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001052 if private:
1053 opts += ['private']
1054 if wip:
1055 opts += ['wip']
1056 if opts:
1057 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001058 cmd.append(ref_spec)
1059
Anthony King7bdac712014-07-16 12:56:40 +01001060 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001061 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062
Mike Frysingerd7f86832020-11-19 19:18:46 -05001063 if not dryrun:
1064 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1065 self.bare_git.UpdateRef(R_PUB + branch.name,
1066 R_HEADS + branch.name,
1067 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001068
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001069# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001070 def _ExtractArchive(self, tarpath, path=None):
1071 """Extract the given tar on its current location
1072
1073 Args:
1074 - tarpath: The path to the actual tar file
1075
1076 """
1077 try:
1078 with tarfile.open(tarpath, 'r') as tar:
1079 tar.extractall(path=path)
1080 return True
1081 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001082 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001083 return False
1084
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001085 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001086 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001087 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001088 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001089 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001090 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001091 force_sync=False,
1092 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001093 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001094 archive=False,
1095 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001096 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001097 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001098 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001099 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001100 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001101 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102 """Perform only the network IO portion of the sync process.
1103 Local working directory/branch state is not affected.
1104 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001105 if archive and not isinstance(self, MetaProject):
1106 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001107 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001108 return False
1109
1110 name = self.relpath.replace('\\', '/')
1111 name = name.replace('/', '_')
1112 tarpath = '%s.tar' % name
1113 topdir = self.manifest.topdir
1114
1115 try:
1116 self._FetchArchive(tarpath, cwd=topdir)
1117 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001118 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001119 return False
1120
1121 # From now on, we only need absolute tarpath
1122 tarpath = os.path.join(topdir, tarpath)
1123
1124 if not self._ExtractArchive(tarpath, path=topdir):
1125 return False
1126 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001127 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001128 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001129 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001130 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001131 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001132
1133 # If the shared object dir already exists, don't try to rebootstrap with a
1134 # clone bundle download. We should have the majority of objects already.
1135 if clone_bundle and os.path.exists(self.objdir):
1136 clone_bundle = False
1137
Raman Tennetif32f2432021-04-12 20:57:25 -07001138 if self.name in partial_clone_exclude:
1139 clone_bundle = True
1140 clone_filter = None
1141
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001142 if is_new is None:
1143 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001144 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001145 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001146 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001147 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001148 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001149
Mike Frysinger1d00a7e2021-12-21 00:40:31 -05001150 if _ALTERNATES or self.manifest.is_multimanifest:
1151 # If gitdir/objects is a symlink, migrate it from the old layout.
1152 gitdir_objects = os.path.join(self.gitdir, 'objects')
1153 if platform_utils.islink(gitdir_objects):
1154 platform_utils.remove(gitdir_objects, missing_ok=True)
1155 gitdir_alt = os.path.join(self.gitdir, 'objects/info/alternates')
1156 if not os.path.exists(gitdir_alt):
1157 os.makedirs(os.path.dirname(gitdir_alt), exist_ok=True)
1158 _lwrite(gitdir_alt, os.path.join(
1159 os.path.relpath(self.objdir, gitdir_objects), 'objects') + '\n')
1160
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001161 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001162 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001163 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001164 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001165 # This works for both absolute and relative alternate directories.
1166 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001167 except IOError:
1168 alt_dir = None
1169 else:
1170 alt_dir = None
1171
Mike Frysingere50b6a72020-02-19 01:45:48 -05001172 if (clone_bundle
1173 and alt_dir is None
1174 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001175 is_new = False
1176
Mike Frysinger73561142021-05-03 01:10:09 -04001177 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001178 if self.sync_c:
1179 current_branch_only = True
1180 elif not self.manifest._loaded:
1181 # Manifest cannot check defaults until it syncs.
1182 current_branch_only = False
1183 elif self.manifest.default.sync_c:
1184 current_branch_only = True
1185
Mike Frysingerd68ed632021-05-03 01:21:35 -04001186 if tags is None:
1187 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001188
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001189 if self.clone_depth:
1190 depth = self.clone_depth
1191 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001192 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001193
Mike Frysinger521d01b2020-02-17 01:51:49 -05001194 # See if we can skip the network fetch entirely.
1195 if not (optimized_fetch and
1196 (ID_RE.match(self.revisionExpr) and
1197 self._CheckForImmutableRevision())):
1198 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001199 initial=is_new,
1200 quiet=quiet, verbose=verbose, output_redir=output_redir,
1201 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001202 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001203 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001204 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001205 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001206 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001207
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001208 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001209 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001210 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001211 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001212 if os.path.exists(alternates_file):
1213 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001214 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1215 merge_output=bool(output_redir))
1216 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001217 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001218 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001219 return False
1220 platform_utils.remove(alternates_file)
1221
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001222 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001223 self._InitMRef()
1224 else:
1225 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001226 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1227 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001228 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001229
1230 def PostRepoUpgrade(self):
1231 self._InitHooks()
1232
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001233 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001234 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001235 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001236 for copyfile in self.copyfiles:
1237 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001238 for linkfile in self.linkfiles:
1239 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001240
Julien Camperguedd654222014-01-09 16:21:37 +01001241 def GetCommitRevisionId(self):
1242 """Get revisionId of a commit.
1243
1244 Use this method instead of GetRevisionId to get the id of the commit rather
1245 than the id of the current git object (for example, a tag)
1246
1247 """
1248 if not self.revisionExpr.startswith(R_TAGS):
1249 return self.GetRevisionId(self._allrefs)
1250
1251 try:
1252 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1253 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001254 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1255 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001256
David Pursehouse8a68ff92012-09-24 12:15:13 +09001257 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001258 if self.revisionId:
1259 return self.revisionId
1260
1261 rem = self.GetRemote(self.remote.name)
1262 rev = rem.ToLocal(self.revisionExpr)
1263
David Pursehouse8a68ff92012-09-24 12:15:13 +09001264 if all_refs is not None and rev in all_refs:
1265 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001266
1267 try:
1268 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1269 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001270 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1271 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001272
Raman Tenneti6a872c92021-01-14 19:17:50 -08001273 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001274 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001275 self.upstream = self.revisionExpr
1276
Raman Tenneti6a872c92021-01-14 19:17:50 -08001277 self.revisionId = revisionId
1278
Martin Kellye4e94d22017-03-21 16:05:12 -07001279 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280 """Perform only the local IO portion of the sync process.
1281 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001282 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001283 if not os.path.exists(self.gitdir):
1284 syncbuf.fail(self,
1285 'Cannot checkout %s due to missing network sync; Run '
1286 '`repo sync -n %s` first.' %
1287 (self.name, self.name))
1288 return
1289
Martin Kellye4e94d22017-03-21 16:05:12 -07001290 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001291 all_refs = self.bare_ref.all
1292 self.CleanPublishedCache(all_refs)
1293 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001294
Mike Frysinger0458faa2021-03-10 23:35:44 -05001295 # Special case the root of the repo client checkout. Make sure it doesn't
1296 # contain files being checked out to dirs we don't allow.
1297 if self.relpath == '.':
1298 PROTECTED_PATHS = {'.repo'}
1299 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1300 bad_paths = paths & PROTECTED_PATHS
1301 if bad_paths:
1302 syncbuf.fail(self,
1303 'Refusing to checkout project that writes to protected '
1304 'paths: %s' % (', '.join(bad_paths),))
1305 return
1306
David Pursehouse1d947b32012-10-25 12:23:11 +09001307 def _doff():
1308 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001309 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001310
Martin Kellye4e94d22017-03-21 16:05:12 -07001311 def _dosubmodules():
1312 self._SyncSubmodules(quiet=True)
1313
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001314 head = self.work_git.GetHead()
1315 if head.startswith(R_HEADS):
1316 branch = head[len(R_HEADS):]
1317 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001318 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001319 except KeyError:
1320 head = None
1321 else:
1322 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001324 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001325 # Currently on a detached HEAD. The user is assumed to
1326 # not have any local modifications worth worrying about.
1327 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001328 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001329 syncbuf.fail(self, _PriorSyncFailedError())
1330 return
1331
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001332 if head == revid:
1333 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001334 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001335 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001336 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001337 # The copy/linkfile config may have changed.
1338 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001339 return
1340 else:
1341 lost = self._revlist(not_rev(revid), HEAD)
1342 if lost:
1343 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001344
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001345 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001346 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001347 if submodules:
1348 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001349 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001350 syncbuf.fail(self, e)
1351 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001352 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001353 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001354
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001355 if head == revid:
1356 # No changes; don't do anything further.
1357 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001358 # The copy/linkfile config may have changed.
1359 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001360 return
1361
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001362 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001364 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001366 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001368 syncbuf.info(self,
1369 "leaving %s; does not track upstream",
1370 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001371 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001372 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001373 if submodules:
1374 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001375 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001376 syncbuf.fail(self, e)
1377 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001378 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001379 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001380
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001381 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001382
1383 # See if we can perform a fast forward merge. This can happen if our
1384 # branch isn't in the exact same state as we last published.
1385 try:
1386 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1387 # Skip the published logic.
1388 pub = False
1389 except GitError:
1390 pub = self.WasPublished(branch.name, all_refs)
1391
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001392 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001393 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394 if not_merged:
1395 if upstream_gain:
1396 # The user has published this branch and some of those
1397 # commits are not yet merged upstream. We do not want
1398 # to rewrite the published commits so we punt.
1399 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001400 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001401 "branch %s is published (but not merged) and is now "
1402 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001403 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001404 elif pub == head:
1405 # All published commits are merged, and thus we are a
1406 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001407 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001408 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001409 if submodules:
1410 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001411 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001412
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001413 # Examine the local commits not in the remote. Find the
1414 # last one attributed to this user, if any.
1415 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001416 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001417 last_mine = None
1418 cnt_mine = 0
1419 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001420 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001421 if committer_email == self.UserEmail:
1422 last_mine = commit_id
1423 cnt_mine += 1
1424
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001425 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001426 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001427
1428 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001429 syncbuf.fail(self, _DirtyError())
1430 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001431
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001432 # If the upstream switched on us, warn the user.
1433 #
1434 if branch.merge != self.revisionExpr:
1435 if branch.merge and self.revisionExpr:
1436 syncbuf.info(self,
1437 'manifest switched %s...%s',
1438 branch.merge,
1439 self.revisionExpr)
1440 elif branch.merge:
1441 syncbuf.info(self,
1442 'manifest no longer tracks %s',
1443 branch.merge)
1444
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001445 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001446 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001447 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001448 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001449 syncbuf.info(self,
1450 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001451 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001452
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001453 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001454 if not ID_RE.match(self.revisionExpr):
1455 # in case of manifest sync the revisionExpr might be a SHA1
1456 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001457 if not branch.merge.startswith('refs/'):
1458 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001459 branch.Save()
1460
Mike Pontillod3153822012-02-28 11:53:24 -08001461 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001462 def _docopyandlink():
1463 self._CopyAndLinkFiles()
1464
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001465 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001466 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001467 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001468 if submodules:
1469 syncbuf.later2(self, _dosubmodules)
1470 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001471 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001472 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001473 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001474 if submodules:
1475 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001476 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001477 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001478 syncbuf.fail(self, e)
1479 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001480 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001481 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001482 if submodules:
1483 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001484
Mike Frysingere6a202f2019-08-02 15:57:57 -04001485 def AddCopyFile(self, src, dest, topdir):
1486 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001487
Mike Frysingere6a202f2019-08-02 15:57:57 -04001488 No filesystem changes occur here. Actual copying happens later on.
1489
1490 Paths should have basic validation run on them before being queued.
1491 Further checking will be handled when the actual copy happens.
1492 """
1493 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1494
1495 def AddLinkFile(self, src, dest, topdir):
1496 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1497
1498 No filesystem changes occur here. Actual linking happens later on.
1499
1500 Paths should have basic validation run on them before being queued.
1501 Further checking will be handled when the actual link happens.
1502 """
1503 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001504
James W. Mills24c13082012-04-12 15:04:13 -05001505 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001506 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001507
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001508 def DownloadPatchSet(self, change_id, patch_id):
1509 """Download a single patch set of a single change to FETCH_HEAD.
1510 """
1511 remote = self.GetRemote(self.remote.name)
1512
1513 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001514 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001515 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001516 if GitCommand(self, cmd, bare=True).Wait() != 0:
1517 return None
1518 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001519 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001520 change_id,
1521 patch_id,
1522 self.bare_git.rev_parse('FETCH_HEAD'))
1523
Mike Frysingerc0d18662020-02-19 19:19:18 -05001524 def DeleteWorktree(self, quiet=False, force=False):
1525 """Delete the source checkout and any other housekeeping tasks.
1526
1527 This currently leaves behind the internal .repo/ cache state. This helps
1528 when switching branches or manifest changes get reverted as we don't have
1529 to redownload all the git objects. But we should do some GC at some point.
1530
1531 Args:
1532 quiet: Whether to hide normal messages.
1533 force: Always delete tree even if dirty.
1534
1535 Returns:
1536 True if the worktree was completely cleaned out.
1537 """
1538 if self.IsDirty():
1539 if force:
1540 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1541 (self.relpath,), file=sys.stderr)
1542 else:
1543 print('error: %s: Cannot remove project: uncommitted changes are '
1544 'present.\n' % (self.relpath,), file=sys.stderr)
1545 return False
1546
1547 if not quiet:
1548 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1549
1550 # Unlock and delink from the main worktree. We don't use git's worktree
1551 # remove because it will recursively delete projects -- we handle that
1552 # ourselves below. https://crbug.com/git/48
1553 if self.use_git_worktrees:
1554 needle = platform_utils.realpath(self.gitdir)
1555 # Find the git worktree commondir under .repo/worktrees/.
1556 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1557 assert output.startswith('worktree '), output
1558 commondir = output[9:]
1559 # Walk each of the git worktrees to see where they point.
1560 configs = os.path.join(commondir, 'worktrees')
1561 for name in os.listdir(configs):
1562 gitdir = os.path.join(configs, name, 'gitdir')
1563 with open(gitdir) as fp:
1564 relpath = fp.read().strip()
1565 # Resolve the checkout path and see if it matches this project.
1566 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1567 if fullpath == needle:
1568 platform_utils.rmtree(os.path.join(configs, name))
1569
1570 # Delete the .git directory first, so we're less likely to have a partially
1571 # working git repository around. There shouldn't be any git projects here,
1572 # so rmtree works.
1573
1574 # Try to remove plain files first in case of git worktrees. If this fails
1575 # for any reason, we'll fall back to rmtree, and that'll display errors if
1576 # it can't remove things either.
1577 try:
1578 platform_utils.remove(self.gitdir)
1579 except OSError:
1580 pass
1581 try:
1582 platform_utils.rmtree(self.gitdir)
1583 except OSError as e:
1584 if e.errno != errno.ENOENT:
1585 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1586 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1587 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1588 return False
1589
1590 # Delete everything under the worktree, except for directories that contain
1591 # another git project.
1592 dirs_to_remove = []
1593 failed = False
1594 for root, dirs, files in platform_utils.walk(self.worktree):
1595 for f in files:
1596 path = os.path.join(root, f)
1597 try:
1598 platform_utils.remove(path)
1599 except OSError as e:
1600 if e.errno != errno.ENOENT:
1601 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1602 failed = True
1603 dirs[:] = [d for d in dirs
1604 if not os.path.lexists(os.path.join(root, d, '.git'))]
1605 dirs_to_remove += [os.path.join(root, d) for d in dirs
1606 if os.path.join(root, d) not in dirs_to_remove]
1607 for d in reversed(dirs_to_remove):
1608 if platform_utils.islink(d):
1609 try:
1610 platform_utils.remove(d)
1611 except OSError as e:
1612 if e.errno != errno.ENOENT:
1613 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1614 failed = True
1615 elif not platform_utils.listdir(d):
1616 try:
1617 platform_utils.rmdir(d)
1618 except OSError as e:
1619 if e.errno != errno.ENOENT:
1620 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1621 failed = True
1622 if failed:
1623 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1624 file=sys.stderr)
1625 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1626 return False
1627
1628 # Try deleting parent dirs if they are empty.
1629 path = self.worktree
1630 while path != self.manifest.topdir:
1631 try:
1632 platform_utils.rmdir(path)
1633 except OSError as e:
1634 if e.errno != errno.ENOENT:
1635 break
1636 path = os.path.dirname(path)
1637
1638 return True
1639
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001640# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001641 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001642 """Create a new branch off the manifest's revision.
1643 """
Simran Basib9a1b732015-08-20 12:19:28 -07001644 if not branch_merge:
1645 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001646 head = self.work_git.GetHead()
1647 if head == (R_HEADS + name):
1648 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001649
David Pursehouse8a68ff92012-09-24 12:15:13 +09001650 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001651 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001652 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001653 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001654 capture_stdout=True,
1655 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001656
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001657 branch = self.GetBranch(name)
1658 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001659 branch.merge = branch_merge
1660 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1661 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001662
1663 if revision is None:
1664 revid = self.GetRevisionId(all_refs)
1665 else:
1666 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001667
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001668 if head.startswith(R_HEADS):
1669 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001670 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001671 except KeyError:
1672 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001673 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001674 ref = R_HEADS + name
1675 self.work_git.update_ref(ref, revid)
1676 self.work_git.symbolic_ref(HEAD, ref)
1677 branch.Save()
1678 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001679
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001680 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001681 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001682 capture_stdout=True,
1683 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001684 branch.Save()
1685 return True
1686 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001687
Wink Saville02d79452009-04-10 13:01:24 -07001688 def CheckoutBranch(self, name):
1689 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001690
1691 Args:
1692 name: The name of the branch to checkout.
1693
1694 Returns:
1695 True if the checkout succeeded; False if it didn't; None if the branch
1696 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001697 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001698 rev = R_HEADS + name
1699 head = self.work_git.GetHead()
1700 if head == rev:
1701 # Already on the branch
1702 #
1703 return True
Wink Saville02d79452009-04-10 13:01:24 -07001704
David Pursehouse8a68ff92012-09-24 12:15:13 +09001705 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001706 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001707 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001708 except KeyError:
1709 # Branch does not exist in this project
1710 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001711 return None
Wink Saville02d79452009-04-10 13:01:24 -07001712
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001713 if head.startswith(R_HEADS):
1714 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001715 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001716 except KeyError:
1717 head = None
1718
1719 if head == revid:
1720 # Same revision; just update HEAD to point to the new
1721 # target branch, but otherwise take no other action.
1722 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001723 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1724 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001725 return True
1726
1727 return GitCommand(self,
1728 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001729 capture_stdout=True,
1730 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001731
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001732 def AbandonBranch(self, name):
1733 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001734
1735 Args:
1736 name: The name of the branch to abandon.
1737
1738 Returns:
1739 True if the abandon succeeded; False if it didn't; None if the branch
1740 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001741 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001742 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001743 all_refs = self.bare_ref.all
1744 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001745 # Doesn't exist
1746 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001747
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001748 head = self.work_git.GetHead()
1749 if head == rev:
1750 # We can't destroy the branch while we are sitting
1751 # on it. Switch to a detached HEAD.
1752 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001753 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001754
David Pursehouse8a68ff92012-09-24 12:15:13 +09001755 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001756 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001757 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001758 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001759 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001760
1761 return GitCommand(self,
1762 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001763 capture_stdout=True,
1764 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001765
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766 def PruneHeads(self):
1767 """Prune any topic branches already merged into upstream.
1768 """
1769 cb = self.CurrentBranch
1770 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001771 left = self._allrefs
1772 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001773 if name.startswith(R_HEADS):
1774 name = name[len(R_HEADS):]
1775 if cb is None or name != cb:
1776 kill.append(name)
1777
Mike Frysingera3794e92021-03-11 23:24:01 -05001778 # Minor optimization: If there's nothing to prune, then don't try to read
1779 # any project state.
1780 if not kill and not cb:
1781 return []
1782
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001783 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001784 if cb is not None \
1785 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001786 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001787 self.work_git.DetachHead(HEAD)
1788 kill.append(cb)
1789
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001790 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001791 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001792
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001793 try:
1794 self.bare_git.DetachHead(rev)
1795
1796 b = ['branch', '-d']
1797 b.extend(kill)
1798 b = GitCommand(self, b, bare=True,
1799 capture_stdout=True,
1800 capture_stderr=True)
1801 b.Wait()
1802 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001803 if ID_RE.match(old):
1804 self.bare_git.DetachHead(old)
1805 else:
1806 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001807 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001808
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001809 for branch in kill:
1810 if (R_HEADS + branch) not in left:
1811 self.CleanPublishedCache()
1812 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001813
1814 if cb and cb not in kill:
1815 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001816 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001817
1818 kept = []
1819 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001820 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001821 branch = self.GetBranch(branch)
1822 base = branch.LocalMerge
1823 if not base:
1824 base = rev
1825 kept.append(ReviewableBranch(self, branch, base))
1826 return kept
1827
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001828# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001829 def GetRegisteredSubprojects(self):
1830 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001831
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001832 def rec(subprojects):
1833 if not subprojects:
1834 return
1835 result.extend(subprojects)
1836 for p in subprojects:
1837 rec(p.subprojects)
1838 rec(self.subprojects)
1839 return result
1840
1841 def _GetSubmodules(self):
1842 # Unfortunately we cannot call `git submodule status --recursive` here
1843 # because the working tree might not exist yet, and it cannot be used
1844 # without a working tree in its current implementation.
1845
1846 def get_submodules(gitdir, rev):
1847 # Parse .gitmodules for submodule sub_paths and sub_urls
1848 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1849 if not sub_paths:
1850 return []
1851 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1852 # revision of submodule repository
1853 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1854 submodules = []
1855 for sub_path, sub_url in zip(sub_paths, sub_urls):
1856 try:
1857 sub_rev = sub_revs[sub_path]
1858 except KeyError:
1859 # Ignore non-exist submodules
1860 continue
1861 submodules.append((sub_rev, sub_path, sub_url))
1862 return submodules
1863
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001864 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1865 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001866
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001867 def parse_gitmodules(gitdir, rev):
1868 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1869 try:
Anthony King7bdac712014-07-16 12:56:40 +01001870 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1871 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001872 except GitError:
1873 return [], []
1874 if p.Wait() != 0:
1875 return [], []
1876
1877 gitmodules_lines = []
1878 fd, temp_gitmodules_path = tempfile.mkstemp()
1879 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001880 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001881 os.close(fd)
1882 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001883 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1884 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001885 if p.Wait() != 0:
1886 return [], []
1887 gitmodules_lines = p.stdout.split('\n')
1888 except GitError:
1889 return [], []
1890 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001891 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001892
1893 names = set()
1894 paths = {}
1895 urls = {}
1896 for line in gitmodules_lines:
1897 if not line:
1898 continue
1899 m = re_path.match(line)
1900 if m:
1901 names.add(m.group(1))
1902 paths[m.group(1)] = m.group(2)
1903 continue
1904 m = re_url.match(line)
1905 if m:
1906 names.add(m.group(1))
1907 urls[m.group(1)] = m.group(2)
1908 continue
1909 names = sorted(names)
1910 return ([paths.get(name, '') for name in names],
1911 [urls.get(name, '') for name in names])
1912
1913 def git_ls_tree(gitdir, rev, paths):
1914 cmd = ['ls-tree', rev, '--']
1915 cmd.extend(paths)
1916 try:
Anthony King7bdac712014-07-16 12:56:40 +01001917 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1918 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001919 except GitError:
1920 return []
1921 if p.Wait() != 0:
1922 return []
1923 objects = {}
1924 for line in p.stdout.split('\n'):
1925 if not line.strip():
1926 continue
1927 object_rev, object_path = line.split()[2:4]
1928 objects[object_path] = object_rev
1929 return objects
1930
1931 try:
1932 rev = self.GetRevisionId()
1933 except GitError:
1934 return []
1935 return get_submodules(self.gitdir, rev)
1936
1937 def GetDerivedSubprojects(self):
1938 result = []
1939 if not self.Exists:
1940 # If git repo does not exist yet, querying its submodules will
1941 # mess up its states; so return here.
1942 return result
1943 for rev, path, url in self._GetSubmodules():
1944 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001945 relpath, worktree, gitdir, objdir = \
1946 self.manifest.GetSubprojectPaths(self, name, path)
1947 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001948 if project:
1949 result.extend(project.GetDerivedSubprojects())
1950 continue
David James8d201162013-10-11 17:03:19 -07001951
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001952 if url.startswith('..'):
1953 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001954 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001955 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001956 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001957 review=self.remote.review,
1958 revision=self.remote.revision)
1959 subproject = Project(manifest=self.manifest,
1960 name=name,
1961 remote=remote,
1962 gitdir=gitdir,
1963 objdir=objdir,
1964 worktree=worktree,
1965 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001966 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001967 revisionId=rev,
1968 rebase=self.rebase,
1969 groups=self.groups,
1970 sync_c=self.sync_c,
1971 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001972 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001973 parent=self,
1974 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001975 result.append(subproject)
1976 result.extend(subproject.GetDerivedSubprojects())
1977 return result
1978
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001979# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001980 def EnableRepositoryExtension(self, key, value='true', version=1):
1981 """Enable git repository extension |key| with |value|.
1982
1983 Args:
1984 key: The extension to enabled. Omit the "extensions." prefix.
1985 value: The value to use for the extension.
1986 version: The minimum git repository version needed.
1987 """
1988 # Make sure the git repo version is new enough already.
1989 found_version = self.config.GetInt('core.repositoryFormatVersion')
1990 if found_version is None:
1991 found_version = 0
1992 if found_version < version:
1993 self.config.SetString('core.repositoryFormatVersion', str(version))
1994
1995 # Enable the extension!
1996 self.config.SetString('extensions.%s' % (key,), value)
1997
Mike Frysinger50a81de2020-09-06 15:51:21 -04001998 def ResolveRemoteHead(self, name=None):
1999 """Find out what the default branch (HEAD) points to.
2000
2001 Normally this points to refs/heads/master, but projects are moving to main.
2002 Support whatever the server uses rather than hardcoding "master" ourselves.
2003 """
2004 if name is None:
2005 name = self.remote.name
2006
2007 # The output will look like (NB: tabs are separators):
2008 # ref: refs/heads/master HEAD
2009 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2010 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2011
2012 for line in output.splitlines():
2013 lhs, rhs = line.split('\t', 1)
2014 if rhs == 'HEAD' and lhs.startswith('ref:'):
2015 return lhs[4:].strip()
2016
2017 return None
2018
Zac Livingstone4332262017-06-16 08:56:09 -06002019 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002020 try:
2021 # if revision (sha or tag) is not present then following function
2022 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002023 self.bare_git.rev_list('-1', '--missing=allow-any',
2024 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00002025 if self.upstream:
2026 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
2027 self.bare_git.rev_list('-1', '--missing=allow-any',
2028 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002029 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002030 return True
2031 except GitError:
2032 # There is no such persistent revision. We have to fetch it.
2033 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002034
Julien Campergue335f5ef2013-10-16 11:02:35 +02002035 def _FetchArchive(self, tarpath, cwd=None):
2036 cmd = ['archive', '-v', '-o', tarpath]
2037 cmd.append('--remote=%s' % self.remote.url)
2038 cmd.append('--prefix=%s/' % self.relpath)
2039 cmd.append(self.revisionExpr)
2040
2041 command = GitCommand(self, cmd, cwd=cwd,
2042 capture_stdout=True,
2043 capture_stderr=True)
2044
2045 if command.Wait() != 0:
2046 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2047
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002048 def _RemoteFetch(self, name=None,
2049 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002050 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002051 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002052 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002053 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002054 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002055 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002056 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002057 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002058 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002059 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002060 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002061 clone_filter=None,
2062 retry_fetches=2,
2063 retry_sleep_initial_sec=4.0,
2064 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002065 is_sha1 = False
2066 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002067 # The depth should not be used when fetching to a mirror because
2068 # it will result in a shallow repository that cannot be cloned or
2069 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002070 # The repo project should also never be synced with partial depth.
2071 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2072 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002073
Shawn Pearce69e04d82014-01-29 12:48:54 -08002074 if depth:
2075 current_branch_only = True
2076
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002077 if ID_RE.match(self.revisionExpr) is not None:
2078 is_sha1 = True
2079
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002080 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002081 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002082 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002083 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002084 elif self.upstream and self.upstream.startswith(R_TAGS):
2085 # This is a tag and its commit id should never change.
2086 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002087
2088 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002089 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002090 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002091 print('Skipped fetching project %s (already have persistent ref)'
2092 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002093 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002094 if is_sha1 and not depth:
2095 # When syncing a specific commit and --depth is not set:
2096 # * if upstream is explicitly specified and is not a sha1, fetch only
2097 # upstream as users expect only upstream to be fetch.
2098 # Note: The commit might not be in upstream in which case the sync
2099 # will fail.
2100 # * otherwise, fetch all branches to make sure we end up with the
2101 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002102 if self.upstream:
2103 current_branch_only = not ID_RE.match(self.upstream)
2104 else:
2105 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002106
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002107 if not name:
2108 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002109
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002110 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002111 if not remote.PreConnectFetch(ssh_proxy):
2112 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002113
Shawn O. Pearce88443382010-10-08 10:02:09 +02002114 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002115 if alt_dir and 'objects' == os.path.basename(alt_dir):
2116 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002117 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002118
David Pursehouse8a68ff92012-09-24 12:15:13 +09002119 all_refs = self.bare_ref.all
2120 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002121 tmp = set()
2122
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302123 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002124 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002125 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002126 all_refs[r] = ref_id
2127 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002128 continue
2129
David Pursehouse8a68ff92012-09-24 12:15:13 +09002130 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002131 continue
2132
David Pursehouse8a68ff92012-09-24 12:15:13 +09002133 r = 'refs/_alt/%s' % ref_id
2134 all_refs[r] = ref_id
2135 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002136 tmp.add(r)
2137
heping3d7bbc92017-04-12 19:51:47 +08002138 tmp_packed_lines = []
2139 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002140
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302141 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002142 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002143 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002144 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002145 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002146
heping3d7bbc92017-04-12 19:51:47 +08002147 tmp_packed = ''.join(tmp_packed_lines)
2148 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002149 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002150 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002151 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002152
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002153 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002154
Xin Li745be2e2019-06-03 11:24:30 -07002155 if clone_filter:
2156 git_require((2, 19, 0), fail=True, msg='partial clones')
2157 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002158 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002159
Conley Owensf97e8382015-01-21 11:12:46 -08002160 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002161 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002162 else:
2163 # If this repo has shallow objects, then we don't know which refs have
2164 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2165 # do this with projects that don't have shallow objects, since it is less
2166 # efficient.
2167 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2168 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002169
Mike Frysinger4847e052020-02-22 00:07:35 -05002170 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002171 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002172 if not quiet and sys.stdout.isatty():
2173 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002174 if not self.worktree:
2175 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002176 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002177
Mike Frysingere57f1142019-03-18 21:27:54 -04002178 if force_sync:
2179 cmd.append('--force')
2180
David Pursehouse74cfd272015-10-14 10:50:15 +09002181 if prune:
2182 cmd.append('--prune')
2183
LaMont Jonesff6b1da2022-06-01 21:03:34 +00002184 # Always pass something for --recurse-submodules, git with GIT_DIR behaves
2185 # incorrectly when not given `--recurse-submodules=no`. (b/218891912)
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002186 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002187
Kuang-che Wu6856f982019-11-25 12:37:55 +08002188 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002189 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002190 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002191 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002192 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002193 spec.append('tag')
2194 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002195
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302196 if self.manifest.IsMirror and not current_branch_only:
2197 branch = None
2198 else:
2199 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002200 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002201 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002202 # Shallow checkout of a specific commit, fetch from that commit and not
2203 # the heads only as the commit might be deeper in the history.
2204 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002205 if self.upstream:
2206 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002207 else:
2208 if is_sha1:
2209 branch = self.upstream
2210 if branch is not None and branch.strip():
2211 if not branch.startswith('refs/'):
2212 branch = R_HEADS + branch
2213 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2214
2215 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2216 # whole repo.
2217 if self.manifest.IsMirror and not spec:
2218 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2219
2220 # If using depth then we should not get all the tags since they may
2221 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002222 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002223 cmd.append('--no-tags')
2224 else:
2225 cmd.append('--tags')
2226 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2227
Conley Owens80b87fe2014-05-09 17:13:44 -07002228 cmd.extend(spec)
2229
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002230 # At least one retry minimum due to git remote prune.
2231 retry_fetches = max(retry_fetches, 2)
2232 retry_cur_sleep = retry_sleep_initial_sec
2233 ok = prune_tried = False
2234 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002235 gitcmd = GitCommand(
2236 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2237 ssh_proxy=ssh_proxy,
2238 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002239 if gitcmd.stdout and not quiet and output_redir:
2240 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002241 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002242 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002243 ok = True
2244 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002245
2246 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002247 elif (gitcmd.stdout and
2248 'error:' in gitcmd.stdout and
2249 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002250 # Fallthru to sleep+retry logic at the bottom.
2251 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002252
Mike Frysinger6823bc22021-04-15 02:06:28 -04002253 # Try to prune remote branches once in case there are conflicts.
2254 # For example, if the remote had refs/heads/upstream, but deleted that and
2255 # now has refs/heads/upstream/foo.
2256 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002257 'error:' in gitcmd.stdout and
2258 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002259 not prune_tried):
2260 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002261 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002262 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002263 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002264 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002265 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002266 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002267 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002268 continue
Brian Harring14a66742012-09-28 20:21:57 -07002269 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002270 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2271 # in sha1 mode, we just tried sync'ing from the upstream field; it
2272 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002273 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002274 elif ret < 0:
2275 # Git died with a signal, exit immediately
2276 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002277
2278 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002279 if not verbose and gitcmd.stdout:
2280 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002281 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002282 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2283 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002284 time.sleep(retry_cur_sleep)
2285 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2286 MAXIMUM_RETRY_SLEEP_SEC)
2287 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2288 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002289
2290 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002291 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002292 if old_packed != '':
2293 _lwrite(packed_refs, old_packed)
2294 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002295 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002296 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002297
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002298 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002299 # We just synced the upstream given branch; verify we
2300 # got what we wanted, else trigger a second run of all
2301 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002302 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002303 # Sync the current branch only with depth set to None.
2304 # We always pass depth=None down to avoid infinite recursion.
2305 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002306 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002307 current_branch_only=current_branch_only and depth,
2308 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002309 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002310
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002311 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002312
Mike Frysingere50b6a72020-02-19 01:45:48 -05002313 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002314 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002315 return False
2316
2317 remote = self.GetRemote(self.remote.name)
2318 bundle_url = remote.url + '/clone.bundle'
2319 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002320 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2321 'persistent-http',
2322 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002323 return False
2324
2325 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2326 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2327
2328 exist_dst = os.path.exists(bundle_dst)
2329 exist_tmp = os.path.exists(bundle_tmp)
2330
2331 if not initial and not exist_dst and not exist_tmp:
2332 return False
2333
2334 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002335 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2336 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002337 if not exist_dst:
2338 return False
2339
2340 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002341 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002342 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002343 if not quiet and sys.stdout.isatty():
2344 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002345 if not self.worktree:
2346 cmd.append('--update-head-ok')
2347 cmd.append(bundle_dst)
2348 for f in remote.fetch:
2349 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002350 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002351
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002352 ok = GitCommand(
2353 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002354 platform_utils.remove(bundle_dst, missing_ok=True)
2355 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002356 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002357
Mike Frysingere50b6a72020-02-19 01:45:48 -05002358 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002359 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002360
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002361 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002362 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002363 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002364 if os.path.exists(tmpPath):
2365 size = os.stat(tmpPath).st_size
2366 if size >= 1024:
2367 cmd += ['--continue-at', '%d' % (size,)]
2368 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002369 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002370 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002371 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002372 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002373 if proxy:
2374 cmd += ['--proxy', proxy]
2375 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2376 cmd += ['--proxy', os.environ['http_proxy']]
2377 if srcUrl.startswith('persistent-https'):
2378 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2379 elif srcUrl.startswith('persistent-http'):
2380 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002381 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002382
Dave Borowitz137d0132015-01-02 11:12:54 -08002383 if IsTrace():
2384 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002385 if verbose:
2386 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2387 stdout = None if verbose else subprocess.PIPE
2388 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002389 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002390 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002391 except OSError:
2392 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002393
Mike Frysingere50b6a72020-02-19 01:45:48 -05002394 (output, _) = proc.communicate()
2395 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002396
Dave Borowitz137d0132015-01-02 11:12:54 -08002397 if curlret == 22:
2398 # From curl man page:
2399 # 22: HTTP page not retrieved. The requested url was not found or
2400 # returned another error with the HTTP error code being 400 or above.
2401 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002402 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002403 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2404 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002405 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002406 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002407 elif curlret and not verbose and output:
2408 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002409
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002410 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002411 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002412 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002413 return True
2414 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002415 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002416 return False
2417 else:
2418 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002419
Kris Giesingc8d882a2014-12-23 13:02:32 -08002420 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002421 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002422 with open(path, 'rb') as f:
2423 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002424 return True
2425 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002426 if not quiet:
2427 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002428 return False
2429 except OSError:
2430 return False
2431
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002432 def _Checkout(self, rev, quiet=False):
2433 cmd = ['checkout']
2434 if quiet:
2435 cmd.append('-q')
2436 cmd.append(rev)
2437 cmd.append('--')
2438 if GitCommand(self, cmd).Wait() != 0:
2439 if self._allrefs:
2440 raise GitError('%s checkout %s ' % (self.name, rev))
2441
Mike Frysinger915fda12020-03-22 12:15:20 -04002442 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002443 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002444 if ffonly:
2445 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002446 if record_origin:
2447 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002448 cmd.append(rev)
2449 cmd.append('--')
2450 if GitCommand(self, cmd).Wait() != 0:
2451 if self._allrefs:
2452 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2453
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302454 def _LsRemote(self, refs):
2455 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302456 p = GitCommand(self, cmd, capture_stdout=True)
2457 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002458 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302459 return None
2460
Anthony King7bdac712014-07-16 12:56:40 +01002461 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002462 cmd = ['revert']
2463 cmd.append('--no-edit')
2464 cmd.append(rev)
2465 cmd.append('--')
2466 if GitCommand(self, cmd).Wait() != 0:
2467 if self._allrefs:
2468 raise GitError('%s revert %s ' % (self.name, rev))
2469
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002470 def _ResetHard(self, rev, quiet=True):
2471 cmd = ['reset', '--hard']
2472 if quiet:
2473 cmd.append('-q')
2474 cmd.append(rev)
2475 if GitCommand(self, cmd).Wait() != 0:
2476 raise GitError('%s reset --hard %s ' % (self.name, rev))
2477
Martin Kellye4e94d22017-03-21 16:05:12 -07002478 def _SyncSubmodules(self, quiet=True):
2479 cmd = ['submodule', 'update', '--init', '--recursive']
2480 if quiet:
2481 cmd.append('-q')
2482 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002483 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002484
Anthony King7bdac712014-07-16 12:56:40 +01002485 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002486 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002487 if onto is not None:
2488 cmd.extend(['--onto', onto])
2489 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002490 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002491 raise GitError('%s rebase %s ' % (self.name, upstream))
2492
Pierre Tardy3d125942012-05-04 12:18:12 +02002493 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002494 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002495 if ffonly:
2496 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002497 if GitCommand(self, cmd).Wait() != 0:
2498 raise GitError('%s merge %s ' % (self.name, head))
2499
David Pursehousee8ace262020-02-13 12:41:15 +09002500 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002501 init_git_dir = not os.path.exists(self.gitdir)
2502 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002503 try:
2504 # Initialize the bare repository, which contains all of the objects.
2505 if init_obj_dir:
2506 os.makedirs(self.objdir)
2507 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002508
Mike Frysinger333c0a42021-11-15 12:39:00 -05002509 self._UpdateHooks(quiet=quiet)
2510
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002511 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002512 # Enable per-worktree config file support if possible. This is more a
2513 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002514 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002515 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002516
Kevin Degib1a07b82015-07-27 13:33:43 -06002517 # If we have a separate directory to hold refs, initialize it as well.
2518 if self.objdir != self.gitdir:
2519 if init_git_dir:
2520 os.makedirs(self.gitdir)
2521
2522 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002523 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002524 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002525 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002526 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002527 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002528 print("Retrying clone after deleting %s" %
2529 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002530 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002531 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2532 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002533 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002534 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002535 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2536 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002537 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002538 raise e
2539 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002540
Kevin Degi384b3c52014-10-16 16:02:58 -06002541 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002542 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002543 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002544
LaMont Jonescc879a92021-11-18 22:40:18 +00002545 def _expanded_ref_dirs():
2546 """Iterate through the possible git reference directory paths."""
2547 name = self.name + '.git'
2548 yield mirror_git or os.path.join(ref_dir, name)
2549 for prefix in '', self.remote.name:
2550 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2551 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002552
LaMont Jonescc879a92021-11-18 22:40:18 +00002553 if ref_dir or mirror_git:
2554 found_ref_dir = None
2555 for path in _expanded_ref_dirs():
2556 if os.path.exists(path):
2557 found_ref_dir = path
2558 break
2559 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002560
Kevin Degib1a07b82015-07-27 13:33:43 -06002561 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002562 if not os.path.isabs(ref_dir):
2563 # The alternate directory is relative to the object database.
2564 ref_dir = os.path.relpath(ref_dir,
2565 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002566 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002567 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002568
Kevin Degib1a07b82015-07-27 13:33:43 -06002569 m = self.manifest.manifestProject.config
2570 for key in ['user.name', 'user.email']:
2571 if m.Has(key, include_defaults=False):
2572 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002573 if not self.manifest.EnableGitLfs:
2574 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2575 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002576 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002577 except Exception:
2578 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002579 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002580 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002581 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002582 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002583
David Pursehousee8ace262020-02-13 12:41:15 +09002584 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002585 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002586 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002587
David Pursehousee8ace262020-02-13 12:41:15 +09002588 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002589 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002590 if not os.path.exists(hooks):
2591 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002592
2593 # Delete sample hooks. They're noise.
2594 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002595 try:
2596 platform_utils.remove(hook, missing_ok=True)
2597 except PermissionError:
2598 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002599
Jonathan Nieder93719792015-03-17 11:29:58 -07002600 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002601 name = os.path.basename(stock_hook)
2602
Victor Boivie65e0f352011-04-18 11:23:29 +02002603 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002604 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002605 # Don't install a Gerrit Code Review hook if this
2606 # project does not appear to use it for reviews.
2607 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002608 # Since the manifest project is one of those, but also
2609 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002610 continue
2611
2612 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002613 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002614 continue
2615 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002616 # If the files are the same, we'll leave it alone. We create symlinks
2617 # below by default but fallback to hardlinks if the OS blocks them.
2618 # So if we're here, it's probably because we made a hardlink below.
2619 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002620 if not quiet:
2621 _warn("%s: Not replacing locally modified %s hook",
2622 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002623 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002624 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002625 platform_utils.symlink(
2626 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002627 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002628 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002629 try:
2630 os.link(stock_hook, dst)
2631 except OSError:
2632 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002633 else:
2634 raise
2635
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002636 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002637 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002638 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002639 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002640 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002641 remote.review = self.remote.review
2642 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002643
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002644 if self.worktree:
2645 remote.ResetFetch(mirror=False)
2646 else:
2647 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002648 remote.Save()
2649
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002650 def _InitMRef(self):
2651 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002652 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002653 # Set up the m/ space to point to the worktree-specific ref space.
2654 # We'll update the worktree-specific ref space on each checkout.
2655 ref = R_M + self.manifest.branch
2656 if not self.bare_ref.symref(ref):
2657 self.bare_git.symbolic_ref(
2658 '-m', 'redirecting to worktree scope',
2659 ref, R_WORKTREE_M + self.manifest.branch)
2660
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002661 # We can't update this ref with git worktrees until it exists.
2662 # We'll wait until the initial checkout to set it.
2663 if not os.path.exists(self.worktree):
2664 return
2665
2666 base = R_WORKTREE_M
2667 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002668
2669 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002670 else:
2671 base = R_M
2672 active_git = self.bare_git
2673
2674 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002675
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002676 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002677 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002678
Remy Böhmer1469c282020-12-15 18:49:02 +01002679 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002680 cur = self.bare_ref.symref(ref)
2681
2682 if self.revisionId:
2683 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2684 msg = 'manifest set to %s' % self.revisionId
2685 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002686 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002687 else:
2688 remote = self.GetRemote(self.remote.name)
2689 dst = remote.ToLocal(self.revisionExpr)
2690 if cur != dst:
2691 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002692 if detach:
2693 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2694 else:
2695 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002696
Mike Frysingerc72bd842021-11-14 03:58:00 -05002697 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002698 # Git worktrees don't use symlinks to share at all.
2699 if self.use_git_worktrees:
2700 return
2701
Mike Frysingerd33dce02021-12-20 18:16:33 -05002702 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002703 # Try to self-heal a bit in simple cases.
2704 dst_path = os.path.join(destdir, name)
2705 src_path = os.path.join(srcdir, name)
2706
Mike Frysingered4f2112020-02-11 23:06:29 -05002707 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002708 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002709 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002710 # Fail if the links are pointing to the wrong place
2711 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002712 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002713 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002714 'work tree. If you\'re comfortable with the '
2715 'possibility of losing the work tree\'s git metadata,'
2716 ' use `repo sync --force-sync {0}` to '
2717 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002718
Mike Frysingerc72bd842021-11-14 03:58:00 -05002719 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002720 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2721
2722 Args:
2723 gitdir: The bare git repository. Must already be initialized.
2724 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002725 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2726 This saves you the effort of initializing |dotgit| yourself.
2727 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002728 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002729 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002730
2731 to_copy = []
2732 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002733 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002734
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002735 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002736 for name in set(to_copy).union(to_symlink):
2737 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002738 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002739 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002740
Kevin Degi384b3c52014-10-16 16:02:58 -06002741 if os.path.lexists(dst):
2742 continue
David James8d201162013-10-11 17:03:19 -07002743
2744 # If the source dir doesn't exist, create an empty dir.
2745 if name in symlink_dirs and not os.path.lexists(src):
2746 os.makedirs(src)
2747
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002748 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002749 platform_utils.symlink(
2750 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002751 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002752 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002753 shutil.copytree(src, dst)
2754 elif os.path.isfile(src):
2755 shutil.copy(src, dst)
2756
David James8d201162013-10-11 17:03:19 -07002757 except OSError as e:
2758 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002759 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002760 else:
2761 raise
2762
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002763 def _InitGitWorktree(self):
2764 """Init the project using git worktrees."""
2765 self.bare_git.worktree('prune')
2766 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2767 self.worktree, self.GetRevisionId())
2768
2769 # Rewrite the internal state files to use relative paths between the
2770 # checkouts & worktrees.
2771 dotgit = os.path.join(self.worktree, '.git')
2772 with open(dotgit, 'r') as fp:
2773 # Figure out the checkout->worktree path.
2774 setting = fp.read()
2775 assert setting.startswith('gitdir:')
2776 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002777 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2778 # of file permissions. Delete it and recreate it from scratch to avoid.
2779 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002780 # Use relative path from checkout->worktree & maintain Unix line endings
2781 # on all OS's to match git behavior.
2782 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002783 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2784 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002785 # Use relative path from worktree->checkout & maintain Unix line endings
2786 # on all OS's to match git behavior.
2787 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002788 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2789
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002790 self._InitMRef()
2791
Martin Kellye4e94d22017-03-21 16:05:12 -07002792 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002793 """Setup the worktree .git path.
2794
2795 This is the user-visible path like src/foo/.git/.
2796
2797 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2798 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2799
2800 Older checkouts had .git/ directories. If we see that, migrate it.
2801
2802 This also handles changes in the manifest. Maybe this project was backed
2803 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2804 the path we point to under .repo/projects/ to match.
2805 """
2806 dotgit = os.path.join(self.worktree, '.git')
2807
2808 # If using an old layout style (a directory), migrate it.
2809 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2810 self._MigrateOldWorkTreeGitDir(dotgit)
2811
2812 init_dotgit = not os.path.exists(dotgit)
2813 if self.use_git_worktrees:
2814 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002815 self._InitGitWorktree()
2816 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002817 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002818 if not init_dotgit:
2819 # See if the project has changed.
2820 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2821 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002822
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002823 if init_dotgit or not os.path.exists(dotgit):
2824 os.makedirs(self.worktree, exist_ok=True)
2825 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002826
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002827 if init_dotgit:
2828 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002829
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002830 # Finish checking out the worktree.
2831 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2832 if GitCommand(self, cmd).Wait() != 0:
2833 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002834
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002835 if submodules:
2836 self._SyncSubmodules(quiet=True)
2837 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002838
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002839 @classmethod
2840 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2841 """Migrate the old worktree .git/ dir style to a symlink.
2842
2843 This logic specifically only uses state from |dotgit| to figure out where to
2844 move content and not |self|. This way if the backing project also changed
2845 places, we only do the .git/ dir to .git symlink migration here. The path
2846 updates will happen independently.
2847 """
2848 # Figure out where in .repo/projects/ it's pointing to.
2849 if not os.path.islink(os.path.join(dotgit, 'refs')):
2850 raise GitError(f'{dotgit}: unsupported checkout state')
2851 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2852
2853 # Remove known symlink paths that exist in .repo/projects/.
2854 KNOWN_LINKS = {
2855 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2856 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2857 }
2858 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2859 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002860 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2861 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002862 }
2863
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002864 # First see if we'd succeed before starting the migration.
2865 unknown_paths = []
2866 for name in platform_utils.listdir(dotgit):
2867 # Ignore all temporary/backup names. These are common with vim & emacs.
2868 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2869 continue
2870
2871 dotgit_path = os.path.join(dotgit, name)
2872 if name in KNOWN_LINKS:
2873 if not platform_utils.islink(dotgit_path):
2874 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2875 else:
2876 gitdir_path = os.path.join(gitdir, name)
2877 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2878 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2879 if unknown_paths:
2880 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2881
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002882 # Now walk the paths and sync the .git/ to .repo/projects/.
2883 for name in platform_utils.listdir(dotgit):
2884 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002885
2886 # Ignore all temporary/backup names. These are common with vim & emacs.
2887 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2888 platform_utils.remove(dotgit_path)
2889 elif name in KNOWN_LINKS:
2890 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002891 else:
2892 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002893 platform_utils.remove(gitdir_path, missing_ok=True)
2894 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002895
2896 # Now that the dir should be empty, clear it out, and symlink it over.
2897 platform_utils.rmdir(dotgit)
2898 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002899
Renaud Paquay788e9622017-01-27 11:41:12 -08002900 def _get_symlink_error_message(self):
2901 if platform_utils.isWindows():
2902 return ('Unable to create symbolic link. Please re-run the command as '
2903 'Administrator, or see '
2904 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2905 'for other options.')
2906 return 'filesystem must support symlinks'
2907
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002908 def _revlist(self, *args, **kw):
2909 a = []
2910 a.extend(args)
2911 a.append('--')
2912 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002913
2914 @property
2915 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002916 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002917
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002918 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002919 """Get logs between two revisions of this project."""
2920 comp = '..'
2921 if rev1:
2922 revs = [rev1]
2923 if rev2:
2924 revs.extend([comp, rev2])
2925 cmd = ['log', ''.join(revs)]
2926 out = DiffColoring(self.config)
2927 if out.is_on and color:
2928 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002929 if pretty_format is not None:
2930 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002931 if oneline:
2932 cmd.append('--oneline')
2933
2934 try:
2935 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2936 if log.Wait() == 0:
2937 return log.stdout
2938 except GitError:
2939 # worktree may not exist if groups changed for example. In that case,
2940 # try in gitdir instead.
2941 if not os.path.exists(self.worktree):
2942 return self.bare_git.log(*cmd[1:])
2943 else:
2944 raise
2945 return None
2946
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002947 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2948 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002949 """Get the list of logs from this revision to given revisionId"""
2950 logs = {}
2951 selfId = self.GetRevisionId(self._allrefs)
2952 toId = toProject.GetRevisionId(toProject._allrefs)
2953
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002954 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2955 pretty_format=pretty_format)
2956 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2957 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002958 return logs
2959
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002960 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002961
David James8d201162013-10-11 17:03:19 -07002962 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002963 self._project = project
2964 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002965 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002966
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002967 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2968 def __getstate__(self):
2969 return (self._project, self._bare, self._gitdir)
2970
2971 def __setstate__(self, state):
2972 self._project, self._bare, self._gitdir = state
2973
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002974 def LsOthers(self):
2975 p = GitCommand(self._project,
2976 ['ls-files',
2977 '-z',
2978 '--others',
2979 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002980 bare=False,
David James8d201162013-10-11 17:03:19 -07002981 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002982 capture_stdout=True,
2983 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002984 if p.Wait() == 0:
2985 out = p.stdout
2986 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002987 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002988 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002989 return []
2990
2991 def DiffZ(self, name, *args):
2992 cmd = [name]
2993 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002994 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002995 cmd.extend(args)
2996 p = GitCommand(self._project,
2997 cmd,
David James8d201162013-10-11 17:03:19 -07002998 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002999 bare=False,
3000 capture_stdout=True,
3001 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05003002 p.Wait()
3003 r = {}
3004 out = p.stdout
3005 if out:
3006 out = iter(out[:-1].split('\0'))
3007 while out:
3008 try:
3009 info = next(out)
3010 path = next(out)
3011 except StopIteration:
3012 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003013
Mike Frysinger84230002021-02-16 17:08:35 -05003014 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003015
Mike Frysinger84230002021-02-16 17:08:35 -05003016 def __init__(self, path, omode, nmode, oid, nid, state):
3017 self.path = path
3018 self.src_path = None
3019 self.old_mode = omode
3020 self.new_mode = nmode
3021 self.old_id = oid
3022 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003023
Mike Frysinger84230002021-02-16 17:08:35 -05003024 if len(state) == 1:
3025 self.status = state
3026 self.level = None
3027 else:
3028 self.status = state[:1]
3029 self.level = state[1:]
3030 while self.level.startswith('0'):
3031 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003032
Mike Frysinger84230002021-02-16 17:08:35 -05003033 info = info[1:].split(' ')
3034 info = _Info(path, *info)
3035 if info.status in ('R', 'C'):
3036 info.src_path = info.path
3037 info.path = next(out)
3038 r[info.path] = info
3039 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003040
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003041 def GetDotgitPath(self, subpath=None):
3042 """Return the full path to the .git dir.
3043
3044 As a convenience, append |subpath| if provided.
3045 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003046 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003047 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003048 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003049 dotgit = os.path.join(self._project.worktree, '.git')
3050 if os.path.isfile(dotgit):
3051 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3052 with open(dotgit) as fp:
3053 setting = fp.read()
3054 assert setting.startswith('gitdir:')
3055 gitdir = setting.split(':', 1)[1].strip()
3056 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3057
3058 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3059
3060 def GetHead(self):
3061 """Return the ref that HEAD points to."""
3062 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003063 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003064 with open(path) as fd:
3065 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003066 except IOError as e:
3067 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003068 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303069 line = line.decode()
3070 except AttributeError:
3071 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003072 if line.startswith('ref: '):
3073 return line[5:-1]
3074 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003075
3076 def SetHead(self, ref, message=None):
3077 cmdv = []
3078 if message is not None:
3079 cmdv.extend(['-m', message])
3080 cmdv.append(HEAD)
3081 cmdv.append(ref)
3082 self.symbolic_ref(*cmdv)
3083
3084 def DetachHead(self, new, message=None):
3085 cmdv = ['--no-deref']
3086 if message is not None:
3087 cmdv.extend(['-m', message])
3088 cmdv.append(HEAD)
3089 cmdv.append(new)
3090 self.update_ref(*cmdv)
3091
3092 def UpdateRef(self, name, new, old=None,
3093 message=None,
3094 detach=False):
3095 cmdv = []
3096 if message is not None:
3097 cmdv.extend(['-m', message])
3098 if detach:
3099 cmdv.append('--no-deref')
3100 cmdv.append(name)
3101 cmdv.append(new)
3102 if old is not None:
3103 cmdv.append(old)
3104 self.update_ref(*cmdv)
3105
3106 def DeleteRef(self, name, old=None):
3107 if not old:
3108 old = self.rev_parse(name)
3109 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003110 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003111
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003112 def rev_list(self, *args, **kw):
3113 if 'format' in kw:
3114 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3115 else:
3116 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003117 cmdv.extend(args)
3118 p = GitCommand(self._project,
3119 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003120 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003121 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003122 capture_stdout=True,
3123 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003124 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003125 raise GitError('%s rev-list %s: %s' %
3126 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003127 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003128
3129 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003130 """Allow arbitrary git commands using pythonic syntax.
3131
3132 This allows you to do things like:
3133 git_obj.rev_parse('HEAD')
3134
3135 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3136 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003137 Any other positional arguments will be passed to the git command, and the
3138 following keyword arguments are supported:
3139 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003140
3141 Args:
3142 name: The name of the git command to call. Any '_' characters will
3143 be replaced with '-'.
3144
3145 Returns:
3146 A callable object that will try to call git with the named command.
3147 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003148 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003149
Dave Borowitz091f8932012-10-23 17:01:04 -07003150 def runner(*args, **kwargs):
3151 cmdv = []
3152 config = kwargs.pop('config', None)
3153 for k in kwargs:
3154 raise TypeError('%s() got an unexpected keyword argument %r'
3155 % (name, k))
3156 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303157 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003158 cmdv.append('-c')
3159 cmdv.append('%s=%s' % (k, v))
3160 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003161 cmdv.extend(args)
3162 p = GitCommand(self._project,
3163 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003164 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003165 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003166 capture_stdout=True,
3167 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003168 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003169 raise GitError('%s %s: %s' %
3170 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003171 r = p.stdout
3172 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3173 return r[:-1]
3174 return r
3175 return runner
3176
3177
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003178class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003179
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003180 def __str__(self):
3181 return 'prior sync failed; rebase still in progress'
3182
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003183
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003184class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003185
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003186 def __str__(self):
3187 return 'contains uncommitted changes'
3188
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003189
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003190class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003191
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003192 def __init__(self, project, text):
3193 self.project = project
3194 self.text = text
3195
3196 def Print(self, syncbuf):
3197 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3198 syncbuf.out.nl()
3199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003200
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003201class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003202
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003203 def __init__(self, project, why):
3204 self.project = project
3205 self.why = why
3206
3207 def Print(self, syncbuf):
3208 syncbuf.out.fail('error: %s/: %s',
3209 self.project.relpath,
3210 str(self.why))
3211 syncbuf.out.nl()
3212
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003213
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003214class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003215
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003216 def __init__(self, project, action):
3217 self.project = project
3218 self.action = action
3219
3220 def Run(self, syncbuf):
3221 out = syncbuf.out
3222 out.project('project %s/', self.project.relpath)
3223 out.nl()
3224 try:
3225 self.action()
3226 out.nl()
3227 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003228 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003229 out.nl()
3230 return False
3231
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003232
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003233class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003234
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003235 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003236 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003237 self.project = self.printer('header', attr='bold')
3238 self.info = self.printer('info')
3239 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003240
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003241
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003242class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003243
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003244 def __init__(self, config, detach_head=False):
3245 self._messages = []
3246 self._failures = []
3247 self._later_queue1 = []
3248 self._later_queue2 = []
3249
3250 self.out = _SyncColoring(config)
3251 self.out.redirect(sys.stderr)
3252
3253 self.detach_head = detach_head
3254 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003255 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003256
3257 def info(self, project, fmt, *args):
3258 self._messages.append(_InfoMessage(project, fmt % args))
3259
3260 def fail(self, project, err=None):
3261 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003262 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003263
3264 def later1(self, project, what):
3265 self._later_queue1.append(_Later(project, what))
3266
3267 def later2(self, project, what):
3268 self._later_queue2.append(_Later(project, what))
3269
3270 def Finish(self):
3271 self._PrintMessages()
3272 self._RunLater()
3273 self._PrintMessages()
3274 return self.clean
3275
David Rileye0684ad2017-04-05 00:02:59 -07003276 def Recently(self):
3277 recent_clean = self.recent_clean
3278 self.recent_clean = True
3279 return recent_clean
3280
3281 def _MarkUnclean(self):
3282 self.clean = False
3283 self.recent_clean = False
3284
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003285 def _RunLater(self):
3286 for q in ['_later_queue1', '_later_queue2']:
3287 if not self._RunQueue(q):
3288 return
3289
3290 def _RunQueue(self, queue):
3291 for m in getattr(self, queue):
3292 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003293 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003294 return False
3295 setattr(self, queue, [])
3296 return True
3297
3298 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003299 if self._messages or self._failures:
3300 if os.isatty(2):
3301 self.out.write(progress.CSI_ERASE_LINE)
3302 self.out.write('\r')
3303
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003304 for m in self._messages:
3305 m.Print(self)
3306 for m in self._failures:
3307 m.Print(self)
3308
3309 self._messages = []
3310 self._failures = []
3311
3312
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003313class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003314 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003315
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003316 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003317 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003318 manifest=manifest,
3319 name=name,
3320 gitdir=gitdir,
3321 objdir=gitdir,
3322 worktree=worktree,
3323 remote=RemoteSpec('origin'),
3324 relpath='.repo/%s' % name,
3325 revisionExpr='refs/heads/master',
3326 revisionId=None,
3327 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003328
3329 def PreSync(self):
3330 if self.Exists:
3331 cb = self.CurrentBranch
3332 if cb:
3333 base = self.GetBranch(cb).merge
3334 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003335 self.revisionExpr = base
3336 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003337
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003338 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003339 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003340 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003341 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003342 return False
3343
David Pursehouse8a68ff92012-09-24 12:15:13 +09003344 all_refs = self.bare_ref.all
3345 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003346 head = self.work_git.GetHead()
3347 if head.startswith(R_HEADS):
3348 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003349 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003350 except KeyError:
3351 head = None
3352
3353 if revid == head:
3354 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003355 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003356 return True
3357 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003358
3359
3360class RepoProject(MetaProject):
3361 """The MetaProject for repo itself."""
3362
3363 @property
3364 def LastFetch(self):
3365 try:
3366 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3367 return os.path.getmtime(fh)
3368 except OSError:
3369 return 0
3370
3371class ManifestProject(MetaProject):
3372 """The MetaProject for manifests."""
3373
3374 def MetaBranchSwitch(self, submodules=False):
3375 """Prepare for manifest branch switch."""
3376
3377 # detach and delete manifest branch, allowing a new
3378 # branch to take over
3379 syncbuf = SyncBuffer(self.config, detach_head=True)
3380 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3381 syncbuf.Finish()
3382
3383 return GitCommand(self,
3384 ['update-ref', '-d', 'refs/heads/default'],
3385 capture_stdout=True,
3386 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003387
3388 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003389 def standalone_manifest_url(self):
3390 """The URL of the standalone manifest, or None."""
LaMont Jones55ee3042022-04-06 17:10:21 +00003391 return self.config.GetString('manifest.standalone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003392
3393 @property
3394 def manifest_groups(self):
3395 """The manifest groups string."""
3396 return self.config.GetString('manifest.groups')
3397
3398 @property
3399 def reference(self):
3400 """The --reference for this manifest."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003401 return self.config.GetString('repo.reference')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003402
3403 @property
3404 def dissociate(self):
3405 """Whether to dissociate."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003406 return self.config.GetBoolean('repo.dissociate')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003407
3408 @property
3409 def archive(self):
3410 """Whether we use archive."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003411 return self.config.GetBoolean('repo.archive')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003412
3413 @property
3414 def mirror(self):
3415 """Whether we use mirror."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003416 return self.config.GetBoolean('repo.mirror')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003417
3418 @property
3419 def use_worktree(self):
3420 """Whether we use worktree."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003421 return self.config.GetBoolean('repo.worktree')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003422
3423 @property
3424 def clone_bundle(self):
3425 """Whether we use clone_bundle."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003426 return self.config.GetBoolean('repo.clonebundle')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003427
3428 @property
3429 def submodules(self):
3430 """Whether we use submodules."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003431 return self.config.GetBoolean('repo.submodules')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003432
3433 @property
3434 def git_lfs(self):
3435 """Whether we use git_lfs."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003436 return self.config.GetBoolean('repo.git-lfs')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003437
3438 @property
3439 def use_superproject(self):
3440 """Whether we use superproject."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003441 return self.config.GetBoolean('repo.superproject')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003442
3443 @property
3444 def partial_clone(self):
3445 """Whether this is a partial clone."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003446 return self.config.GetBoolean('repo.partialclone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003447
3448 @property
3449 def depth(self):
3450 """Partial clone depth."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003451 return self.config.GetString('repo.depth')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003452
3453 @property
3454 def clone_filter(self):
3455 """The clone filter."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003456 return self.config.GetString('repo.clonefilter')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003457
3458 @property
3459 def partial_clone_exclude(self):
3460 """Partial clone exclude string"""
LaMont Jones4ada0432022-04-14 15:10:43 +00003461 return self.config.GetBoolean('repo.partialcloneexclude')
3462
3463 @property
3464 def manifest_platform(self):
3465 """The --platform argument from `repo init`."""
3466 return self.config.GetString('manifest.platform')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003467
3468 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003469 def _platform_name(self):
3470 """Return the name of the platform."""
3471 return platform.system().lower()
3472
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003473 def SyncWithPossibleInit(self, submanifest, verbose=False,
3474 current_branch_only=False, tags='', git_event_log=None):
3475 """Sync a manifestProject, possibly for the first time.
3476
3477 Call Sync() with arguments from the most recent `repo init`. If this is a
3478 new sub manifest, then inherit options from the parent's manifestProject.
3479
3480 This is used by subcmds.Sync() to do an initial download of new sub
3481 manifests.
3482
3483 Args:
3484 submanifest: an XmlSubmanifest, the submanifest to re-sync.
3485 verbose: a boolean, whether to show all output, rather than only errors.
3486 current_branch_only: a boolean, whether to only fetch the current manifest
3487 branch from the server.
3488 tags: a boolean, whether to fetch tags.
3489 git_event_log: an EventLog, for git tracing.
3490 """
3491 # TODO(lamontjones): when refactoring sync (and init?) consider how to
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003492 # better get the init options that we should use for new submanifests that
3493 # are added when syncing an existing workspace.
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003494 git_event_log = git_event_log or EventLog()
3495 spec = submanifest.ToSubmanifestSpec()
3496 # Use the init options from the existing manifestProject, or the parent if
3497 # it doesn't exist.
3498 #
3499 # Today, we only support changing manifest_groups on the sub-manifest, with
3500 # no supported-for-the-user way to change the other arguments from those
3501 # specified by the outermost manifest.
3502 #
3503 # TODO(lamontjones): determine which of these should come from the outermost
3504 # manifest and which should come from the parent manifest.
3505 mp = self if self.Exists else submanifest.parent.manifestProject
3506 return self.Sync(
3507 manifest_url=spec.manifestUrl,
3508 manifest_branch=spec.revision,
3509 standalone_manifest=mp.standalone_manifest_url,
3510 groups=mp.manifest_groups,
3511 platform=mp.manifest_platform,
3512 mirror=mp.mirror,
3513 dissociate=mp.dissociate,
3514 reference=mp.reference,
3515 worktree=mp.use_worktree,
3516 submodules=mp.submodules,
3517 archive=mp.archive,
3518 partial_clone=mp.partial_clone,
3519 clone_filter=mp.clone_filter,
3520 partial_clone_exclude=mp.partial_clone_exclude,
3521 clone_bundle=mp.clone_bundle,
3522 git_lfs=mp.git_lfs,
3523 use_superproject=mp.use_superproject,
3524 verbose=verbose,
3525 current_branch_only=current_branch_only,
3526 tags=tags,
3527 depth=mp.depth,
3528 git_event_log=git_event_log,
3529 manifest_name=spec.manifestName,
3530 this_manifest_only=True,
3531 outer_manifest=False,
3532 )
3533
LaMont Jones9b03f152022-03-29 23:01:18 +00003534 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003535 standalone_manifest=False, groups='', mirror=False, reference='',
3536 dissociate=False, worktree=False, submodules=False, archive=False,
3537 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003538 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3539 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jones55ee3042022-04-06 17:10:21 +00003540 git_event_log=None, platform='', manifest_name='default.xml',
3541 tags='', this_manifest_only=False, outer_manifest=True):
LaMont Jones9b03f152022-03-29 23:01:18 +00003542 """Sync the manifest and all submanifests.
3543
3544 Args:
3545 manifest_url: a string, the URL of the manifest project.
3546 manifest_branch: a string, the manifest branch to use.
3547 standalone_manifest: a boolean, whether to store the manifest as a static
3548 file.
3549 groups: a string, restricts the checkout to projects with the specified
3550 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003551 mirror: a boolean, whether to create a mirror of the remote repository.
3552 reference: a string, location of a repo instance to use as a reference.
3553 dissociate: a boolean, whether to dissociate from reference mirrors after
3554 clone.
3555 worktree: a boolean, whether to use git-worktree to manage projects.
3556 submodules: a boolean, whether sync submodules associated with the
3557 manifest project.
3558 archive: a boolean, whether to checkout each project as an archive. See
3559 git-archive.
3560 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003561 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003562 clone_filter: a string, filter to use with partial_clone.
3563 partial_clone_exclude : a string, comma-delimeted list of project namess
3564 to exclude from partial clone.
3565 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3566 git_lfs: a boolean, whether to enable git LFS support.
3567 use_superproject: a boolean, whether to use the manifest superproject to
3568 sync projects.
3569 verbose: a boolean, whether to show all output, rather than only errors.
3570 current_branch_only: a boolean, whether to only fetch the current manifest
3571 branch from the server.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003572 platform: a string, restrict the checkout to projects with the specified
3573 platform group.
LaMont Jones55ee3042022-04-06 17:10:21 +00003574 git_event_log: an EventLog, for git tracing.
LaMont Jones4ada0432022-04-14 15:10:43 +00003575 tags: a boolean, whether to fetch tags.
LaMont Jones409407a2022-04-05 21:21:56 +00003576 manifest_name: a string, the name of the manifest file to use.
3577 this_manifest_only: a boolean, whether to only operate on the current sub
3578 manifest.
3579 outer_manifest: a boolean, whether to start at the outermost manifest.
LaMont Jones9b03f152022-03-29 23:01:18 +00003580
3581 Returns:
3582 a boolean, whether the sync was successful.
3583 """
3584 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3585
LaMont Jones501733c2022-04-20 16:42:32 +00003586 groups = groups or self.manifest.GetDefaultGroupsStr(with_platform=False)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003587 platform = platform or 'auto'
LaMont Jones55ee3042022-04-06 17:10:21 +00003588 git_event_log = git_event_log or EventLog()
LaMont Jones409407a2022-04-05 21:21:56 +00003589 if outer_manifest and self.manifest.is_submanifest:
3590 # In a multi-manifest checkout, use the outer manifest unless we are told
3591 # not to.
3592 return self.client.outer_manifest.manifestProject.Sync(
3593 manifest_url=manifest_url,
3594 manifest_branch=manifest_branch,
3595 standalone_manifest=standalone_manifest,
3596 groups=groups,
3597 platform=platform,
3598 mirror=mirror,
3599 dissociate=dissociate,
3600 reference=reference,
3601 worktree=worktree,
3602 submodules=submodules,
3603 archive=archive,
3604 partial_clone=partial_clone,
3605 clone_filter=clone_filter,
3606 partial_clone_exclude=partial_clone_exclude,
3607 clone_bundle=clone_bundle,
3608 git_lfs=git_lfs,
3609 use_superproject=use_superproject,
3610 verbose=verbose,
3611 current_branch_only=current_branch_only,
3612 tags=tags,
3613 depth=depth,
LaMont Jones55ee3042022-04-06 17:10:21 +00003614 git_event_log=git_event_log,
LaMont Jones409407a2022-04-05 21:21:56 +00003615 manifest_name=manifest_name,
3616 this_manifest_only=this_manifest_only,
3617 outer_manifest=False)
3618
LaMont Jones9b03f152022-03-29 23:01:18 +00003619 # If repo has already been initialized, we take -u with the absence of
3620 # --standalone-manifest to mean "transition to a standard repo set up",
3621 # which necessitates starting fresh.
3622 # If --standalone-manifest is set, we always tear everything down and start
3623 # anew.
3624 if self.Exists:
3625 was_standalone_manifest = self.config.GetString('manifest.standalone')
3626 if was_standalone_manifest and not manifest_url:
3627 print('fatal: repo was initialized with a standlone manifest, '
3628 'cannot be re-initialized without --manifest-url/-u')
3629 return False
3630
3631 if standalone_manifest or (was_standalone_manifest and manifest_url):
3632 self.config.ClearCache()
3633 if self.gitdir and os.path.exists(self.gitdir):
3634 platform_utils.rmtree(self.gitdir)
3635 if self.worktree and os.path.exists(self.worktree):
3636 platform_utils.rmtree(self.worktree)
3637
3638 is_new = not self.Exists
3639 if is_new:
3640 if not manifest_url:
3641 print('fatal: manifest url is required.', file=sys.stderr)
3642 return False
3643
LaMont Jones409407a2022-04-05 21:21:56 +00003644 if verbose:
LaMont Jones9b03f152022-03-29 23:01:18 +00003645 print('Downloading manifest from %s' %
3646 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3647 file=sys.stderr)
3648
3649 # The manifest project object doesn't keep track of the path on the
3650 # server where this git is located, so let's save that here.
3651 mirrored_manifest_git = None
3652 if reference:
3653 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3654 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3655 if not mirrored_manifest_git.endswith(".git"):
3656 mirrored_manifest_git += ".git"
3657 if not os.path.exists(mirrored_manifest_git):
3658 mirrored_manifest_git = os.path.join(reference,
3659 '.repo/manifests.git')
3660
3661 self._InitGitDir(mirror_git=mirrored_manifest_git)
3662
3663 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3664 # still do much of the manifests.git set up, but will avoid actual syncs to
3665 # a remote.
3666 if standalone_manifest:
3667 self.config.SetString('manifest.standalone', manifest_url)
3668 elif not manifest_url and not manifest_branch:
3669 # If -u is set and --standalone-manifest is not, then we're not in
3670 # standalone mode. Otherwise, use config to infer what we were in the last
3671 # init.
3672 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3673 if not standalone_manifest:
3674 self.config.SetString('manifest.standalone', None)
3675
3676 self._ConfigureDepth(depth)
3677
3678 # Set the remote URL before the remote branch as we might need it below.
3679 if manifest_url:
3680 r = self.GetRemote(self.remote.name)
3681 r.url = manifest_url
3682 r.ResetFetch()
3683 r.Save()
3684
3685 if not standalone_manifest:
3686 if manifest_branch:
3687 if manifest_branch == 'HEAD':
3688 manifest_branch = self.ResolveRemoteHead()
3689 if manifest_branch is None:
3690 print('fatal: unable to resolve HEAD', file=sys.stderr)
3691 return False
3692 self.revisionExpr = manifest_branch
3693 else:
3694 if is_new:
3695 default_branch = self.ResolveRemoteHead()
3696 if default_branch is None:
3697 # If the remote doesn't have HEAD configured, default to master.
3698 default_branch = 'refs/heads/master'
3699 self.revisionExpr = default_branch
3700 else:
3701 self.PreSync()
3702
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003703 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003704 all_platforms = ['linux', 'darwin', 'windows']
3705 platformize = lambda x: 'platform-' + x
3706 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003707 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003708 groups.append(platformize(self._platform_name))
3709 elif platform == 'all':
3710 groups.extend(map(platformize, all_platforms))
3711 elif platform in all_platforms:
3712 groups.append(platformize(platform))
3713 elif platform != 'none':
3714 print('fatal: invalid platform flag', file=sys.stderr)
3715 return False
LaMont Jones4ada0432022-04-14 15:10:43 +00003716 self.config.SetString('manifest.platform', platform)
LaMont Jones9b03f152022-03-29 23:01:18 +00003717
3718 groups = [x for x in groups if x]
3719 groupstr = ','.join(groups)
3720 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3721 groupstr = None
3722 self.config.SetString('manifest.groups', groupstr)
3723
3724 if reference:
3725 self.config.SetString('repo.reference', reference)
3726
3727 if dissociate:
3728 self.config.SetBoolean('repo.dissociate', dissociate)
3729
3730 if worktree:
3731 if mirror:
3732 print('fatal: --mirror and --worktree are incompatible',
3733 file=sys.stderr)
3734 return False
3735 if submodules:
3736 print('fatal: --submodules and --worktree are incompatible',
3737 file=sys.stderr)
3738 return False
3739 self.config.SetBoolean('repo.worktree', worktree)
3740 if is_new:
3741 self.use_git_worktrees = True
3742 print('warning: --worktree is experimental!', file=sys.stderr)
3743
3744 if archive:
3745 if is_new:
3746 self.config.SetBoolean('repo.archive', archive)
3747 else:
3748 print('fatal: --archive is only supported when initializing a new '
3749 'workspace.', file=sys.stderr)
3750 print('Either delete the .repo folder in this workspace, or initialize '
3751 'in another location.', file=sys.stderr)
3752 return False
3753
3754 if mirror:
3755 if is_new:
3756 self.config.SetBoolean('repo.mirror', mirror)
3757 else:
3758 print('fatal: --mirror is only supported when initializing a new '
3759 'workspace.', file=sys.stderr)
3760 print('Either delete the .repo folder in this workspace, or initialize '
3761 'in another location.', file=sys.stderr)
3762 return False
3763
3764 if partial_clone is not None:
3765 if mirror:
3766 print('fatal: --mirror and --partial-clone are mutually exclusive',
3767 file=sys.stderr)
3768 return False
3769 self.config.SetBoolean('repo.partialclone', partial_clone)
3770 if clone_filter:
3771 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jones55ee3042022-04-06 17:10:21 +00003772 elif self.partial_clone:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003773 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003774 else:
3775 clone_filter = None
3776
3777 if partial_clone_exclude is not None:
3778 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3779
3780 if clone_bundle is None:
3781 clone_bundle = False if partial_clone else True
3782 else:
3783 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3784
3785 if submodules:
3786 self.config.SetBoolean('repo.submodules', submodules)
3787
3788 if git_lfs is not None:
3789 if git_lfs:
3790 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3791
3792 self.config.SetBoolean('repo.git-lfs', git_lfs)
3793 if not is_new:
3794 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3795 ' Existing projects will require manual updates.\n', file=sys.stderr)
3796
3797 if use_superproject is not None:
3798 self.config.SetBoolean('repo.superproject', use_superproject)
3799
LaMont Jones0165e202022-04-27 17:34:42 +00003800 if not standalone_manifest:
3801 if not self.Sync_NetworkHalf(
3802 is_new=is_new, quiet=not verbose, verbose=verbose,
3803 clone_bundle=clone_bundle, current_branch_only=current_branch_only,
3804 tags=tags, submodules=submodules, clone_filter=clone_filter,
3805 partial_clone_exclude=self.manifest.PartialCloneExclude):
3806 r = self.GetRemote(self.remote.name)
3807 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
LaMont Jones9b03f152022-03-29 23:01:18 +00003808
LaMont Jones0165e202022-04-27 17:34:42 +00003809 # Better delete the manifest git dir if we created it; otherwise next
3810 # time (when user fixes problems) we won't go through the "is_new" logic.
3811 if is_new:
3812 platform_utils.rmtree(self.gitdir)
LaMont Jones9b03f152022-03-29 23:01:18 +00003813 return False
3814
LaMont Jones0165e202022-04-27 17:34:42 +00003815 if manifest_branch:
3816 self.MetaBranchSwitch(submodules=submodules)
3817
3818 syncbuf = SyncBuffer(self.config)
3819 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3820 syncbuf.Finish()
3821
3822 if is_new or self.CurrentBranch is None:
3823 if not self.StartBranch('default'):
3824 print('fatal: cannot create default in manifest', file=sys.stderr)
3825 return False
3826
3827 if not manifest_name:
3828 print('fatal: manifest name (-m) is required.', file=sys.stderr)
3829 return False
3830
3831 elif is_new:
3832 # This is a new standalone manifest.
3833 manifest_name = 'default.xml'
3834 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3835 dest = os.path.join(self.worktree, manifest_name)
3836 os.makedirs(os.path.dirname(dest), exist_ok=True)
3837 with open(dest, 'wb') as f:
3838 f.write(manifest_data)
LaMont Jones409407a2022-04-05 21:21:56 +00003839
3840 try:
3841 self.manifest.Link(manifest_name)
3842 except ManifestParseError as e:
3843 print("fatal: manifest '%s' not available" % manifest_name,
3844 file=sys.stderr)
3845 print('fatal: %s' % str(e), file=sys.stderr)
3846 return False
3847
LaMont Jones55ee3042022-04-06 17:10:21 +00003848 if not this_manifest_only:
3849 for submanifest in self.manifest.submanifests.values():
LaMont Jonesb90a4222022-04-14 15:00:09 +00003850 spec = submanifest.ToSubmanifestSpec()
LaMont Jones55ee3042022-04-06 17:10:21 +00003851 submanifest.repo_client.manifestProject.Sync(
3852 manifest_url=spec.manifestUrl,
3853 manifest_branch=spec.revision,
3854 standalone_manifest=standalone_manifest,
3855 groups=self.manifest_groups,
3856 platform=platform,
3857 mirror=mirror,
3858 dissociate=dissociate,
3859 reference=reference,
3860 worktree=worktree,
3861 submodules=submodules,
3862 archive=archive,
3863 partial_clone=partial_clone,
3864 clone_filter=clone_filter,
3865 partial_clone_exclude=partial_clone_exclude,
3866 clone_bundle=clone_bundle,
3867 git_lfs=git_lfs,
3868 use_superproject=use_superproject,
3869 verbose=verbose,
3870 current_branch_only=current_branch_only,
3871 tags=tags,
3872 depth=depth,
3873 git_event_log=git_event_log,
3874 manifest_name=spec.manifestName,
3875 this_manifest_only=False,
3876 outer_manifest=False,
3877 )
LaMont Jones409407a2022-04-05 21:21:56 +00003878
LaMont Jones0ddb6772022-05-20 09:11:54 +00003879 # Lastly, if the manifest has a <superproject> then have the superproject
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003880 # sync it (if it will be used).
3881 if git_superproject.UseSuperproject(use_superproject, self.manifest):
LaMont Jones0ddb6772022-05-20 09:11:54 +00003882 sync_result = self.manifest.superproject.Sync(git_event_log)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003883 if not sync_result.success:
3884 print('warning: git update of superproject for '
3885 f'{self.manifest.path_prefix} failed, repo sync will not use '
3886 'superproject to fetch source; while this error is not fatal, '
3887 'and you can continue to run repo sync, please run repo init '
3888 'with the --no-use-superproject option to stop seeing this '
3889 'warning', file=sys.stderr)
3890 if sync_result.fatal and use_superproject is not None:
3891 return False
LaMont Jones409407a2022-04-05 21:21:56 +00003892
LaMont Jones9b03f152022-03-29 23:01:18 +00003893 return True
3894
3895 def _ConfigureDepth(self, depth):
3896 """Configure the depth we'll sync down.
3897
3898 Args:
3899 depth: an int, how deep of a partial clone to create.
3900 """
3901 # Opt.depth will be non-None if user actually passed --depth to repo init.
3902 if depth is not None:
3903 if depth > 0:
3904 # Positive values will set the depth.
3905 depth = str(depth)
3906 else:
3907 # Negative numbers will clear the depth; passing None to SetString
3908 # will do that.
3909 depth = None
3910
3911 # We store the depth in the main manifest project.
3912 self.config.SetString('repo.depth', depth)