blob: 48cf08c65f9057efb7bbf8151b413a042fa84a8f [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.
LaMont Jones68d69632022-06-07 18:24:20 +0000467 @property
468 def shareable_dirs(self):
469 """Return the shareable directories"""
470 if self.UseAlternates:
471 return ['hooks', 'rr-cache']
472 else:
473 return ['hooks', 'objects', 'rr-cache']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700474
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700475 def __init__(self,
476 manifest,
477 name,
478 remote,
479 gitdir,
David James8d201162013-10-11 17:03:19 -0700480 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700481 worktree,
482 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700483 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800484 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100485 rebase=True,
486 groups=None,
487 sync_c=False,
488 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900489 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100490 clone_depth=None,
491 upstream=None,
492 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500493 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100494 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900495 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700496 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600497 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700498 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800499 """Init a Project object.
500
501 Args:
502 manifest: The XmlManifest object.
503 name: The `name` attribute of manifest.xml's project element.
504 remote: RemoteSpec object specifying its remote's properties.
505 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700506 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800507 worktree: Absolute path of git working tree.
508 relpath: Relative path of git working tree to repo's top directory.
509 revisionExpr: The `revision` attribute of manifest.xml's project element.
510 revisionId: git commit id for checking out.
511 rebase: The `rebase` attribute of manifest.xml's project element.
512 groups: The `groups` attribute of manifest.xml's project element.
513 sync_c: The `sync-c` attribute of manifest.xml's project element.
514 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900515 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800516 upstream: The `upstream` attribute of manifest.xml's project element.
517 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500518 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800519 is_derived: False if the project was explicitly defined in the manifest;
520 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400521 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900522 optimized_fetch: If True, when a project is set to a sha1 revision, only
523 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600524 retry_fetches: Retry remote fetches n times upon receiving transient error
525 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700526 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800527 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400528 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700529 self.name = name
530 self.remote = remote
Michael Kelly37c21c22020-06-13 02:10:40 -0700531 self.UpdatePaths(relpath, worktree, gitdir, objdir)
Michael Kelly2f3c3312020-07-21 19:40:38 -0700532 self.SetRevision(revisionExpr, revisionId=revisionId)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700533
Mike Pontillod3153822012-02-28 11:53:24 -0800534 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700535 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700536 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800537 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900538 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900539 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700540 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800541 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500542 # NB: Do not use this setting in __init__ to change behavior so that the
543 # manifest.git checkout can inspect & change it after instantiating. See
544 # the XmlManifest init code for more info.
545 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800546 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900547 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600548 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800549 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800550
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700551 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500553 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500554 self.annotations = []
Bryan Jacobsf609f912013-05-06 13:36:24 -0400555 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700556 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700557
Doug Anderson37282b42011-03-04 11:54:18 -0800558 # This will be filled in if a project is later identified to be the
559 # project containing repo hooks.
560 self.enabled_repo_hooks = []
561
LaMont Jonescc879a92021-11-18 22:40:18 +0000562 def RelPath(self, local=True):
563 """Return the path for the project relative to a manifest.
564
565 Args:
566 local: a boolean, if True, the path is relative to the local
567 (sub)manifest. If false, the path is relative to the
568 outermost manifest.
569 """
570 if local:
571 return self.relpath
572 return os.path.join(self.manifest.path_prefix, self.relpath)
573
Michael Kelly2f3c3312020-07-21 19:40:38 -0700574 def SetRevision(self, revisionExpr, revisionId=None):
575 """Set revisionId based on revision expression and id"""
576 self.revisionExpr = revisionExpr
577 if revisionId is None and revisionExpr and IsId(revisionExpr):
578 self.revisionId = self.revisionExpr
579 else:
580 self.revisionId = revisionId
581
Michael Kelly37c21c22020-06-13 02:10:40 -0700582 def UpdatePaths(self, relpath, worktree, gitdir, objdir):
583 """Update paths used by this project"""
584 self.gitdir = gitdir.replace('\\', '/')
585 self.objdir = objdir.replace('\\', '/')
586 if worktree:
587 self.worktree = os.path.normpath(worktree).replace('\\', '/')
588 else:
589 self.worktree = None
590 self.relpath = relpath
591
592 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
593 defaults=self.manifest.globalConfig)
594
595 if self.worktree:
596 self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
597 else:
598 self.work_git = None
599 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
600 self.bare_ref = GitRefs(self.gitdir)
601 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
602
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700603 @property
LaMont Jones68d69632022-06-07 18:24:20 +0000604 def UseAlternates(self):
605 """Whether git alternates are in use.
606
607 This will be removed once migration to alternates is complete.
608 """
609 return _ALTERNATES or self.manifest.is_multimanifest
610
611 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800612 def Derived(self):
613 return self.is_derived
614
615 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700616 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700617 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700618
619 @property
620 def CurrentBranch(self):
621 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400622
623 The branch name omits the 'refs/heads/' prefix.
624 None is returned if the project is on a detached HEAD, or if the work_git is
625 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700626 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400627 try:
628 b = self.work_git.GetHead()
629 except NoManifestException:
630 # If the local checkout is in a bad state, don't barf. Let the callers
631 # process this like the head is unreadable.
632 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700633 if b.startswith(R_HEADS):
634 return b[len(R_HEADS):]
635 return None
636
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700637 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500638 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
639 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
640 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200641
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700642 def IsDirty(self, consider_untracked=True):
643 """Is the working directory modified in some way?
644 """
645 self.work_git.update_index('-q',
646 '--unmerged',
647 '--ignore-missing',
648 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900649 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700650 return True
651 if self.work_git.DiffZ('diff-files'):
652 return True
653 if consider_untracked and self.work_git.LsOthers():
654 return True
655 return False
656
657 _userident_name = None
658 _userident_email = None
659
660 @property
661 def UserName(self):
662 """Obtain the user's personal name.
663 """
664 if self._userident_name is None:
665 self._LoadUserIdentity()
666 return self._userident_name
667
668 @property
669 def UserEmail(self):
670 """Obtain the user's email address. This is very likely
671 to be their Gerrit login.
672 """
673 if self._userident_email is None:
674 self._LoadUserIdentity()
675 return self._userident_email
676
677 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900678 u = self.bare_git.var('GIT_COMMITTER_IDENT')
679 m = re.compile("^(.*) <([^>]*)> ").match(u)
680 if m:
681 self._userident_name = m.group(1)
682 self._userident_email = m.group(2)
683 else:
684 self._userident_name = ''
685 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700686
687 def GetRemote(self, name):
688 """Get the configuration for a single remote.
689 """
690 return self.config.GetRemote(name)
691
692 def GetBranch(self, name):
693 """Get the configuration for a single branch.
694 """
695 return self.config.GetBranch(name)
696
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700697 def GetBranches(self):
698 """Get all existing local branches.
699 """
700 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900701 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700702 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700703
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530704 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700705 if name.startswith(R_HEADS):
706 name = name[len(R_HEADS):]
707 b = self.GetBranch(name)
708 b.current = name == current
709 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900710 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700711 heads[name] = b
712
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530713 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700714 if name.startswith(R_PUB):
715 name = name[len(R_PUB):]
716 b = heads.get(name)
717 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900718 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700719
720 return heads
721
Colin Cross5acde752012-03-28 20:15:45 -0700722 def MatchesGroups(self, manifest_groups):
723 """Returns true if the manifest groups specified at init should cause
724 this project to be synced.
725 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700726 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700727
728 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700729 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700730 manifest_groups: "-group1,group2"
731 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500732
733 The special manifest group "default" will match any project that
734 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700735 """
LaMont Jones501733c2022-04-20 16:42:32 +0000736 default_groups = self.manifest.default_groups or ['default']
737 expanded_manifest_groups = manifest_groups or default_groups
Conley Owensbb1b5f52012-08-13 13:11:18 -0700738 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700739 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500740 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700741
Conley Owens971de8e2012-04-16 10:36:08 -0700742 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700743 for group in expanded_manifest_groups:
744 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700745 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700746 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700747 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700748
Conley Owens971de8e2012-04-16 10:36:08 -0700749 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700750
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700751# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700752 def UncommitedFiles(self, get_all=True):
753 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700754
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700755 Args:
756 get_all: a boolean, if True - get information about all different
757 uncommitted files. If False - return as soon as any kind of
758 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500759 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700760 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500761 self.work_git.update_index('-q',
762 '--unmerged',
763 '--ignore-missing',
764 '--refresh')
765 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700766 details.append("rebase in progress")
767 if not get_all:
768 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500769
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700770 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
771 if changes:
772 details.extend(changes)
773 if not get_all:
774 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500775
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700776 changes = self.work_git.DiffZ('diff-files').keys()
777 if changes:
778 details.extend(changes)
779 if not get_all:
780 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500781
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700782 changes = self.work_git.LsOthers()
783 if changes:
784 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500785
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700786 return details
787
788 def HasChanges(self):
789 """Returns true if there are uncommitted changes.
790 """
791 if self.UncommitedFiles(get_all=False):
792 return True
793 else:
794 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500795
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600796 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700797 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200798
799 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200800 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600801 quiet: If True then only print the project name. Do not print
802 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700804 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700805 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200806 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700807 print(file=output_redir)
808 print('project %s/' % self.relpath, file=output_redir)
809 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810 return
811
812 self.work_git.update_index('-q',
813 '--unmerged',
814 '--ignore-missing',
815 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700816 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700817 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
818 df = self.work_git.DiffZ('diff-files')
819 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100820 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700821 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700822
823 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700824 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200825 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700826 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700827
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600828 if quiet:
829 out.nl()
830 return 'DIRTY'
831
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 branch = self.CurrentBranch
833 if branch is None:
834 out.nobranch('(*** NO BRANCH ***)')
835 else:
836 out.branch('branch %s', branch)
837 out.nl()
838
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700839 if rb:
840 out.important('prior sync failed; rebase still in progress')
841 out.nl()
842
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843 paths = list()
844 paths.extend(di.keys())
845 paths.extend(df.keys())
846 paths.extend(do)
847
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530848 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900849 try:
850 i = di[p]
851 except KeyError:
852 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900854 try:
855 f = df[p]
856 except KeyError:
857 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200858
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900859 if i:
860 i_status = i.status.upper()
861 else:
862 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700863
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900864 if f:
865 f_status = f.status.lower()
866 else:
867 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868
869 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800870 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700871 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872 else:
873 line = ' %s%s\t%s' % (i_status, f_status, p)
874
875 if i and not f:
876 out.added('%s', line)
877 elif (i and f) or (not i and f):
878 out.changed('%s', line)
879 elif not i and not f:
880 out.untracked('%s', line)
881 else:
882 out.write('%s', line)
883 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200884
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700885 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500887 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888 """Prints the status of the repository to stdout.
889 """
890 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500891 if output_redir:
892 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893 cmd = ['diff']
894 if out.is_on:
895 cmd.append('--color')
896 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300897 if absolute_paths:
898 cmd.append('--src-prefix=a/%s/' % self.relpath)
899 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400901 try:
902 p = GitCommand(self,
903 cmd,
904 capture_stdout=True,
905 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500906 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400907 except GitError as e:
908 out.nl()
909 out.project('project %s/' % self.relpath)
910 out.nl()
911 out.fail('%s', str(e))
912 out.nl()
913 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500914 if p.stdout:
915 out.nl()
916 out.project('project %s/' % self.relpath)
917 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500918 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400919 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700921# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900922 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923 """Was the branch published (uploaded) for code review?
924 If so, returns the SHA-1 hash of the last published
925 state for the branch.
926 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700927 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900928 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700929 try:
930 return self.bare_git.rev_parse(key)
931 except GitError:
932 return None
933 else:
934 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900935 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700936 except KeyError:
937 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700938
David Pursehouse8a68ff92012-09-24 12:15:13 +0900939 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700940 """Prunes any stale published refs.
941 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900942 if all_refs is None:
943 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944 heads = set()
945 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530946 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947 if name.startswith(R_HEADS):
948 heads.add(name)
949 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900950 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530952 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953 n = name[len(R_PUB):]
954 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900955 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700956
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700957 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958 """List any branches which can be uploaded for review.
959 """
960 heads = {}
961 pubed = {}
962
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530963 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900965 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900967 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968
969 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530970 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900971 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700973 if selected_branch and branch != selected_branch:
974 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700975
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800976 rb = self.GetUploadableBranch(branch)
977 if rb:
978 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 return ready
980
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800981 def GetUploadableBranch(self, branch_name):
982 """Get a single uploadable branch, or None.
983 """
984 branch = self.GetBranch(branch_name)
985 base = branch.LocalMerge
986 if branch.LocalMerge:
987 rb = ReviewableBranch(self, branch, base)
988 if rb.commits:
989 return rb
990 return None
991
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700992 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100993 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500994 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700995 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500996 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500997 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200998 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700999 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001000 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001001 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001002 validate_certs=True,
1003 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001004 """Uploads the named branch for code review.
1005 """
1006 if branch is None:
1007 branch = self.CurrentBranch
1008 if branch is None:
1009 raise GitError('not currently on a branch')
1010
1011 branch = self.GetBranch(branch)
1012 if not branch.LocalMerge:
1013 raise GitError('branch %s does not track a remote' % branch.name)
1014 if not branch.remote.review:
1015 raise GitError('remote %s has no review url' % branch.remote.name)
1016
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001017 # Basic validity check on label syntax.
1018 for label in labels:
1019 if not re.match(r'^.+[+-][0-9]+$', label):
1020 raise UploadError(
1021 f'invalid label syntax "{label}": labels use forms like '
1022 'CodeReview+1 or Verified-1')
1023
Bryan Jacobsf609f912013-05-06 13:36:24 -04001024 if dest_branch is None:
1025 dest_branch = self.dest_branch
1026 if dest_branch is None:
1027 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028 if not dest_branch.startswith(R_HEADS):
1029 dest_branch = R_HEADS + dest_branch
1030
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001031 if not branch.remote.projectname:
1032 branch.remote.projectname = self.name
1033 branch.remote.Save()
1034
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001035 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001036 if url is None:
1037 raise UploadError('review not configured')
1038 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001039 if dryrun:
1040 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001041
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001042 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001043 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001044
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001045 for push_option in (push_options or []):
1046 cmd.append('-o')
1047 cmd.append(push_option)
1048
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001049 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001050
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001051 if dest_branch.startswith(R_HEADS):
1052 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001053
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001054 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001055 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001056 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001057 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001058 opts += ['t=%s' % p for p in hashtags]
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001059 # NB: No need to encode labels as they've been validated above.
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001060 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001061
David Pursehousef25a3702018-11-14 19:01:22 -08001062 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001063 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001064 if notify:
1065 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001066 if private:
1067 opts += ['private']
1068 if wip:
1069 opts += ['wip']
1070 if opts:
1071 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001072 cmd.append(ref_spec)
1073
Anthony King7bdac712014-07-16 12:56:40 +01001074 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001075 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076
Mike Frysingerd7f86832020-11-19 19:18:46 -05001077 if not dryrun:
1078 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1079 self.bare_git.UpdateRef(R_PUB + branch.name,
1080 R_HEADS + branch.name,
1081 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001083# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001084 def _ExtractArchive(self, tarpath, path=None):
1085 """Extract the given tar on its current location
1086
1087 Args:
1088 - tarpath: The path to the actual tar file
1089
1090 """
1091 try:
1092 with tarfile.open(tarpath, 'r') as tar:
1093 tar.extractall(path=path)
1094 return True
1095 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001096 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001097 return False
1098
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001099 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001100 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001101 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001102 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001103 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001104 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001105 force_sync=False,
1106 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001107 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001108 archive=False,
1109 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001110 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001111 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001112 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001113 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001114 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001115 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001116 """Perform only the network IO portion of the sync process.
1117 Local working directory/branch state is not affected.
1118 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001119 if archive and not isinstance(self, MetaProject):
1120 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001121 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001122 return False
1123
1124 name = self.relpath.replace('\\', '/')
1125 name = name.replace('/', '_')
1126 tarpath = '%s.tar' % name
1127 topdir = self.manifest.topdir
1128
1129 try:
1130 self._FetchArchive(tarpath, cwd=topdir)
1131 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001132 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001133 return False
1134
1135 # From now on, we only need absolute tarpath
1136 tarpath = os.path.join(topdir, tarpath)
1137
1138 if not self._ExtractArchive(tarpath, path=topdir):
1139 return False
1140 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001141 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001142 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001143 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001144 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001145 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001146
1147 # If the shared object dir already exists, don't try to rebootstrap with a
1148 # clone bundle download. We should have the majority of objects already.
1149 if clone_bundle and os.path.exists(self.objdir):
1150 clone_bundle = False
1151
Raman Tennetif32f2432021-04-12 20:57:25 -07001152 if self.name in partial_clone_exclude:
1153 clone_bundle = True
1154 clone_filter = None
1155
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001156 if is_new is None:
1157 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001158 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001159 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001160 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001161 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001163
LaMont Jones68d69632022-06-07 18:24:20 +00001164 if self.UseAlternates:
Mike Frysinger1d00a7e2021-12-21 00:40:31 -05001165 # If gitdir/objects is a symlink, migrate it from the old layout.
1166 gitdir_objects = os.path.join(self.gitdir, 'objects')
1167 if platform_utils.islink(gitdir_objects):
1168 platform_utils.remove(gitdir_objects, missing_ok=True)
1169 gitdir_alt = os.path.join(self.gitdir, 'objects/info/alternates')
1170 if not os.path.exists(gitdir_alt):
1171 os.makedirs(os.path.dirname(gitdir_alt), exist_ok=True)
1172 _lwrite(gitdir_alt, os.path.join(
1173 os.path.relpath(self.objdir, gitdir_objects), 'objects') + '\n')
1174
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001175 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001176 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001177 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001178 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001179 # This works for both absolute and relative alternate directories.
1180 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001181 except IOError:
1182 alt_dir = None
1183 else:
1184 alt_dir = None
1185
Mike Frysingere50b6a72020-02-19 01:45:48 -05001186 if (clone_bundle
1187 and alt_dir is None
1188 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001189 is_new = False
1190
Mike Frysinger73561142021-05-03 01:10:09 -04001191 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001192 if self.sync_c:
1193 current_branch_only = True
1194 elif not self.manifest._loaded:
1195 # Manifest cannot check defaults until it syncs.
1196 current_branch_only = False
1197 elif self.manifest.default.sync_c:
1198 current_branch_only = True
1199
Mike Frysingerd68ed632021-05-03 01:21:35 -04001200 if tags is None:
1201 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001202
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001203 if self.clone_depth:
1204 depth = self.clone_depth
1205 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001206 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001207
Mike Frysinger521d01b2020-02-17 01:51:49 -05001208 # See if we can skip the network fetch entirely.
1209 if not (optimized_fetch and
1210 (ID_RE.match(self.revisionExpr) and
1211 self._CheckForImmutableRevision())):
1212 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001213 initial=is_new,
1214 quiet=quiet, verbose=verbose, output_redir=output_redir,
1215 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001216 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001217 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001218 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001219 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001220 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001221
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001222 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001223 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001224 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001225 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001226 if os.path.exists(alternates_file):
1227 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001228 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1229 merge_output=bool(output_redir))
1230 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001231 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001232 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001233 return False
1234 platform_utils.remove(alternates_file)
1235
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001236 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001237 self._InitMRef()
1238 else:
1239 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001240 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1241 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001243
1244 def PostRepoUpgrade(self):
1245 self._InitHooks()
1246
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001247 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001248 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001249 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001250 for copyfile in self.copyfiles:
1251 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001252 for linkfile in self.linkfiles:
1253 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001254
Julien Camperguedd654222014-01-09 16:21:37 +01001255 def GetCommitRevisionId(self):
1256 """Get revisionId of a commit.
1257
1258 Use this method instead of GetRevisionId to get the id of the commit rather
1259 than the id of the current git object (for example, a tag)
1260
1261 """
1262 if not self.revisionExpr.startswith(R_TAGS):
1263 return self.GetRevisionId(self._allrefs)
1264
1265 try:
1266 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1267 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001268 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1269 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001270
David Pursehouse8a68ff92012-09-24 12:15:13 +09001271 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001272 if self.revisionId:
1273 return self.revisionId
1274
1275 rem = self.GetRemote(self.remote.name)
1276 rev = rem.ToLocal(self.revisionExpr)
1277
David Pursehouse8a68ff92012-09-24 12:15:13 +09001278 if all_refs is not None and rev in all_refs:
1279 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001280
1281 try:
1282 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1283 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001284 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1285 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001286
Raman Tenneti6a872c92021-01-14 19:17:50 -08001287 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001288 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001289 self.upstream = self.revisionExpr
1290
Raman Tenneti6a872c92021-01-14 19:17:50 -08001291 self.revisionId = revisionId
1292
Martin Kellye4e94d22017-03-21 16:05:12 -07001293 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 """Perform only the local IO portion of the sync process.
1295 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001297 if not os.path.exists(self.gitdir):
1298 syncbuf.fail(self,
1299 'Cannot checkout %s due to missing network sync; Run '
1300 '`repo sync -n %s` first.' %
1301 (self.name, self.name))
1302 return
1303
Martin Kellye4e94d22017-03-21 16:05:12 -07001304 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001305 all_refs = self.bare_ref.all
1306 self.CleanPublishedCache(all_refs)
1307 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001308
Mike Frysinger0458faa2021-03-10 23:35:44 -05001309 # Special case the root of the repo client checkout. Make sure it doesn't
1310 # contain files being checked out to dirs we don't allow.
1311 if self.relpath == '.':
1312 PROTECTED_PATHS = {'.repo'}
1313 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1314 bad_paths = paths & PROTECTED_PATHS
1315 if bad_paths:
1316 syncbuf.fail(self,
1317 'Refusing to checkout project that writes to protected '
1318 'paths: %s' % (', '.join(bad_paths),))
1319 return
1320
David Pursehouse1d947b32012-10-25 12:23:11 +09001321 def _doff():
1322 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001323 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001324
Martin Kellye4e94d22017-03-21 16:05:12 -07001325 def _dosubmodules():
1326 self._SyncSubmodules(quiet=True)
1327
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001328 head = self.work_git.GetHead()
1329 if head.startswith(R_HEADS):
1330 branch = head[len(R_HEADS):]
1331 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001332 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001333 except KeyError:
1334 head = None
1335 else:
1336 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001338 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001339 # Currently on a detached HEAD. The user is assumed to
1340 # not have any local modifications worth worrying about.
1341 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001342 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001343 syncbuf.fail(self, _PriorSyncFailedError())
1344 return
1345
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001346 if head == revid:
1347 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001348 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001349 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001350 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001351 # The copy/linkfile config may have changed.
1352 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001353 return
1354 else:
1355 lost = self._revlist(not_rev(revid), HEAD)
1356 if lost:
1357 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001358
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001360 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001361 if submodules:
1362 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001363 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001364 syncbuf.fail(self, e)
1365 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001366 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001367 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001369 if head == revid:
1370 # No changes; don't do anything further.
1371 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001372 # The copy/linkfile config may have changed.
1373 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001374 return
1375
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001377
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001378 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001379 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001380 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001382 syncbuf.info(self,
1383 "leaving %s; does not track upstream",
1384 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001386 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001387 if submodules:
1388 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001389 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001390 syncbuf.fail(self, e)
1391 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001392 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001393 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001395 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001396
1397 # See if we can perform a fast forward merge. This can happen if our
1398 # branch isn't in the exact same state as we last published.
1399 try:
1400 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1401 # Skip the published logic.
1402 pub = False
1403 except GitError:
1404 pub = self.WasPublished(branch.name, all_refs)
1405
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001406 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001407 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001408 if not_merged:
1409 if upstream_gain:
1410 # The user has published this branch and some of those
1411 # commits are not yet merged upstream. We do not want
1412 # to rewrite the published commits so we punt.
1413 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001414 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001415 "branch %s is published (but not merged) and is now "
1416 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001417 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001418 elif pub == head:
1419 # All published commits are merged, and thus we are a
1420 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001421 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001422 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001423 if submodules:
1424 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001425 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001426
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001427 # Examine the local commits not in the remote. Find the
1428 # last one attributed to this user, if any.
1429 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001430 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001431 last_mine = None
1432 cnt_mine = 0
1433 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001434 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001435 if committer_email == self.UserEmail:
1436 last_mine = commit_id
1437 cnt_mine += 1
1438
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001439 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001440 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001441
1442 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001443 syncbuf.fail(self, _DirtyError())
1444 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001445
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001446 # If the upstream switched on us, warn the user.
1447 #
1448 if branch.merge != self.revisionExpr:
1449 if branch.merge and self.revisionExpr:
1450 syncbuf.info(self,
1451 'manifest switched %s...%s',
1452 branch.merge,
1453 self.revisionExpr)
1454 elif branch.merge:
1455 syncbuf.info(self,
1456 'manifest no longer tracks %s',
1457 branch.merge)
1458
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001459 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001460 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001461 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001462 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001463 syncbuf.info(self,
1464 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001465 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001466
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001467 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001468 if not ID_RE.match(self.revisionExpr):
1469 # in case of manifest sync the revisionExpr might be a SHA1
1470 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001471 if not branch.merge.startswith('refs/'):
1472 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001473 branch.Save()
1474
Mike Pontillod3153822012-02-28 11:53:24 -08001475 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001476 def _docopyandlink():
1477 self._CopyAndLinkFiles()
1478
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001479 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001480 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001481 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001482 if submodules:
1483 syncbuf.later2(self, _dosubmodules)
1484 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001485 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001486 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001487 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001488 if submodules:
1489 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001490 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001491 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001492 syncbuf.fail(self, e)
1493 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001494 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001495 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001496 if submodules:
1497 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001498
Mike Frysingere6a202f2019-08-02 15:57:57 -04001499 def AddCopyFile(self, src, dest, topdir):
1500 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001501
Mike Frysingere6a202f2019-08-02 15:57:57 -04001502 No filesystem changes occur here. Actual copying happens later on.
1503
1504 Paths should have basic validation run on them before being queued.
1505 Further checking will be handled when the actual copy happens.
1506 """
1507 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1508
1509 def AddLinkFile(self, src, dest, topdir):
1510 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1511
1512 No filesystem changes occur here. Actual linking happens later on.
1513
1514 Paths should have basic validation run on them before being queued.
1515 Further checking will be handled when the actual link happens.
1516 """
1517 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001518
James W. Mills24c13082012-04-12 15:04:13 -05001519 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001520 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001521
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001522 def DownloadPatchSet(self, change_id, patch_id):
1523 """Download a single patch set of a single change to FETCH_HEAD.
1524 """
1525 remote = self.GetRemote(self.remote.name)
1526
1527 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001528 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001529 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001530 if GitCommand(self, cmd, bare=True).Wait() != 0:
1531 return None
1532 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001533 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001534 change_id,
1535 patch_id,
1536 self.bare_git.rev_parse('FETCH_HEAD'))
1537
Mike Frysingerc0d18662020-02-19 19:19:18 -05001538 def DeleteWorktree(self, quiet=False, force=False):
1539 """Delete the source checkout and any other housekeeping tasks.
1540
1541 This currently leaves behind the internal .repo/ cache state. This helps
1542 when switching branches or manifest changes get reverted as we don't have
1543 to redownload all the git objects. But we should do some GC at some point.
1544
1545 Args:
1546 quiet: Whether to hide normal messages.
1547 force: Always delete tree even if dirty.
1548
1549 Returns:
1550 True if the worktree was completely cleaned out.
1551 """
1552 if self.IsDirty():
1553 if force:
1554 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1555 (self.relpath,), file=sys.stderr)
1556 else:
1557 print('error: %s: Cannot remove project: uncommitted changes are '
1558 'present.\n' % (self.relpath,), file=sys.stderr)
1559 return False
1560
1561 if not quiet:
1562 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1563
1564 # Unlock and delink from the main worktree. We don't use git's worktree
1565 # remove because it will recursively delete projects -- we handle that
1566 # ourselves below. https://crbug.com/git/48
1567 if self.use_git_worktrees:
1568 needle = platform_utils.realpath(self.gitdir)
1569 # Find the git worktree commondir under .repo/worktrees/.
1570 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1571 assert output.startswith('worktree '), output
1572 commondir = output[9:]
1573 # Walk each of the git worktrees to see where they point.
1574 configs = os.path.join(commondir, 'worktrees')
1575 for name in os.listdir(configs):
1576 gitdir = os.path.join(configs, name, 'gitdir')
1577 with open(gitdir) as fp:
1578 relpath = fp.read().strip()
1579 # Resolve the checkout path and see if it matches this project.
1580 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1581 if fullpath == needle:
1582 platform_utils.rmtree(os.path.join(configs, name))
1583
1584 # Delete the .git directory first, so we're less likely to have a partially
1585 # working git repository around. There shouldn't be any git projects here,
1586 # so rmtree works.
1587
1588 # Try to remove plain files first in case of git worktrees. If this fails
1589 # for any reason, we'll fall back to rmtree, and that'll display errors if
1590 # it can't remove things either.
1591 try:
1592 platform_utils.remove(self.gitdir)
1593 except OSError:
1594 pass
1595 try:
1596 platform_utils.rmtree(self.gitdir)
1597 except OSError as e:
1598 if e.errno != errno.ENOENT:
1599 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1600 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1601 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1602 return False
1603
1604 # Delete everything under the worktree, except for directories that contain
1605 # another git project.
1606 dirs_to_remove = []
1607 failed = False
1608 for root, dirs, files in platform_utils.walk(self.worktree):
1609 for f in files:
1610 path = os.path.join(root, f)
1611 try:
1612 platform_utils.remove(path)
1613 except OSError as e:
1614 if e.errno != errno.ENOENT:
1615 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1616 failed = True
1617 dirs[:] = [d for d in dirs
1618 if not os.path.lexists(os.path.join(root, d, '.git'))]
1619 dirs_to_remove += [os.path.join(root, d) for d in dirs
1620 if os.path.join(root, d) not in dirs_to_remove]
1621 for d in reversed(dirs_to_remove):
1622 if platform_utils.islink(d):
1623 try:
1624 platform_utils.remove(d)
1625 except OSError as e:
1626 if e.errno != errno.ENOENT:
1627 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1628 failed = True
1629 elif not platform_utils.listdir(d):
1630 try:
1631 platform_utils.rmdir(d)
1632 except OSError as e:
1633 if e.errno != errno.ENOENT:
1634 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1635 failed = True
1636 if failed:
1637 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1638 file=sys.stderr)
1639 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1640 return False
1641
1642 # Try deleting parent dirs if they are empty.
1643 path = self.worktree
1644 while path != self.manifest.topdir:
1645 try:
1646 platform_utils.rmdir(path)
1647 except OSError as e:
1648 if e.errno != errno.ENOENT:
1649 break
1650 path = os.path.dirname(path)
1651
1652 return True
1653
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001654# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001655 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001656 """Create a new branch off the manifest's revision.
1657 """
Simran Basib9a1b732015-08-20 12:19:28 -07001658 if not branch_merge:
1659 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001660 head = self.work_git.GetHead()
1661 if head == (R_HEADS + name):
1662 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001663
David Pursehouse8a68ff92012-09-24 12:15:13 +09001664 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001665 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001666 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001667 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001668 capture_stdout=True,
1669 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001670
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001671 branch = self.GetBranch(name)
1672 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001673 branch.merge = branch_merge
1674 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1675 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001676
1677 if revision is None:
1678 revid = self.GetRevisionId(all_refs)
1679 else:
1680 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001681
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001682 if head.startswith(R_HEADS):
1683 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001684 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001685 except KeyError:
1686 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001687 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001688 ref = R_HEADS + name
1689 self.work_git.update_ref(ref, revid)
1690 self.work_git.symbolic_ref(HEAD, ref)
1691 branch.Save()
1692 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001693
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001694 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001695 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001696 capture_stdout=True,
1697 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001698 branch.Save()
1699 return True
1700 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001701
Wink Saville02d79452009-04-10 13:01:24 -07001702 def CheckoutBranch(self, name):
1703 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001704
1705 Args:
1706 name: The name of the branch to checkout.
1707
1708 Returns:
1709 True if the checkout succeeded; False if it didn't; None if the branch
1710 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001711 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001712 rev = R_HEADS + name
1713 head = self.work_git.GetHead()
1714 if head == rev:
1715 # Already on the branch
1716 #
1717 return True
Wink Saville02d79452009-04-10 13:01:24 -07001718
David Pursehouse8a68ff92012-09-24 12:15:13 +09001719 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001720 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001721 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001722 except KeyError:
1723 # Branch does not exist in this project
1724 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001725 return None
Wink Saville02d79452009-04-10 13:01:24 -07001726
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001727 if head.startswith(R_HEADS):
1728 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001729 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001730 except KeyError:
1731 head = None
1732
1733 if head == revid:
1734 # Same revision; just update HEAD to point to the new
1735 # target branch, but otherwise take no other action.
1736 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001737 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1738 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001739 return True
1740
1741 return GitCommand(self,
1742 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001743 capture_stdout=True,
1744 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001745
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001746 def AbandonBranch(self, name):
1747 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001748
1749 Args:
1750 name: The name of the branch to abandon.
1751
1752 Returns:
1753 True if the abandon succeeded; False if it didn't; None if the branch
1754 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001755 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001756 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001757 all_refs = self.bare_ref.all
1758 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001759 # Doesn't exist
1760 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001761
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001762 head = self.work_git.GetHead()
1763 if head == rev:
1764 # We can't destroy the branch while we are sitting
1765 # on it. Switch to a detached HEAD.
1766 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001767 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001768
David Pursehouse8a68ff92012-09-24 12:15:13 +09001769 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001770 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001771 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001772 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001773 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001774
1775 return GitCommand(self,
1776 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001777 capture_stdout=True,
1778 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001779
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780 def PruneHeads(self):
1781 """Prune any topic branches already merged into upstream.
1782 """
1783 cb = self.CurrentBranch
1784 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001785 left = self._allrefs
1786 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001787 if name.startswith(R_HEADS):
1788 name = name[len(R_HEADS):]
1789 if cb is None or name != cb:
1790 kill.append(name)
1791
Mike Frysingera3794e92021-03-11 23:24:01 -05001792 # Minor optimization: If there's nothing to prune, then don't try to read
1793 # any project state.
1794 if not kill and not cb:
1795 return []
1796
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001797 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001798 if cb is not None \
1799 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001800 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001801 self.work_git.DetachHead(HEAD)
1802 kill.append(cb)
1803
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001804 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001805 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001806
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001807 try:
1808 self.bare_git.DetachHead(rev)
1809
1810 b = ['branch', '-d']
1811 b.extend(kill)
1812 b = GitCommand(self, b, bare=True,
1813 capture_stdout=True,
1814 capture_stderr=True)
1815 b.Wait()
1816 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001817 if ID_RE.match(old):
1818 self.bare_git.DetachHead(old)
1819 else:
1820 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001821 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001822
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001823 for branch in kill:
1824 if (R_HEADS + branch) not in left:
1825 self.CleanPublishedCache()
1826 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001827
1828 if cb and cb not in kill:
1829 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001830 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001831
1832 kept = []
1833 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001834 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001835 branch = self.GetBranch(branch)
1836 base = branch.LocalMerge
1837 if not base:
1838 base = rev
1839 kept.append(ReviewableBranch(self, branch, base))
1840 return kept
1841
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001842# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001843 def GetRegisteredSubprojects(self):
1844 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001845
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001846 def rec(subprojects):
1847 if not subprojects:
1848 return
1849 result.extend(subprojects)
1850 for p in subprojects:
1851 rec(p.subprojects)
1852 rec(self.subprojects)
1853 return result
1854
1855 def _GetSubmodules(self):
1856 # Unfortunately we cannot call `git submodule status --recursive` here
1857 # because the working tree might not exist yet, and it cannot be used
1858 # without a working tree in its current implementation.
1859
1860 def get_submodules(gitdir, rev):
1861 # Parse .gitmodules for submodule sub_paths and sub_urls
1862 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1863 if not sub_paths:
1864 return []
1865 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1866 # revision of submodule repository
1867 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1868 submodules = []
1869 for sub_path, sub_url in zip(sub_paths, sub_urls):
1870 try:
1871 sub_rev = sub_revs[sub_path]
1872 except KeyError:
1873 # Ignore non-exist submodules
1874 continue
1875 submodules.append((sub_rev, sub_path, sub_url))
1876 return submodules
1877
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001878 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1879 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001880
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001881 def parse_gitmodules(gitdir, rev):
1882 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1883 try:
Anthony King7bdac712014-07-16 12:56:40 +01001884 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1885 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001886 except GitError:
1887 return [], []
1888 if p.Wait() != 0:
1889 return [], []
1890
1891 gitmodules_lines = []
1892 fd, temp_gitmodules_path = tempfile.mkstemp()
1893 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001894 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001895 os.close(fd)
1896 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001897 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1898 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001899 if p.Wait() != 0:
1900 return [], []
1901 gitmodules_lines = p.stdout.split('\n')
1902 except GitError:
1903 return [], []
1904 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001905 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001906
1907 names = set()
1908 paths = {}
1909 urls = {}
1910 for line in gitmodules_lines:
1911 if not line:
1912 continue
1913 m = re_path.match(line)
1914 if m:
1915 names.add(m.group(1))
1916 paths[m.group(1)] = m.group(2)
1917 continue
1918 m = re_url.match(line)
1919 if m:
1920 names.add(m.group(1))
1921 urls[m.group(1)] = m.group(2)
1922 continue
1923 names = sorted(names)
1924 return ([paths.get(name, '') for name in names],
1925 [urls.get(name, '') for name in names])
1926
1927 def git_ls_tree(gitdir, rev, paths):
1928 cmd = ['ls-tree', rev, '--']
1929 cmd.extend(paths)
1930 try:
Anthony King7bdac712014-07-16 12:56:40 +01001931 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1932 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001933 except GitError:
1934 return []
1935 if p.Wait() != 0:
1936 return []
1937 objects = {}
1938 for line in p.stdout.split('\n'):
1939 if not line.strip():
1940 continue
1941 object_rev, object_path = line.split()[2:4]
1942 objects[object_path] = object_rev
1943 return objects
1944
1945 try:
1946 rev = self.GetRevisionId()
1947 except GitError:
1948 return []
1949 return get_submodules(self.gitdir, rev)
1950
1951 def GetDerivedSubprojects(self):
1952 result = []
1953 if not self.Exists:
1954 # If git repo does not exist yet, querying its submodules will
1955 # mess up its states; so return here.
1956 return result
1957 for rev, path, url in self._GetSubmodules():
1958 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001959 relpath, worktree, gitdir, objdir = \
1960 self.manifest.GetSubprojectPaths(self, name, path)
1961 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001962 if project:
1963 result.extend(project.GetDerivedSubprojects())
1964 continue
David James8d201162013-10-11 17:03:19 -07001965
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001966 if url.startswith('..'):
1967 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001968 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001969 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001970 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001971 review=self.remote.review,
1972 revision=self.remote.revision)
1973 subproject = Project(manifest=self.manifest,
1974 name=name,
1975 remote=remote,
1976 gitdir=gitdir,
1977 objdir=objdir,
1978 worktree=worktree,
1979 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001980 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001981 revisionId=rev,
1982 rebase=self.rebase,
1983 groups=self.groups,
1984 sync_c=self.sync_c,
1985 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001986 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001987 parent=self,
1988 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001989 result.append(subproject)
1990 result.extend(subproject.GetDerivedSubprojects())
1991 return result
1992
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001993# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001994 def EnableRepositoryExtension(self, key, value='true', version=1):
1995 """Enable git repository extension |key| with |value|.
1996
1997 Args:
1998 key: The extension to enabled. Omit the "extensions." prefix.
1999 value: The value to use for the extension.
2000 version: The minimum git repository version needed.
2001 """
2002 # Make sure the git repo version is new enough already.
2003 found_version = self.config.GetInt('core.repositoryFormatVersion')
2004 if found_version is None:
2005 found_version = 0
2006 if found_version < version:
2007 self.config.SetString('core.repositoryFormatVersion', str(version))
2008
2009 # Enable the extension!
2010 self.config.SetString('extensions.%s' % (key,), value)
2011
Mike Frysinger50a81de2020-09-06 15:51:21 -04002012 def ResolveRemoteHead(self, name=None):
2013 """Find out what the default branch (HEAD) points to.
2014
2015 Normally this points to refs/heads/master, but projects are moving to main.
2016 Support whatever the server uses rather than hardcoding "master" ourselves.
2017 """
2018 if name is None:
2019 name = self.remote.name
2020
2021 # The output will look like (NB: tabs are separators):
2022 # ref: refs/heads/master HEAD
2023 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2024 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2025
2026 for line in output.splitlines():
2027 lhs, rhs = line.split('\t', 1)
2028 if rhs == 'HEAD' and lhs.startswith('ref:'):
2029 return lhs[4:].strip()
2030
2031 return None
2032
Zac Livingstone4332262017-06-16 08:56:09 -06002033 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002034 try:
2035 # if revision (sha or tag) is not present then following function
2036 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002037 self.bare_git.rev_list('-1', '--missing=allow-any',
2038 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00002039 if self.upstream:
2040 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
2041 self.bare_git.rev_list('-1', '--missing=allow-any',
2042 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002043 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002044 return True
2045 except GitError:
2046 # There is no such persistent revision. We have to fetch it.
2047 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002048
Julien Campergue335f5ef2013-10-16 11:02:35 +02002049 def _FetchArchive(self, tarpath, cwd=None):
2050 cmd = ['archive', '-v', '-o', tarpath]
2051 cmd.append('--remote=%s' % self.remote.url)
2052 cmd.append('--prefix=%s/' % self.relpath)
2053 cmd.append(self.revisionExpr)
2054
2055 command = GitCommand(self, cmd, cwd=cwd,
2056 capture_stdout=True,
2057 capture_stderr=True)
2058
2059 if command.Wait() != 0:
2060 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2061
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002062 def _RemoteFetch(self, name=None,
2063 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002064 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002065 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002066 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002067 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002068 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002069 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002070 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002071 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002072 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002073 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002074 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002075 clone_filter=None,
2076 retry_fetches=2,
2077 retry_sleep_initial_sec=4.0,
2078 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002079 is_sha1 = False
2080 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002081 # The depth should not be used when fetching to a mirror because
2082 # it will result in a shallow repository that cannot be cloned or
2083 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002084 # The repo project should also never be synced with partial depth.
2085 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2086 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002087
Shawn Pearce69e04d82014-01-29 12:48:54 -08002088 if depth:
2089 current_branch_only = True
2090
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002091 if ID_RE.match(self.revisionExpr) is not None:
2092 is_sha1 = True
2093
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002094 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002095 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002096 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002097 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002098 elif self.upstream and self.upstream.startswith(R_TAGS):
2099 # This is a tag and its commit id should never change.
2100 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002101
2102 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002103 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002104 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002105 print('Skipped fetching project %s (already have persistent ref)'
2106 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002107 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002108 if is_sha1 and not depth:
2109 # When syncing a specific commit and --depth is not set:
2110 # * if upstream is explicitly specified and is not a sha1, fetch only
2111 # upstream as users expect only upstream to be fetch.
2112 # Note: The commit might not be in upstream in which case the sync
2113 # will fail.
2114 # * otherwise, fetch all branches to make sure we end up with the
2115 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002116 if self.upstream:
2117 current_branch_only = not ID_RE.match(self.upstream)
2118 else:
2119 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002120
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002121 if not name:
2122 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002123
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002124 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002125 if not remote.PreConnectFetch(ssh_proxy):
2126 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002127
Shawn O. Pearce88443382010-10-08 10:02:09 +02002128 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002129 if alt_dir and 'objects' == os.path.basename(alt_dir):
2130 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002131 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002132
David Pursehouse8a68ff92012-09-24 12:15:13 +09002133 all_refs = self.bare_ref.all
2134 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002135 tmp = set()
2136
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302137 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002138 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002139 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002140 all_refs[r] = ref_id
2141 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002142 continue
2143
David Pursehouse8a68ff92012-09-24 12:15:13 +09002144 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002145 continue
2146
David Pursehouse8a68ff92012-09-24 12:15:13 +09002147 r = 'refs/_alt/%s' % ref_id
2148 all_refs[r] = ref_id
2149 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002150 tmp.add(r)
2151
heping3d7bbc92017-04-12 19:51:47 +08002152 tmp_packed_lines = []
2153 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002154
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302155 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002156 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002157 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002158 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002159 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002160
heping3d7bbc92017-04-12 19:51:47 +08002161 tmp_packed = ''.join(tmp_packed_lines)
2162 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002163 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002164 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002165 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002166
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002167 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002168
Xin Li745be2e2019-06-03 11:24:30 -07002169 if clone_filter:
2170 git_require((2, 19, 0), fail=True, msg='partial clones')
2171 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002172 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002173
Conley Owensf97e8382015-01-21 11:12:46 -08002174 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002175 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002176 else:
2177 # If this repo has shallow objects, then we don't know which refs have
2178 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2179 # do this with projects that don't have shallow objects, since it is less
2180 # efficient.
2181 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2182 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002183
Mike Frysinger4847e052020-02-22 00:07:35 -05002184 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002185 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002186 if not quiet and sys.stdout.isatty():
2187 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002188 if not self.worktree:
2189 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002190 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002191
Mike Frysingere57f1142019-03-18 21:27:54 -04002192 if force_sync:
2193 cmd.append('--force')
2194
David Pursehouse74cfd272015-10-14 10:50:15 +09002195 if prune:
2196 cmd.append('--prune')
2197
LaMont Jonesff6b1da2022-06-01 21:03:34 +00002198 # Always pass something for --recurse-submodules, git with GIT_DIR behaves
2199 # incorrectly when not given `--recurse-submodules=no`. (b/218891912)
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002200 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002201
Kuang-che Wu6856f982019-11-25 12:37:55 +08002202 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002203 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002204 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002205 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002206 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002207 spec.append('tag')
2208 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002209
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302210 if self.manifest.IsMirror and not current_branch_only:
2211 branch = None
2212 else:
2213 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002214 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002215 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002216 # Shallow checkout of a specific commit, fetch from that commit and not
2217 # the heads only as the commit might be deeper in the history.
2218 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002219 if self.upstream:
2220 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002221 else:
2222 if is_sha1:
2223 branch = self.upstream
2224 if branch is not None and branch.strip():
2225 if not branch.startswith('refs/'):
2226 branch = R_HEADS + branch
2227 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2228
2229 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2230 # whole repo.
2231 if self.manifest.IsMirror and not spec:
2232 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2233
2234 # If using depth then we should not get all the tags since they may
2235 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002236 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002237 cmd.append('--no-tags')
2238 else:
2239 cmd.append('--tags')
2240 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2241
Conley Owens80b87fe2014-05-09 17:13:44 -07002242 cmd.extend(spec)
2243
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002244 # At least one retry minimum due to git remote prune.
2245 retry_fetches = max(retry_fetches, 2)
2246 retry_cur_sleep = retry_sleep_initial_sec
2247 ok = prune_tried = False
2248 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002249 gitcmd = GitCommand(
2250 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2251 ssh_proxy=ssh_proxy,
2252 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002253 if gitcmd.stdout and not quiet and output_redir:
2254 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002255 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002256 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002257 ok = True
2258 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002259
2260 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002261 elif (gitcmd.stdout and
2262 'error:' in gitcmd.stdout and
2263 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002264 # Fallthru to sleep+retry logic at the bottom.
2265 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002266
Mike Frysinger6823bc22021-04-15 02:06:28 -04002267 # Try to prune remote branches once in case there are conflicts.
2268 # For example, if the remote had refs/heads/upstream, but deleted that and
2269 # now has refs/heads/upstream/foo.
2270 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002271 'error:' in gitcmd.stdout and
2272 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002273 not prune_tried):
2274 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002275 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002276 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002277 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002278 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002279 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002280 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002281 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002282 continue
Brian Harring14a66742012-09-28 20:21:57 -07002283 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002284 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2285 # in sha1 mode, we just tried sync'ing from the upstream field; it
2286 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002287 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002288 elif ret < 0:
2289 # Git died with a signal, exit immediately
2290 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002291
2292 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002293 if not verbose and gitcmd.stdout:
2294 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002295 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002296 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2297 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002298 time.sleep(retry_cur_sleep)
2299 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2300 MAXIMUM_RETRY_SLEEP_SEC)
2301 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2302 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002303
2304 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002305 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002306 if old_packed != '':
2307 _lwrite(packed_refs, old_packed)
2308 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002309 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002310 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002311
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002312 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002313 # We just synced the upstream given branch; verify we
2314 # got what we wanted, else trigger a second run of all
2315 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002316 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002317 # Sync the current branch only with depth set to None.
2318 # We always pass depth=None down to avoid infinite recursion.
2319 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002320 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002321 current_branch_only=current_branch_only and depth,
2322 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002323 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002324
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002325 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002326
Mike Frysingere50b6a72020-02-19 01:45:48 -05002327 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002328 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002329 return False
2330
2331 remote = self.GetRemote(self.remote.name)
2332 bundle_url = remote.url + '/clone.bundle'
2333 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002334 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2335 'persistent-http',
2336 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002337 return False
2338
2339 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2340 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2341
2342 exist_dst = os.path.exists(bundle_dst)
2343 exist_tmp = os.path.exists(bundle_tmp)
2344
2345 if not initial and not exist_dst and not exist_tmp:
2346 return False
2347
2348 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002349 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2350 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002351 if not exist_dst:
2352 return False
2353
2354 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002355 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002356 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002357 if not quiet and sys.stdout.isatty():
2358 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002359 if not self.worktree:
2360 cmd.append('--update-head-ok')
2361 cmd.append(bundle_dst)
2362 for f in remote.fetch:
2363 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002364 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002365
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002366 ok = GitCommand(
2367 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002368 platform_utils.remove(bundle_dst, missing_ok=True)
2369 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002370 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002371
Mike Frysingere50b6a72020-02-19 01:45:48 -05002372 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002373 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002374
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002375 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002376 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002377 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002378 if os.path.exists(tmpPath):
2379 size = os.stat(tmpPath).st_size
2380 if size >= 1024:
2381 cmd += ['--continue-at', '%d' % (size,)]
2382 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002383 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002384 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002385 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002386 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002387 if proxy:
2388 cmd += ['--proxy', proxy]
2389 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2390 cmd += ['--proxy', os.environ['http_proxy']]
2391 if srcUrl.startswith('persistent-https'):
2392 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2393 elif srcUrl.startswith('persistent-http'):
2394 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002395 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002396
Dave Borowitz137d0132015-01-02 11:12:54 -08002397 if IsTrace():
2398 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002399 if verbose:
2400 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2401 stdout = None if verbose else subprocess.PIPE
2402 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002403 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002404 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002405 except OSError:
2406 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002407
Mike Frysingere50b6a72020-02-19 01:45:48 -05002408 (output, _) = proc.communicate()
2409 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002410
Dave Borowitz137d0132015-01-02 11:12:54 -08002411 if curlret == 22:
2412 # From curl man page:
2413 # 22: HTTP page not retrieved. The requested url was not found or
2414 # returned another error with the HTTP error code being 400 or above.
2415 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002416 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002417 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2418 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002419 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002420 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002421 elif curlret and not verbose and output:
2422 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002423
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002424 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002425 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002426 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002427 return True
2428 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002429 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002430 return False
2431 else:
2432 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002433
Kris Giesingc8d882a2014-12-23 13:02:32 -08002434 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002435 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002436 with open(path, 'rb') as f:
2437 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002438 return True
2439 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002440 if not quiet:
2441 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002442 return False
2443 except OSError:
2444 return False
2445
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002446 def _Checkout(self, rev, quiet=False):
2447 cmd = ['checkout']
2448 if quiet:
2449 cmd.append('-q')
2450 cmd.append(rev)
2451 cmd.append('--')
2452 if GitCommand(self, cmd).Wait() != 0:
2453 if self._allrefs:
2454 raise GitError('%s checkout %s ' % (self.name, rev))
2455
Mike Frysinger915fda12020-03-22 12:15:20 -04002456 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002457 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002458 if ffonly:
2459 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002460 if record_origin:
2461 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002462 cmd.append(rev)
2463 cmd.append('--')
2464 if GitCommand(self, cmd).Wait() != 0:
2465 if self._allrefs:
2466 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2467
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302468 def _LsRemote(self, refs):
2469 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302470 p = GitCommand(self, cmd, capture_stdout=True)
2471 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002472 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302473 return None
2474
Anthony King7bdac712014-07-16 12:56:40 +01002475 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002476 cmd = ['revert']
2477 cmd.append('--no-edit')
2478 cmd.append(rev)
2479 cmd.append('--')
2480 if GitCommand(self, cmd).Wait() != 0:
2481 if self._allrefs:
2482 raise GitError('%s revert %s ' % (self.name, rev))
2483
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002484 def _ResetHard(self, rev, quiet=True):
2485 cmd = ['reset', '--hard']
2486 if quiet:
2487 cmd.append('-q')
2488 cmd.append(rev)
2489 if GitCommand(self, cmd).Wait() != 0:
2490 raise GitError('%s reset --hard %s ' % (self.name, rev))
2491
Martin Kellye4e94d22017-03-21 16:05:12 -07002492 def _SyncSubmodules(self, quiet=True):
2493 cmd = ['submodule', 'update', '--init', '--recursive']
2494 if quiet:
2495 cmd.append('-q')
2496 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002497 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002498
Anthony King7bdac712014-07-16 12:56:40 +01002499 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002500 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002501 if onto is not None:
2502 cmd.extend(['--onto', onto])
2503 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002504 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002505 raise GitError('%s rebase %s ' % (self.name, upstream))
2506
Pierre Tardy3d125942012-05-04 12:18:12 +02002507 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002508 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002509 if ffonly:
2510 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002511 if GitCommand(self, cmd).Wait() != 0:
2512 raise GitError('%s merge %s ' % (self.name, head))
2513
David Pursehousee8ace262020-02-13 12:41:15 +09002514 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002515 init_git_dir = not os.path.exists(self.gitdir)
2516 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002517 try:
2518 # Initialize the bare repository, which contains all of the objects.
2519 if init_obj_dir:
2520 os.makedirs(self.objdir)
2521 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002522
Mike Frysinger333c0a42021-11-15 12:39:00 -05002523 self._UpdateHooks(quiet=quiet)
2524
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002525 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002526 # Enable per-worktree config file support if possible. This is more a
2527 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002528 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002529 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002530
Kevin Degib1a07b82015-07-27 13:33:43 -06002531 # If we have a separate directory to hold refs, initialize it as well.
2532 if self.objdir != self.gitdir:
2533 if init_git_dir:
2534 os.makedirs(self.gitdir)
2535
2536 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002537 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002538 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002539 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002540 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002541 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002542 print("Retrying clone after deleting %s" %
2543 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002544 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002545 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2546 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002547 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002548 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002549 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2550 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002551 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002552 raise e
2553 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002554
Kevin Degi384b3c52014-10-16 16:02:58 -06002555 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002556 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002557 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002558
LaMont Jonescc879a92021-11-18 22:40:18 +00002559 def _expanded_ref_dirs():
2560 """Iterate through the possible git reference directory paths."""
2561 name = self.name + '.git'
2562 yield mirror_git or os.path.join(ref_dir, name)
2563 for prefix in '', self.remote.name:
2564 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2565 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002566
LaMont Jonescc879a92021-11-18 22:40:18 +00002567 if ref_dir or mirror_git:
2568 found_ref_dir = None
2569 for path in _expanded_ref_dirs():
2570 if os.path.exists(path):
2571 found_ref_dir = path
2572 break
2573 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002574
Kevin Degib1a07b82015-07-27 13:33:43 -06002575 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002576 if not os.path.isabs(ref_dir):
2577 # The alternate directory is relative to the object database.
2578 ref_dir = os.path.relpath(ref_dir,
2579 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002580 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002581 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002582
Kevin Degib1a07b82015-07-27 13:33:43 -06002583 m = self.manifest.manifestProject.config
2584 for key in ['user.name', 'user.email']:
2585 if m.Has(key, include_defaults=False):
2586 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002587 if not self.manifest.EnableGitLfs:
2588 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2589 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002590 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002591 except Exception:
2592 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002593 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002594 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002595 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002596 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002597
David Pursehousee8ace262020-02-13 12:41:15 +09002598 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002599 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002600 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002601
David Pursehousee8ace262020-02-13 12:41:15 +09002602 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002603 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002604 if not os.path.exists(hooks):
2605 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002606
2607 # Delete sample hooks. They're noise.
2608 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002609 try:
2610 platform_utils.remove(hook, missing_ok=True)
2611 except PermissionError:
2612 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002613
Jonathan Nieder93719792015-03-17 11:29:58 -07002614 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002615 name = os.path.basename(stock_hook)
2616
Victor Boivie65e0f352011-04-18 11:23:29 +02002617 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002618 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002619 # Don't install a Gerrit Code Review hook if this
2620 # project does not appear to use it for reviews.
2621 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002622 # Since the manifest project is one of those, but also
2623 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002624 continue
2625
2626 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002627 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002628 continue
2629 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002630 # If the files are the same, we'll leave it alone. We create symlinks
2631 # below by default but fallback to hardlinks if the OS blocks them.
2632 # So if we're here, it's probably because we made a hardlink below.
2633 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002634 if not quiet:
2635 _warn("%s: Not replacing locally modified %s hook",
2636 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002637 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002638 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002639 platform_utils.symlink(
2640 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002641 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002642 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002643 try:
2644 os.link(stock_hook, dst)
2645 except OSError:
2646 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002647 else:
2648 raise
2649
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002650 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002651 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002652 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002653 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002654 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002655 remote.review = self.remote.review
2656 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002657
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002658 if self.worktree:
2659 remote.ResetFetch(mirror=False)
2660 else:
2661 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002662 remote.Save()
2663
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002664 def _InitMRef(self):
2665 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002666 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002667 # Set up the m/ space to point to the worktree-specific ref space.
2668 # We'll update the worktree-specific ref space on each checkout.
2669 ref = R_M + self.manifest.branch
2670 if not self.bare_ref.symref(ref):
2671 self.bare_git.symbolic_ref(
2672 '-m', 'redirecting to worktree scope',
2673 ref, R_WORKTREE_M + self.manifest.branch)
2674
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002675 # We can't update this ref with git worktrees until it exists.
2676 # We'll wait until the initial checkout to set it.
2677 if not os.path.exists(self.worktree):
2678 return
2679
2680 base = R_WORKTREE_M
2681 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002682
2683 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002684 else:
2685 base = R_M
2686 active_git = self.bare_git
2687
2688 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002689
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002690 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002691 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002692
Remy Böhmer1469c282020-12-15 18:49:02 +01002693 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002694 cur = self.bare_ref.symref(ref)
2695
2696 if self.revisionId:
2697 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2698 msg = 'manifest set to %s' % self.revisionId
2699 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002700 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002701 else:
2702 remote = self.GetRemote(self.remote.name)
2703 dst = remote.ToLocal(self.revisionExpr)
2704 if cur != dst:
2705 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002706 if detach:
2707 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2708 else:
2709 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002710
Mike Frysingerc72bd842021-11-14 03:58:00 -05002711 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002712 # Git worktrees don't use symlinks to share at all.
2713 if self.use_git_worktrees:
2714 return
2715
Mike Frysingerd33dce02021-12-20 18:16:33 -05002716 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002717 # Try to self-heal a bit in simple cases.
2718 dst_path = os.path.join(destdir, name)
2719 src_path = os.path.join(srcdir, name)
2720
Mike Frysingered4f2112020-02-11 23:06:29 -05002721 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002722 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002723 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002724 # Fail if the links are pointing to the wrong place
2725 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002726 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002727 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002728 'work tree. If you\'re comfortable with the '
2729 'possibility of losing the work tree\'s git metadata,'
2730 ' use `repo sync --force-sync {0}` to '
2731 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002732
Mike Frysingerc72bd842021-11-14 03:58:00 -05002733 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002734 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2735
2736 Args:
2737 gitdir: The bare git repository. Must already be initialized.
2738 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002739 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2740 This saves you the effort of initializing |dotgit| yourself.
2741 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002742 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002743 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002744
2745 to_copy = []
2746 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002747 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002748
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002749 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002750 for name in set(to_copy).union(to_symlink):
2751 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002752 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002753 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002754
Kevin Degi384b3c52014-10-16 16:02:58 -06002755 if os.path.lexists(dst):
2756 continue
David James8d201162013-10-11 17:03:19 -07002757
2758 # If the source dir doesn't exist, create an empty dir.
2759 if name in symlink_dirs and not os.path.lexists(src):
2760 os.makedirs(src)
2761
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002762 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002763 platform_utils.symlink(
2764 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002765 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002766 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002767 shutil.copytree(src, dst)
2768 elif os.path.isfile(src):
2769 shutil.copy(src, dst)
2770
David James8d201162013-10-11 17:03:19 -07002771 except OSError as e:
2772 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002773 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002774 else:
2775 raise
2776
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002777 def _InitGitWorktree(self):
2778 """Init the project using git worktrees."""
2779 self.bare_git.worktree('prune')
2780 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2781 self.worktree, self.GetRevisionId())
2782
2783 # Rewrite the internal state files to use relative paths between the
2784 # checkouts & worktrees.
2785 dotgit = os.path.join(self.worktree, '.git')
2786 with open(dotgit, 'r') as fp:
2787 # Figure out the checkout->worktree path.
2788 setting = fp.read()
2789 assert setting.startswith('gitdir:')
2790 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002791 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2792 # of file permissions. Delete it and recreate it from scratch to avoid.
2793 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002794 # Use relative path from checkout->worktree & maintain Unix line endings
2795 # on all OS's to match git behavior.
2796 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002797 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2798 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002799 # Use relative path from worktree->checkout & maintain Unix line endings
2800 # on all OS's to match git behavior.
2801 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002802 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2803
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002804 self._InitMRef()
2805
Martin Kellye4e94d22017-03-21 16:05:12 -07002806 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002807 """Setup the worktree .git path.
2808
2809 This is the user-visible path like src/foo/.git/.
2810
2811 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2812 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2813
2814 Older checkouts had .git/ directories. If we see that, migrate it.
2815
2816 This also handles changes in the manifest. Maybe this project was backed
2817 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2818 the path we point to under .repo/projects/ to match.
2819 """
2820 dotgit = os.path.join(self.worktree, '.git')
2821
2822 # If using an old layout style (a directory), migrate it.
2823 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2824 self._MigrateOldWorkTreeGitDir(dotgit)
2825
2826 init_dotgit = not os.path.exists(dotgit)
2827 if self.use_git_worktrees:
2828 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002829 self._InitGitWorktree()
2830 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002831 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002832 if not init_dotgit:
2833 # See if the project has changed.
2834 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2835 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002836
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002837 if init_dotgit or not os.path.exists(dotgit):
2838 os.makedirs(self.worktree, exist_ok=True)
2839 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002840
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002841 if init_dotgit:
2842 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002843
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002844 # Finish checking out the worktree.
2845 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2846 if GitCommand(self, cmd).Wait() != 0:
2847 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002848
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002849 if submodules:
2850 self._SyncSubmodules(quiet=True)
2851 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002852
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002853 @classmethod
2854 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2855 """Migrate the old worktree .git/ dir style to a symlink.
2856
2857 This logic specifically only uses state from |dotgit| to figure out where to
2858 move content and not |self|. This way if the backing project also changed
2859 places, we only do the .git/ dir to .git symlink migration here. The path
2860 updates will happen independently.
2861 """
2862 # Figure out where in .repo/projects/ it's pointing to.
2863 if not os.path.islink(os.path.join(dotgit, 'refs')):
2864 raise GitError(f'{dotgit}: unsupported checkout state')
2865 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2866
2867 # Remove known symlink paths that exist in .repo/projects/.
2868 KNOWN_LINKS = {
2869 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2870 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2871 }
2872 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2873 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002874 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2875 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002876 }
2877
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002878 # First see if we'd succeed before starting the migration.
2879 unknown_paths = []
2880 for name in platform_utils.listdir(dotgit):
2881 # Ignore all temporary/backup names. These are common with vim & emacs.
2882 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2883 continue
2884
2885 dotgit_path = os.path.join(dotgit, name)
2886 if name in KNOWN_LINKS:
2887 if not platform_utils.islink(dotgit_path):
2888 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2889 else:
2890 gitdir_path = os.path.join(gitdir, name)
2891 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2892 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2893 if unknown_paths:
2894 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2895
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002896 # Now walk the paths and sync the .git/ to .repo/projects/.
2897 for name in platform_utils.listdir(dotgit):
2898 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002899
2900 # Ignore all temporary/backup names. These are common with vim & emacs.
2901 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2902 platform_utils.remove(dotgit_path)
2903 elif name in KNOWN_LINKS:
2904 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002905 else:
2906 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002907 platform_utils.remove(gitdir_path, missing_ok=True)
2908 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002909
2910 # Now that the dir should be empty, clear it out, and symlink it over.
2911 platform_utils.rmdir(dotgit)
2912 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002913
Renaud Paquay788e9622017-01-27 11:41:12 -08002914 def _get_symlink_error_message(self):
2915 if platform_utils.isWindows():
2916 return ('Unable to create symbolic link. Please re-run the command as '
2917 'Administrator, or see '
2918 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2919 'for other options.')
2920 return 'filesystem must support symlinks'
2921
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002922 def _revlist(self, *args, **kw):
2923 a = []
2924 a.extend(args)
2925 a.append('--')
2926 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002927
2928 @property
2929 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002930 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002931
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002932 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002933 """Get logs between two revisions of this project."""
2934 comp = '..'
2935 if rev1:
2936 revs = [rev1]
2937 if rev2:
2938 revs.extend([comp, rev2])
2939 cmd = ['log', ''.join(revs)]
2940 out = DiffColoring(self.config)
2941 if out.is_on and color:
2942 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002943 if pretty_format is not None:
2944 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002945 if oneline:
2946 cmd.append('--oneline')
2947
2948 try:
2949 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2950 if log.Wait() == 0:
2951 return log.stdout
2952 except GitError:
2953 # worktree may not exist if groups changed for example. In that case,
2954 # try in gitdir instead.
2955 if not os.path.exists(self.worktree):
2956 return self.bare_git.log(*cmd[1:])
2957 else:
2958 raise
2959 return None
2960
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002961 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2962 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002963 """Get the list of logs from this revision to given revisionId"""
2964 logs = {}
2965 selfId = self.GetRevisionId(self._allrefs)
2966 toId = toProject.GetRevisionId(toProject._allrefs)
2967
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002968 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2969 pretty_format=pretty_format)
2970 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2971 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002972 return logs
2973
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002974 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002975
David James8d201162013-10-11 17:03:19 -07002976 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002977 self._project = project
2978 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002979 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002980
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002981 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2982 def __getstate__(self):
2983 return (self._project, self._bare, self._gitdir)
2984
2985 def __setstate__(self, state):
2986 self._project, self._bare, self._gitdir = state
2987
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002988 def LsOthers(self):
2989 p = GitCommand(self._project,
2990 ['ls-files',
2991 '-z',
2992 '--others',
2993 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002994 bare=False,
David James8d201162013-10-11 17:03:19 -07002995 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002996 capture_stdout=True,
2997 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002998 if p.Wait() == 0:
2999 out = p.stdout
3000 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003001 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003002 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003003 return []
3004
3005 def DiffZ(self, name, *args):
3006 cmd = [name]
3007 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003008 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003009 cmd.extend(args)
3010 p = GitCommand(self._project,
3011 cmd,
David James8d201162013-10-11 17:03:19 -07003012 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003013 bare=False,
3014 capture_stdout=True,
3015 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05003016 p.Wait()
3017 r = {}
3018 out = p.stdout
3019 if out:
3020 out = iter(out[:-1].split('\0'))
3021 while out:
3022 try:
3023 info = next(out)
3024 path = next(out)
3025 except StopIteration:
3026 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003027
Mike Frysinger84230002021-02-16 17:08:35 -05003028 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003029
Mike Frysinger84230002021-02-16 17:08:35 -05003030 def __init__(self, path, omode, nmode, oid, nid, state):
3031 self.path = path
3032 self.src_path = None
3033 self.old_mode = omode
3034 self.new_mode = nmode
3035 self.old_id = oid
3036 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003037
Mike Frysinger84230002021-02-16 17:08:35 -05003038 if len(state) == 1:
3039 self.status = state
3040 self.level = None
3041 else:
3042 self.status = state[:1]
3043 self.level = state[1:]
3044 while self.level.startswith('0'):
3045 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003046
Mike Frysinger84230002021-02-16 17:08:35 -05003047 info = info[1:].split(' ')
3048 info = _Info(path, *info)
3049 if info.status in ('R', 'C'):
3050 info.src_path = info.path
3051 info.path = next(out)
3052 r[info.path] = info
3053 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003054
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003055 def GetDotgitPath(self, subpath=None):
3056 """Return the full path to the .git dir.
3057
3058 As a convenience, append |subpath| if provided.
3059 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003060 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003061 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003062 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003063 dotgit = os.path.join(self._project.worktree, '.git')
3064 if os.path.isfile(dotgit):
3065 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3066 with open(dotgit) as fp:
3067 setting = fp.read()
3068 assert setting.startswith('gitdir:')
3069 gitdir = setting.split(':', 1)[1].strip()
3070 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3071
3072 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3073
3074 def GetHead(self):
3075 """Return the ref that HEAD points to."""
3076 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003077 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003078 with open(path) as fd:
3079 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003080 except IOError as e:
3081 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003082 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303083 line = line.decode()
3084 except AttributeError:
3085 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003086 if line.startswith('ref: '):
3087 return line[5:-1]
3088 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003089
3090 def SetHead(self, ref, message=None):
3091 cmdv = []
3092 if message is not None:
3093 cmdv.extend(['-m', message])
3094 cmdv.append(HEAD)
3095 cmdv.append(ref)
3096 self.symbolic_ref(*cmdv)
3097
3098 def DetachHead(self, new, message=None):
3099 cmdv = ['--no-deref']
3100 if message is not None:
3101 cmdv.extend(['-m', message])
3102 cmdv.append(HEAD)
3103 cmdv.append(new)
3104 self.update_ref(*cmdv)
3105
3106 def UpdateRef(self, name, new, old=None,
3107 message=None,
3108 detach=False):
3109 cmdv = []
3110 if message is not None:
3111 cmdv.extend(['-m', message])
3112 if detach:
3113 cmdv.append('--no-deref')
3114 cmdv.append(name)
3115 cmdv.append(new)
3116 if old is not None:
3117 cmdv.append(old)
3118 self.update_ref(*cmdv)
3119
3120 def DeleteRef(self, name, old=None):
3121 if not old:
3122 old = self.rev_parse(name)
3123 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003124 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003125
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003126 def rev_list(self, *args, **kw):
3127 if 'format' in kw:
3128 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3129 else:
3130 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003131 cmdv.extend(args)
3132 p = GitCommand(self._project,
3133 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003134 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003135 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003136 capture_stdout=True,
3137 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003138 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003139 raise GitError('%s rev-list %s: %s' %
3140 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003141 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003142
3143 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003144 """Allow arbitrary git commands using pythonic syntax.
3145
3146 This allows you to do things like:
3147 git_obj.rev_parse('HEAD')
3148
3149 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3150 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003151 Any other positional arguments will be passed to the git command, and the
3152 following keyword arguments are supported:
3153 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003154
3155 Args:
3156 name: The name of the git command to call. Any '_' characters will
3157 be replaced with '-'.
3158
3159 Returns:
3160 A callable object that will try to call git with the named command.
3161 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003162 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003163
Dave Borowitz091f8932012-10-23 17:01:04 -07003164 def runner(*args, **kwargs):
3165 cmdv = []
3166 config = kwargs.pop('config', None)
3167 for k in kwargs:
3168 raise TypeError('%s() got an unexpected keyword argument %r'
3169 % (name, k))
3170 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303171 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003172 cmdv.append('-c')
3173 cmdv.append('%s=%s' % (k, v))
3174 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003175 cmdv.extend(args)
3176 p = GitCommand(self._project,
3177 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003178 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003179 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003180 capture_stdout=True,
3181 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003182 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003183 raise GitError('%s %s: %s' %
3184 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003185 r = p.stdout
3186 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3187 return r[:-1]
3188 return r
3189 return runner
3190
3191
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003192class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003193
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003194 def __str__(self):
3195 return 'prior sync failed; rebase still in progress'
3196
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003197
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003198class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003199
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003200 def __str__(self):
3201 return 'contains uncommitted changes'
3202
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003203
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003204class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003205
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003206 def __init__(self, project, text):
3207 self.project = project
3208 self.text = text
3209
3210 def Print(self, syncbuf):
3211 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3212 syncbuf.out.nl()
3213
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003214
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003215class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003216
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003217 def __init__(self, project, why):
3218 self.project = project
3219 self.why = why
3220
3221 def Print(self, syncbuf):
3222 syncbuf.out.fail('error: %s/: %s',
3223 self.project.relpath,
3224 str(self.why))
3225 syncbuf.out.nl()
3226
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003227
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003228class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003229
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003230 def __init__(self, project, action):
3231 self.project = project
3232 self.action = action
3233
3234 def Run(self, syncbuf):
3235 out = syncbuf.out
3236 out.project('project %s/', self.project.relpath)
3237 out.nl()
3238 try:
3239 self.action()
3240 out.nl()
3241 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003242 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003243 out.nl()
3244 return False
3245
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003246
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003247class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003248
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003249 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003250 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003251 self.project = self.printer('header', attr='bold')
3252 self.info = self.printer('info')
3253 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003254
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003255
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003256class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003257
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003258 def __init__(self, config, detach_head=False):
3259 self._messages = []
3260 self._failures = []
3261 self._later_queue1 = []
3262 self._later_queue2 = []
3263
3264 self.out = _SyncColoring(config)
3265 self.out.redirect(sys.stderr)
3266
3267 self.detach_head = detach_head
3268 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003269 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003270
3271 def info(self, project, fmt, *args):
3272 self._messages.append(_InfoMessage(project, fmt % args))
3273
3274 def fail(self, project, err=None):
3275 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003276 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003277
3278 def later1(self, project, what):
3279 self._later_queue1.append(_Later(project, what))
3280
3281 def later2(self, project, what):
3282 self._later_queue2.append(_Later(project, what))
3283
3284 def Finish(self):
3285 self._PrintMessages()
3286 self._RunLater()
3287 self._PrintMessages()
3288 return self.clean
3289
David Rileye0684ad2017-04-05 00:02:59 -07003290 def Recently(self):
3291 recent_clean = self.recent_clean
3292 self.recent_clean = True
3293 return recent_clean
3294
3295 def _MarkUnclean(self):
3296 self.clean = False
3297 self.recent_clean = False
3298
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003299 def _RunLater(self):
3300 for q in ['_later_queue1', '_later_queue2']:
3301 if not self._RunQueue(q):
3302 return
3303
3304 def _RunQueue(self, queue):
3305 for m in getattr(self, queue):
3306 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003307 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003308 return False
3309 setattr(self, queue, [])
3310 return True
3311
3312 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003313 if self._messages or self._failures:
3314 if os.isatty(2):
3315 self.out.write(progress.CSI_ERASE_LINE)
3316 self.out.write('\r')
3317
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003318 for m in self._messages:
3319 m.Print(self)
3320 for m in self._failures:
3321 m.Print(self)
3322
3323 self._messages = []
3324 self._failures = []
3325
3326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003327class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003328 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003329
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003330 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003331 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003332 manifest=manifest,
3333 name=name,
3334 gitdir=gitdir,
3335 objdir=gitdir,
3336 worktree=worktree,
3337 remote=RemoteSpec('origin'),
3338 relpath='.repo/%s' % name,
3339 revisionExpr='refs/heads/master',
3340 revisionId=None,
3341 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003342
3343 def PreSync(self):
3344 if self.Exists:
3345 cb = self.CurrentBranch
3346 if cb:
3347 base = self.GetBranch(cb).merge
3348 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003349 self.revisionExpr = base
3350 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003351
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003352 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003353 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003354 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003355 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003356 return False
3357
David Pursehouse8a68ff92012-09-24 12:15:13 +09003358 all_refs = self.bare_ref.all
3359 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003360 head = self.work_git.GetHead()
3361 if head.startswith(R_HEADS):
3362 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003363 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003364 except KeyError:
3365 head = None
3366
3367 if revid == head:
3368 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003369 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003370 return True
3371 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003372
3373
3374class RepoProject(MetaProject):
3375 """The MetaProject for repo itself."""
3376
3377 @property
3378 def LastFetch(self):
3379 try:
3380 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3381 return os.path.getmtime(fh)
3382 except OSError:
3383 return 0
3384
3385class ManifestProject(MetaProject):
3386 """The MetaProject for manifests."""
3387
3388 def MetaBranchSwitch(self, submodules=False):
3389 """Prepare for manifest branch switch."""
3390
3391 # detach and delete manifest branch, allowing a new
3392 # branch to take over
3393 syncbuf = SyncBuffer(self.config, detach_head=True)
3394 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3395 syncbuf.Finish()
3396
3397 return GitCommand(self,
3398 ['update-ref', '-d', 'refs/heads/default'],
3399 capture_stdout=True,
3400 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003401
3402 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003403 def standalone_manifest_url(self):
3404 """The URL of the standalone manifest, or None."""
LaMont Jones55ee3042022-04-06 17:10:21 +00003405 return self.config.GetString('manifest.standalone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003406
3407 @property
3408 def manifest_groups(self):
3409 """The manifest groups string."""
3410 return self.config.GetString('manifest.groups')
3411
3412 @property
3413 def reference(self):
3414 """The --reference for this manifest."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003415 return self.config.GetString('repo.reference')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003416
3417 @property
3418 def dissociate(self):
3419 """Whether to dissociate."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003420 return self.config.GetBoolean('repo.dissociate')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003421
3422 @property
3423 def archive(self):
3424 """Whether we use archive."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003425 return self.config.GetBoolean('repo.archive')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003426
3427 @property
3428 def mirror(self):
3429 """Whether we use mirror."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003430 return self.config.GetBoolean('repo.mirror')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003431
3432 @property
3433 def use_worktree(self):
3434 """Whether we use worktree."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003435 return self.config.GetBoolean('repo.worktree')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003436
3437 @property
3438 def clone_bundle(self):
3439 """Whether we use clone_bundle."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003440 return self.config.GetBoolean('repo.clonebundle')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003441
3442 @property
3443 def submodules(self):
3444 """Whether we use submodules."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003445 return self.config.GetBoolean('repo.submodules')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003446
3447 @property
3448 def git_lfs(self):
3449 """Whether we use git_lfs."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003450 return self.config.GetBoolean('repo.git-lfs')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003451
3452 @property
3453 def use_superproject(self):
3454 """Whether we use superproject."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003455 return self.config.GetBoolean('repo.superproject')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003456
3457 @property
3458 def partial_clone(self):
3459 """Whether this is a partial clone."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003460 return self.config.GetBoolean('repo.partialclone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003461
3462 @property
3463 def depth(self):
3464 """Partial clone depth."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003465 return self.config.GetString('repo.depth')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003466
3467 @property
3468 def clone_filter(self):
3469 """The clone filter."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003470 return self.config.GetString('repo.clonefilter')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003471
3472 @property
3473 def partial_clone_exclude(self):
3474 """Partial clone exclude string"""
LaMont Jones4ada0432022-04-14 15:10:43 +00003475 return self.config.GetBoolean('repo.partialcloneexclude')
3476
3477 @property
3478 def manifest_platform(self):
3479 """The --platform argument from `repo init`."""
3480 return self.config.GetString('manifest.platform')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003481
3482 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003483 def _platform_name(self):
3484 """Return the name of the platform."""
3485 return platform.system().lower()
3486
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003487 def SyncWithPossibleInit(self, submanifest, verbose=False,
3488 current_branch_only=False, tags='', git_event_log=None):
3489 """Sync a manifestProject, possibly for the first time.
3490
3491 Call Sync() with arguments from the most recent `repo init`. If this is a
3492 new sub manifest, then inherit options from the parent's manifestProject.
3493
3494 This is used by subcmds.Sync() to do an initial download of new sub
3495 manifests.
3496
3497 Args:
3498 submanifest: an XmlSubmanifest, the submanifest to re-sync.
3499 verbose: a boolean, whether to show all output, rather than only errors.
3500 current_branch_only: a boolean, whether to only fetch the current manifest
3501 branch from the server.
3502 tags: a boolean, whether to fetch tags.
3503 git_event_log: an EventLog, for git tracing.
3504 """
3505 # TODO(lamontjones): when refactoring sync (and init?) consider how to
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003506 # better get the init options that we should use for new submanifests that
3507 # are added when syncing an existing workspace.
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003508 git_event_log = git_event_log or EventLog()
3509 spec = submanifest.ToSubmanifestSpec()
3510 # Use the init options from the existing manifestProject, or the parent if
3511 # it doesn't exist.
3512 #
3513 # Today, we only support changing manifest_groups on the sub-manifest, with
3514 # no supported-for-the-user way to change the other arguments from those
3515 # specified by the outermost manifest.
3516 #
3517 # TODO(lamontjones): determine which of these should come from the outermost
3518 # manifest and which should come from the parent manifest.
3519 mp = self if self.Exists else submanifest.parent.manifestProject
3520 return self.Sync(
3521 manifest_url=spec.manifestUrl,
3522 manifest_branch=spec.revision,
3523 standalone_manifest=mp.standalone_manifest_url,
3524 groups=mp.manifest_groups,
3525 platform=mp.manifest_platform,
3526 mirror=mp.mirror,
3527 dissociate=mp.dissociate,
3528 reference=mp.reference,
3529 worktree=mp.use_worktree,
3530 submodules=mp.submodules,
3531 archive=mp.archive,
3532 partial_clone=mp.partial_clone,
3533 clone_filter=mp.clone_filter,
3534 partial_clone_exclude=mp.partial_clone_exclude,
3535 clone_bundle=mp.clone_bundle,
3536 git_lfs=mp.git_lfs,
3537 use_superproject=mp.use_superproject,
3538 verbose=verbose,
3539 current_branch_only=current_branch_only,
3540 tags=tags,
3541 depth=mp.depth,
3542 git_event_log=git_event_log,
3543 manifest_name=spec.manifestName,
3544 this_manifest_only=True,
3545 outer_manifest=False,
3546 )
3547
LaMont Jones9b03f152022-03-29 23:01:18 +00003548 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003549 standalone_manifest=False, groups='', mirror=False, reference='',
3550 dissociate=False, worktree=False, submodules=False, archive=False,
3551 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003552 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3553 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jones55ee3042022-04-06 17:10:21 +00003554 git_event_log=None, platform='', manifest_name='default.xml',
3555 tags='', this_manifest_only=False, outer_manifest=True):
LaMont Jones9b03f152022-03-29 23:01:18 +00003556 """Sync the manifest and all submanifests.
3557
3558 Args:
3559 manifest_url: a string, the URL of the manifest project.
3560 manifest_branch: a string, the manifest branch to use.
3561 standalone_manifest: a boolean, whether to store the manifest as a static
3562 file.
3563 groups: a string, restricts the checkout to projects with the specified
3564 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003565 mirror: a boolean, whether to create a mirror of the remote repository.
3566 reference: a string, location of a repo instance to use as a reference.
3567 dissociate: a boolean, whether to dissociate from reference mirrors after
3568 clone.
3569 worktree: a boolean, whether to use git-worktree to manage projects.
3570 submodules: a boolean, whether sync submodules associated with the
3571 manifest project.
3572 archive: a boolean, whether to checkout each project as an archive. See
3573 git-archive.
3574 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003575 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003576 clone_filter: a string, filter to use with partial_clone.
3577 partial_clone_exclude : a string, comma-delimeted list of project namess
3578 to exclude from partial clone.
3579 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3580 git_lfs: a boolean, whether to enable git LFS support.
3581 use_superproject: a boolean, whether to use the manifest superproject to
3582 sync projects.
3583 verbose: a boolean, whether to show all output, rather than only errors.
3584 current_branch_only: a boolean, whether to only fetch the current manifest
3585 branch from the server.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003586 platform: a string, restrict the checkout to projects with the specified
3587 platform group.
LaMont Jones55ee3042022-04-06 17:10:21 +00003588 git_event_log: an EventLog, for git tracing.
LaMont Jones4ada0432022-04-14 15:10:43 +00003589 tags: a boolean, whether to fetch tags.
LaMont Jones409407a2022-04-05 21:21:56 +00003590 manifest_name: a string, the name of the manifest file to use.
3591 this_manifest_only: a boolean, whether to only operate on the current sub
3592 manifest.
3593 outer_manifest: a boolean, whether to start at the outermost manifest.
LaMont Jones9b03f152022-03-29 23:01:18 +00003594
3595 Returns:
3596 a boolean, whether the sync was successful.
3597 """
3598 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3599
LaMont Jones501733c2022-04-20 16:42:32 +00003600 groups = groups or self.manifest.GetDefaultGroupsStr(with_platform=False)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003601 platform = platform or 'auto'
LaMont Jones55ee3042022-04-06 17:10:21 +00003602 git_event_log = git_event_log or EventLog()
LaMont Jones409407a2022-04-05 21:21:56 +00003603 if outer_manifest and self.manifest.is_submanifest:
3604 # In a multi-manifest checkout, use the outer manifest unless we are told
3605 # not to.
3606 return self.client.outer_manifest.manifestProject.Sync(
3607 manifest_url=manifest_url,
3608 manifest_branch=manifest_branch,
3609 standalone_manifest=standalone_manifest,
3610 groups=groups,
3611 platform=platform,
3612 mirror=mirror,
3613 dissociate=dissociate,
3614 reference=reference,
3615 worktree=worktree,
3616 submodules=submodules,
3617 archive=archive,
3618 partial_clone=partial_clone,
3619 clone_filter=clone_filter,
3620 partial_clone_exclude=partial_clone_exclude,
3621 clone_bundle=clone_bundle,
3622 git_lfs=git_lfs,
3623 use_superproject=use_superproject,
3624 verbose=verbose,
3625 current_branch_only=current_branch_only,
3626 tags=tags,
3627 depth=depth,
LaMont Jones55ee3042022-04-06 17:10:21 +00003628 git_event_log=git_event_log,
LaMont Jones409407a2022-04-05 21:21:56 +00003629 manifest_name=manifest_name,
3630 this_manifest_only=this_manifest_only,
3631 outer_manifest=False)
3632
LaMont Jones9b03f152022-03-29 23:01:18 +00003633 # If repo has already been initialized, we take -u with the absence of
3634 # --standalone-manifest to mean "transition to a standard repo set up",
3635 # which necessitates starting fresh.
3636 # If --standalone-manifest is set, we always tear everything down and start
3637 # anew.
3638 if self.Exists:
3639 was_standalone_manifest = self.config.GetString('manifest.standalone')
3640 if was_standalone_manifest and not manifest_url:
3641 print('fatal: repo was initialized with a standlone manifest, '
3642 'cannot be re-initialized without --manifest-url/-u')
3643 return False
3644
3645 if standalone_manifest or (was_standalone_manifest and manifest_url):
3646 self.config.ClearCache()
3647 if self.gitdir and os.path.exists(self.gitdir):
3648 platform_utils.rmtree(self.gitdir)
3649 if self.worktree and os.path.exists(self.worktree):
3650 platform_utils.rmtree(self.worktree)
3651
3652 is_new = not self.Exists
3653 if is_new:
3654 if not manifest_url:
3655 print('fatal: manifest url is required.', file=sys.stderr)
3656 return False
3657
LaMont Jones409407a2022-04-05 21:21:56 +00003658 if verbose:
LaMont Jones9b03f152022-03-29 23:01:18 +00003659 print('Downloading manifest from %s' %
3660 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3661 file=sys.stderr)
3662
3663 # The manifest project object doesn't keep track of the path on the
3664 # server where this git is located, so let's save that here.
3665 mirrored_manifest_git = None
3666 if reference:
3667 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3668 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3669 if not mirrored_manifest_git.endswith(".git"):
3670 mirrored_manifest_git += ".git"
3671 if not os.path.exists(mirrored_manifest_git):
3672 mirrored_manifest_git = os.path.join(reference,
3673 '.repo/manifests.git')
3674
3675 self._InitGitDir(mirror_git=mirrored_manifest_git)
3676
3677 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3678 # still do much of the manifests.git set up, but will avoid actual syncs to
3679 # a remote.
3680 if standalone_manifest:
3681 self.config.SetString('manifest.standalone', manifest_url)
3682 elif not manifest_url and not manifest_branch:
3683 # If -u is set and --standalone-manifest is not, then we're not in
3684 # standalone mode. Otherwise, use config to infer what we were in the last
3685 # init.
3686 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3687 if not standalone_manifest:
3688 self.config.SetString('manifest.standalone', None)
3689
3690 self._ConfigureDepth(depth)
3691
3692 # Set the remote URL before the remote branch as we might need it below.
3693 if manifest_url:
3694 r = self.GetRemote(self.remote.name)
3695 r.url = manifest_url
3696 r.ResetFetch()
3697 r.Save()
3698
3699 if not standalone_manifest:
3700 if manifest_branch:
3701 if manifest_branch == 'HEAD':
3702 manifest_branch = self.ResolveRemoteHead()
3703 if manifest_branch is None:
3704 print('fatal: unable to resolve HEAD', file=sys.stderr)
3705 return False
3706 self.revisionExpr = manifest_branch
3707 else:
3708 if is_new:
3709 default_branch = self.ResolveRemoteHead()
3710 if default_branch is None:
3711 # If the remote doesn't have HEAD configured, default to master.
3712 default_branch = 'refs/heads/master'
3713 self.revisionExpr = default_branch
3714 else:
3715 self.PreSync()
3716
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003717 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003718 all_platforms = ['linux', 'darwin', 'windows']
3719 platformize = lambda x: 'platform-' + x
3720 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003721 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003722 groups.append(platformize(self._platform_name))
3723 elif platform == 'all':
3724 groups.extend(map(platformize, all_platforms))
3725 elif platform in all_platforms:
3726 groups.append(platformize(platform))
3727 elif platform != 'none':
3728 print('fatal: invalid platform flag', file=sys.stderr)
3729 return False
LaMont Jones4ada0432022-04-14 15:10:43 +00003730 self.config.SetString('manifest.platform', platform)
LaMont Jones9b03f152022-03-29 23:01:18 +00003731
3732 groups = [x for x in groups if x]
3733 groupstr = ','.join(groups)
3734 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3735 groupstr = None
3736 self.config.SetString('manifest.groups', groupstr)
3737
3738 if reference:
3739 self.config.SetString('repo.reference', reference)
3740
3741 if dissociate:
3742 self.config.SetBoolean('repo.dissociate', dissociate)
3743
3744 if worktree:
3745 if mirror:
3746 print('fatal: --mirror and --worktree are incompatible',
3747 file=sys.stderr)
3748 return False
3749 if submodules:
3750 print('fatal: --submodules and --worktree are incompatible',
3751 file=sys.stderr)
3752 return False
3753 self.config.SetBoolean('repo.worktree', worktree)
3754 if is_new:
3755 self.use_git_worktrees = True
3756 print('warning: --worktree is experimental!', file=sys.stderr)
3757
3758 if archive:
3759 if is_new:
3760 self.config.SetBoolean('repo.archive', archive)
3761 else:
3762 print('fatal: --archive is only supported when initializing a new '
3763 'workspace.', file=sys.stderr)
3764 print('Either delete the .repo folder in this workspace, or initialize '
3765 'in another location.', file=sys.stderr)
3766 return False
3767
3768 if mirror:
3769 if is_new:
3770 self.config.SetBoolean('repo.mirror', mirror)
3771 else:
3772 print('fatal: --mirror is only supported when initializing a new '
3773 'workspace.', file=sys.stderr)
3774 print('Either delete the .repo folder in this workspace, or initialize '
3775 'in another location.', file=sys.stderr)
3776 return False
3777
3778 if partial_clone is not None:
3779 if mirror:
3780 print('fatal: --mirror and --partial-clone are mutually exclusive',
3781 file=sys.stderr)
3782 return False
3783 self.config.SetBoolean('repo.partialclone', partial_clone)
3784 if clone_filter:
3785 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jones55ee3042022-04-06 17:10:21 +00003786 elif self.partial_clone:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003787 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003788 else:
3789 clone_filter = None
3790
3791 if partial_clone_exclude is not None:
3792 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3793
3794 if clone_bundle is None:
3795 clone_bundle = False if partial_clone else True
3796 else:
3797 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3798
3799 if submodules:
3800 self.config.SetBoolean('repo.submodules', submodules)
3801
3802 if git_lfs is not None:
3803 if git_lfs:
3804 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3805
3806 self.config.SetBoolean('repo.git-lfs', git_lfs)
3807 if not is_new:
3808 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3809 ' Existing projects will require manual updates.\n', file=sys.stderr)
3810
3811 if use_superproject is not None:
3812 self.config.SetBoolean('repo.superproject', use_superproject)
3813
LaMont Jones0165e202022-04-27 17:34:42 +00003814 if not standalone_manifest:
3815 if not self.Sync_NetworkHalf(
3816 is_new=is_new, quiet=not verbose, verbose=verbose,
3817 clone_bundle=clone_bundle, current_branch_only=current_branch_only,
3818 tags=tags, submodules=submodules, clone_filter=clone_filter,
3819 partial_clone_exclude=self.manifest.PartialCloneExclude):
3820 r = self.GetRemote(self.remote.name)
3821 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
LaMont Jones9b03f152022-03-29 23:01:18 +00003822
LaMont Jones0165e202022-04-27 17:34:42 +00003823 # Better delete the manifest git dir if we created it; otherwise next
3824 # time (when user fixes problems) we won't go through the "is_new" logic.
3825 if is_new:
3826 platform_utils.rmtree(self.gitdir)
LaMont Jones9b03f152022-03-29 23:01:18 +00003827 return False
3828
LaMont Jones0165e202022-04-27 17:34:42 +00003829 if manifest_branch:
3830 self.MetaBranchSwitch(submodules=submodules)
3831
3832 syncbuf = SyncBuffer(self.config)
3833 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3834 syncbuf.Finish()
3835
3836 if is_new or self.CurrentBranch is None:
3837 if not self.StartBranch('default'):
3838 print('fatal: cannot create default in manifest', file=sys.stderr)
3839 return False
3840
3841 if not manifest_name:
3842 print('fatal: manifest name (-m) is required.', file=sys.stderr)
3843 return False
3844
3845 elif is_new:
3846 # This is a new standalone manifest.
3847 manifest_name = 'default.xml'
3848 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3849 dest = os.path.join(self.worktree, manifest_name)
3850 os.makedirs(os.path.dirname(dest), exist_ok=True)
3851 with open(dest, 'wb') as f:
3852 f.write(manifest_data)
LaMont Jones409407a2022-04-05 21:21:56 +00003853
3854 try:
3855 self.manifest.Link(manifest_name)
3856 except ManifestParseError as e:
3857 print("fatal: manifest '%s' not available" % manifest_name,
3858 file=sys.stderr)
3859 print('fatal: %s' % str(e), file=sys.stderr)
3860 return False
3861
LaMont Jones55ee3042022-04-06 17:10:21 +00003862 if not this_manifest_only:
3863 for submanifest in self.manifest.submanifests.values():
LaMont Jonesb90a4222022-04-14 15:00:09 +00003864 spec = submanifest.ToSubmanifestSpec()
LaMont Jones55ee3042022-04-06 17:10:21 +00003865 submanifest.repo_client.manifestProject.Sync(
3866 manifest_url=spec.manifestUrl,
3867 manifest_branch=spec.revision,
3868 standalone_manifest=standalone_manifest,
3869 groups=self.manifest_groups,
3870 platform=platform,
3871 mirror=mirror,
3872 dissociate=dissociate,
3873 reference=reference,
3874 worktree=worktree,
3875 submodules=submodules,
3876 archive=archive,
3877 partial_clone=partial_clone,
3878 clone_filter=clone_filter,
3879 partial_clone_exclude=partial_clone_exclude,
3880 clone_bundle=clone_bundle,
3881 git_lfs=git_lfs,
3882 use_superproject=use_superproject,
3883 verbose=verbose,
3884 current_branch_only=current_branch_only,
3885 tags=tags,
3886 depth=depth,
3887 git_event_log=git_event_log,
3888 manifest_name=spec.manifestName,
3889 this_manifest_only=False,
3890 outer_manifest=False,
3891 )
LaMont Jones409407a2022-04-05 21:21:56 +00003892
LaMont Jones0ddb6772022-05-20 09:11:54 +00003893 # Lastly, if the manifest has a <superproject> then have the superproject
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003894 # sync it (if it will be used).
3895 if git_superproject.UseSuperproject(use_superproject, self.manifest):
LaMont Jones0ddb6772022-05-20 09:11:54 +00003896 sync_result = self.manifest.superproject.Sync(git_event_log)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003897 if not sync_result.success:
3898 print('warning: git update of superproject for '
3899 f'{self.manifest.path_prefix} failed, repo sync will not use '
3900 'superproject to fetch source; while this error is not fatal, '
3901 'and you can continue to run repo sync, please run repo init '
3902 'with the --no-use-superproject option to stop seeing this '
3903 'warning', file=sys.stderr)
3904 if sync_result.fatal and use_superproject is not None:
3905 return False
LaMont Jones409407a2022-04-05 21:21:56 +00003906
LaMont Jones9b03f152022-03-29 23:01:18 +00003907 return True
3908
3909 def _ConfigureDepth(self, depth):
3910 """Configure the depth we'll sync down.
3911
3912 Args:
3913 depth: an int, how deep of a partial clone to create.
3914 """
3915 # Opt.depth will be non-None if user actually passed --depth to repo init.
3916 if depth is not None:
3917 if depth > 0:
3918 # Positive values will set the depth.
3919 depth = str(depth)
3920 else:
3921 # Negative numbers will clear the depth; passing None to SetString
3922 # will do that.
3923 depth = None
3924
3925 # We store the depth in the main manifest project.
3926 self.config.SetString('repo.depth', depth)