blob: 07102d42f62bf258f99f63f987c6638acc97cc05 [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,
William Escandeac76fd32022-08-02 16:05:37 -0700208 ready=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200209 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800210 validate_certs=True,
211 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500212 self.project.UploadForReview(branch=self.name,
213 people=people,
214 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700215 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500216 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500217 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200218 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700219 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200220 wip=wip,
William Escandeac76fd32022-08-02 16:05:37 -0700221 ready=ready,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200222 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800223 validate_certs=validate_certs,
224 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700226 def GetPublishedRefs(self):
227 refs = {}
228 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700229 self.branch.remote.SshReviewUrl(self.project.UserEmail),
230 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700231 for line in output.split('\n'):
232 try:
233 (sha, ref) = line.split()
234 refs[sha] = ref
235 except ValueError:
236 pass
237
238 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700242
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500244 super().__init__(config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100245 self.project = self.printer('header', attr='bold')
246 self.branch = self.printer('header', attr='bold')
247 self.nobranch = self.printer('nobranch', fg='red')
248 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700249
Anthony King7bdac712014-07-16 12:56:40 +0100250 self.added = self.printer('added', fg='green')
251 self.changed = self.printer('changed', fg='red')
252 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253
254
255class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700256
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700257 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500258 super().__init__(config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100259 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400260 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700261
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
Jack Neus6ea0cae2021-07-20 20:52:33 +0000263class Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700264
James W. Mills24c13082012-04-12 15:04:13 -0500265 def __init__(self, name, value, keep):
266 self.name = name
267 self.value = value
268 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700269
Jack Neus6ea0cae2021-07-20 20:52:33 +0000270 def __eq__(self, other):
271 if not isinstance(other, Annotation):
272 return False
273 return self.__dict__ == other.__dict__
274
275 def __lt__(self, other):
276 # This exists just so that lists of Annotation objects can be sorted, for
277 # use in comparisons.
278 if not isinstance(other, Annotation):
279 raise ValueError('comparison is not between two Annotation objects')
280 if self.name == other.name:
281 if self.value == other.value:
282 return self.keep < other.keep
283 return self.value < other.value
284 return self.name < other.name
285
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700286
Mike Frysingere6a202f2019-08-02 15:57:57 -0400287def _SafeExpandPath(base, subpath, skipfinal=False):
288 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700289
Mike Frysingere6a202f2019-08-02 15:57:57 -0400290 We make sure no intermediate symlinks are traversed, and that the final path
291 is not a special file (e.g. not a socket or fifo).
292
293 NB: We rely on a number of paths already being filtered out while parsing the
294 manifest. See the validation logic in manifest_xml.py for more details.
295 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500296 # Split up the path by its components. We can't use os.path.sep exclusively
297 # as some platforms (like Windows) will convert / to \ and that bypasses all
298 # our constructed logic here. Especially since manifest authors only use
299 # / in their paths.
300 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
301 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400302 if skipfinal:
303 # Whether the caller handles the final component itself.
304 finalpart = components.pop()
305
306 path = base
307 for part in components:
308 if part in {'.', '..'}:
309 raise ManifestInvalidPathError(
310 '%s: "%s" not allowed in paths' % (subpath, part))
311
312 path = os.path.join(path, part)
313 if platform_utils.islink(path):
314 raise ManifestInvalidPathError(
315 '%s: traversing symlinks not allow' % (path,))
316
317 if os.path.exists(path):
318 if not os.path.isfile(path) and not platform_utils.isdir(path):
319 raise ManifestInvalidPathError(
320 '%s: only regular files & directories allowed' % (path,))
321
322 if skipfinal:
323 path = os.path.join(path, finalpart)
324
325 return path
326
327
328class _CopyFile(object):
329 """Container for <copyfile> manifest element."""
330
331 def __init__(self, git_worktree, src, topdir, dest):
332 """Register a <copyfile> request.
333
334 Args:
335 git_worktree: Absolute path to the git project checkout.
336 src: Relative path under |git_worktree| of file to read.
337 topdir: Absolute path to the top of the repo client checkout.
338 dest: Relative path under |topdir| of file to write.
339 """
340 self.git_worktree = git_worktree
341 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700342 self.src = src
343 self.dest = dest
344
345 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400346 src = _SafeExpandPath(self.git_worktree, self.src)
347 dest = _SafeExpandPath(self.topdir, self.dest)
348
349 if platform_utils.isdir(src):
350 raise ManifestInvalidPathError(
351 '%s: copying from directory not supported' % (self.src,))
352 if platform_utils.isdir(dest):
353 raise ManifestInvalidPathError(
354 '%s: copying to directory not allowed' % (self.dest,))
355
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700356 # copy file if it does not exist or is out of date
357 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
358 try:
359 # remove existing file first, since it might be read-only
360 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800361 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400362 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200363 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700364 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200365 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700366 shutil.copy(src, dest)
367 # make the file read-only
368 mode = os.stat(dest)[stat.ST_MODE]
369 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
370 os.chmod(dest, mode)
371 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700372 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700373
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700374
Anthony King7bdac712014-07-16 12:56:40 +0100375class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400376 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700377
Mike Frysingere6a202f2019-08-02 15:57:57 -0400378 def __init__(self, git_worktree, src, topdir, dest):
379 """Register a <linkfile> request.
380
381 Args:
382 git_worktree: Absolute path to the git project checkout.
383 src: Target of symlink relative to path under |git_worktree|.
384 topdir: Absolute path to the top of the repo client checkout.
385 dest: Relative path under |topdir| of symlink to create.
386 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700387 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400388 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500389 self.src = src
390 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500391
Wink Saville4c426ef2015-06-03 08:05:17 -0700392 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500393 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700394 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500395 try:
396 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800397 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800398 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500399 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700400 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700401 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500402 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700403 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500404 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700405 _error('Cannot link file %s to %s', relSrc, absDest)
406
407 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400408 """Link the self.src & self.dest paths.
409
410 Handles wild cards on the src linking all of the files in the source in to
411 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700412 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500413 # Some people use src="." to create stable links to projects. Lets allow
414 # that but reject all other uses of "." to keep things simple.
415 if self.src == '.':
416 src = self.git_worktree
417 else:
418 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400419
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300420 if not glob.has_magic(src):
421 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400422 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
423 # dest & src are absolute paths at this point. Make sure the target of
424 # the symlink is relative in the context of the repo client checkout.
425 relpath = os.path.relpath(src, os.path.dirname(dest))
426 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700427 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400428 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300429 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400430 if os.path.exists(dest) and not platform_utils.isdir(dest):
431 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700432 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400433 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700434 # Create a releative path from source dir to destination dir
435 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400436 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700437
438 # Get the source file name
439 srcFile = os.path.basename(absSrcFile)
440
441 # Now form the final full paths to srcFile. They will be
442 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400443 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700444 relSrc = os.path.join(relSrcDir, srcFile)
445 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500446
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700447
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700448class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700449
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700450 def __init__(self,
451 name,
Anthony King7bdac712014-07-16 12:56:40 +0100452 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700453 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100454 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700455 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700456 orig_name=None,
457 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700458 self.name = name
459 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700460 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700461 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100462 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700463 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700464 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700465
Ian Kasprzak0286e312021-02-05 10:06:18 -0800466
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700467class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600468 # These objects can be shared between several working trees.
LaMont Jones68d69632022-06-07 18:24:20 +0000469 @property
470 def shareable_dirs(self):
471 """Return the shareable directories"""
472 if self.UseAlternates:
473 return ['hooks', 'rr-cache']
474 else:
475 return ['hooks', 'objects', 'rr-cache']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700476
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700477 def __init__(self,
478 manifest,
479 name,
480 remote,
481 gitdir,
David James8d201162013-10-11 17:03:19 -0700482 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700483 worktree,
484 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700485 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800486 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100487 rebase=True,
488 groups=None,
489 sync_c=False,
490 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900491 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100492 clone_depth=None,
493 upstream=None,
494 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500495 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100496 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900497 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700498 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600499 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700500 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800501 """Init a Project object.
502
503 Args:
504 manifest: The XmlManifest object.
505 name: The `name` attribute of manifest.xml's project element.
506 remote: RemoteSpec object specifying its remote's properties.
507 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700508 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800509 worktree: Absolute path of git working tree.
510 relpath: Relative path of git working tree to repo's top directory.
511 revisionExpr: The `revision` attribute of manifest.xml's project element.
512 revisionId: git commit id for checking out.
513 rebase: The `rebase` attribute of manifest.xml's project element.
514 groups: The `groups` attribute of manifest.xml's project element.
515 sync_c: The `sync-c` attribute of manifest.xml's project element.
516 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900517 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800518 upstream: The `upstream` attribute of manifest.xml's project element.
519 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500520 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800521 is_derived: False if the project was explicitly defined in the manifest;
522 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400523 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900524 optimized_fetch: If True, when a project is set to a sha1 revision, only
525 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600526 retry_fetches: Retry remote fetches n times upon receiving transient error
527 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700528 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800529 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400530 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700531 self.name = name
532 self.remote = remote
Michael Kelly37c21c22020-06-13 02:10:40 -0700533 self.UpdatePaths(relpath, worktree, gitdir, objdir)
Michael Kelly2f3c3312020-07-21 19:40:38 -0700534 self.SetRevision(revisionExpr, revisionId=revisionId)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700535
Mike Pontillod3153822012-02-28 11:53:24 -0800536 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700537 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700538 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800539 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900540 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900541 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700542 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800543 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500544 # NB: Do not use this setting in __init__ to change behavior so that the
545 # manifest.git checkout can inspect & change it after instantiating. See
546 # the XmlManifest init code for more info.
547 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800548 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900549 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600550 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800551 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800552
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700553 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700554 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500555 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500556 self.annotations = []
Bryan Jacobsf609f912013-05-06 13:36:24 -0400557 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700558 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700559
Doug Anderson37282b42011-03-04 11:54:18 -0800560 # This will be filled in if a project is later identified to be the
561 # project containing repo hooks.
562 self.enabled_repo_hooks = []
563
LaMont Jonescc879a92021-11-18 22:40:18 +0000564 def RelPath(self, local=True):
565 """Return the path for the project relative to a manifest.
566
567 Args:
568 local: a boolean, if True, the path is relative to the local
569 (sub)manifest. If false, the path is relative to the
570 outermost manifest.
571 """
572 if local:
573 return self.relpath
574 return os.path.join(self.manifest.path_prefix, self.relpath)
575
Michael Kelly2f3c3312020-07-21 19:40:38 -0700576 def SetRevision(self, revisionExpr, revisionId=None):
577 """Set revisionId based on revision expression and id"""
578 self.revisionExpr = revisionExpr
579 if revisionId is None and revisionExpr and IsId(revisionExpr):
580 self.revisionId = self.revisionExpr
581 else:
582 self.revisionId = revisionId
583
Michael Kelly37c21c22020-06-13 02:10:40 -0700584 def UpdatePaths(self, relpath, worktree, gitdir, objdir):
585 """Update paths used by this project"""
586 self.gitdir = gitdir.replace('\\', '/')
587 self.objdir = objdir.replace('\\', '/')
588 if worktree:
589 self.worktree = os.path.normpath(worktree).replace('\\', '/')
590 else:
591 self.worktree = None
592 self.relpath = relpath
593
594 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
595 defaults=self.manifest.globalConfig)
596
597 if self.worktree:
598 self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
599 else:
600 self.work_git = None
601 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
602 self.bare_ref = GitRefs(self.gitdir)
603 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
604
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700605 @property
LaMont Jones68d69632022-06-07 18:24:20 +0000606 def UseAlternates(self):
607 """Whether git alternates are in use.
608
609 This will be removed once migration to alternates is complete.
610 """
611 return _ALTERNATES or self.manifest.is_multimanifest
612
613 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800614 def Derived(self):
615 return self.is_derived
616
617 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700618 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700619 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700620
621 @property
622 def CurrentBranch(self):
623 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400624
625 The branch name omits the 'refs/heads/' prefix.
626 None is returned if the project is on a detached HEAD, or if the work_git is
627 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700628 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400629 try:
630 b = self.work_git.GetHead()
631 except NoManifestException:
632 # If the local checkout is in a bad state, don't barf. Let the callers
633 # process this like the head is unreadable.
634 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635 if b.startswith(R_HEADS):
636 return b[len(R_HEADS):]
637 return None
638
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700639 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500640 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
641 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
642 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200643
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700644 def IsDirty(self, consider_untracked=True):
645 """Is the working directory modified in some way?
646 """
647 self.work_git.update_index('-q',
648 '--unmerged',
649 '--ignore-missing',
650 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900651 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700652 return True
653 if self.work_git.DiffZ('diff-files'):
654 return True
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200655 if consider_untracked and self.UntrackedFiles():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700656 return True
657 return False
658
659 _userident_name = None
660 _userident_email = None
661
662 @property
663 def UserName(self):
664 """Obtain the user's personal name.
665 """
666 if self._userident_name is None:
667 self._LoadUserIdentity()
668 return self._userident_name
669
670 @property
671 def UserEmail(self):
672 """Obtain the user's email address. This is very likely
673 to be their Gerrit login.
674 """
675 if self._userident_email is None:
676 self._LoadUserIdentity()
677 return self._userident_email
678
679 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900680 u = self.bare_git.var('GIT_COMMITTER_IDENT')
681 m = re.compile("^(.*) <([^>]*)> ").match(u)
682 if m:
683 self._userident_name = m.group(1)
684 self._userident_email = m.group(2)
685 else:
686 self._userident_name = ''
687 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700688
689 def GetRemote(self, name):
690 """Get the configuration for a single remote.
691 """
692 return self.config.GetRemote(name)
693
694 def GetBranch(self, name):
695 """Get the configuration for a single branch.
696 """
697 return self.config.GetBranch(name)
698
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700699 def GetBranches(self):
700 """Get all existing local branches.
701 """
702 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900703 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700704 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700705
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530706 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700707 if name.startswith(R_HEADS):
708 name = name[len(R_HEADS):]
709 b = self.GetBranch(name)
710 b.current = name == current
711 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900712 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700713 heads[name] = b
714
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530715 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700716 if name.startswith(R_PUB):
717 name = name[len(R_PUB):]
718 b = heads.get(name)
719 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900720 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700721
722 return heads
723
Colin Cross5acde752012-03-28 20:15:45 -0700724 def MatchesGroups(self, manifest_groups):
725 """Returns true if the manifest groups specified at init should cause
726 this project to be synced.
727 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700728 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700729
730 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700731 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700732 manifest_groups: "-group1,group2"
733 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500734
735 The special manifest group "default" will match any project that
736 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700737 """
LaMont Jones501733c2022-04-20 16:42:32 +0000738 default_groups = self.manifest.default_groups or ['default']
739 expanded_manifest_groups = manifest_groups or default_groups
Conley Owensbb1b5f52012-08-13 13:11:18 -0700740 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700741 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500742 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700743
Conley Owens971de8e2012-04-16 10:36:08 -0700744 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700745 for group in expanded_manifest_groups:
746 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700747 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700748 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700749 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700750
Conley Owens971de8e2012-04-16 10:36:08 -0700751 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700752
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700753# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700754 def UncommitedFiles(self, get_all=True):
755 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700756
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700757 Args:
758 get_all: a boolean, if True - get information about all different
759 uncommitted files. If False - return as soon as any kind of
760 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500761 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700762 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500763 self.work_git.update_index('-q',
764 '--unmerged',
765 '--ignore-missing',
766 '--refresh')
767 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700768 details.append("rebase in progress")
769 if not get_all:
770 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500771
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700772 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
773 if changes:
774 details.extend(changes)
775 if not get_all:
776 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500777
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700778 changes = self.work_git.DiffZ('diff-files').keys()
779 if changes:
780 details.extend(changes)
781 if not get_all:
782 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500783
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200784 changes = self.UntrackedFiles()
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700785 if changes:
786 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500787
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700788 return details
789
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200790 def UntrackedFiles(self):
791 """Returns a list of strings, untracked files in the git tree."""
792 return self.work_git.LsOthers()
793
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700794 def HasChanges(self):
795 """Returns true if there are uncommitted changes.
796 """
Martin Geisler8db78c72022-07-08 11:05:24 +0200797 return bool(self.UncommitedFiles(get_all=False))
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500798
LaMont Jones8501d462022-06-22 19:21:15 +0000799 def PrintWorkTreeStatus(self, output_redir=None, quiet=False, local=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700800 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200801
802 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200803 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600804 quiet: If True then only print the project name. Do not print
805 the modified files, branch name, etc.
LaMont Jones8501d462022-06-22 19:21:15 +0000806 local: a boolean, if True, the path is relative to the local
807 (sub)manifest. If false, the path is relative to the
808 outermost manifest.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700810 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700811 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200812 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700813 print(file=output_redir)
LaMont Jones8501d462022-06-22 19:21:15 +0000814 print('project %s/' % self.RelPath(local), file=output_redir)
Sarah Owenscecd1d82012-11-01 22:59:27 -0700815 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816 return
817
818 self.work_git.update_index('-q',
819 '--unmerged',
820 '--ignore-missing',
821 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700822 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700823 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
824 df = self.work_git.DiffZ('diff-files')
825 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100826 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700827 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828
829 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700830 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200831 out.redirect(output_redir)
LaMont Jones8501d462022-06-22 19:21:15 +0000832 out.project('project %-40s', self.RelPath(local) + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700833
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600834 if quiet:
835 out.nl()
836 return 'DIRTY'
837
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 branch = self.CurrentBranch
839 if branch is None:
840 out.nobranch('(*** NO BRANCH ***)')
841 else:
842 out.branch('branch %s', branch)
843 out.nl()
844
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700845 if rb:
846 out.important('prior sync failed; rebase still in progress')
847 out.nl()
848
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849 paths = list()
850 paths.extend(di.keys())
851 paths.extend(df.keys())
852 paths.extend(do)
853
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530854 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900855 try:
856 i = di[p]
857 except KeyError:
858 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700859
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900860 try:
861 f = df[p]
862 except KeyError:
863 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200864
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900865 if i:
866 i_status = i.status.upper()
867 else:
868 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900870 if f:
871 f_status = f.status.lower()
872 else:
873 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874
875 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800876 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700877 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878 else:
879 line = ' %s%s\t%s' % (i_status, f_status, p)
880
881 if i and not f:
882 out.added('%s', line)
883 elif (i and f) or (not i and f):
884 out.changed('%s', line)
885 elif not i and not f:
886 out.untracked('%s', line)
887 else:
888 out.write('%s', line)
889 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200890
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700891 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892
LaMont Jones8501d462022-06-22 19:21:15 +0000893 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None,
894 local=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700895 """Prints the status of the repository to stdout.
896 """
897 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500898 if output_redir:
899 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900 cmd = ['diff']
901 if out.is_on:
902 cmd.append('--color')
903 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300904 if absolute_paths:
LaMont Jones8501d462022-06-22 19:21:15 +0000905 cmd.append('--src-prefix=a/%s/' % self.RelPath(local))
906 cmd.append('--dst-prefix=b/%s/' % self.RelPath(local))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700907 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400908 try:
909 p = GitCommand(self,
910 cmd,
911 capture_stdout=True,
912 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500913 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400914 except GitError as e:
915 out.nl()
LaMont Jones8501d462022-06-22 19:21:15 +0000916 out.project('project %s/' % self.RelPath(local))
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400917 out.nl()
918 out.fail('%s', str(e))
919 out.nl()
920 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500921 if p.stdout:
922 out.nl()
LaMont Jones8501d462022-06-22 19:21:15 +0000923 out.project('project %s/' % self.RelPath(local))
Mike Frysinger84230002021-02-16 17:08:35 -0500924 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500925 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400926 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700928# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900929 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 """Was the branch published (uploaded) for code review?
931 If so, returns the SHA-1 hash of the last published
932 state for the branch.
933 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700934 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900935 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700936 try:
937 return self.bare_git.rev_parse(key)
938 except GitError:
939 return None
940 else:
941 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900942 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700943 except KeyError:
944 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945
David Pursehouse8a68ff92012-09-24 12:15:13 +0900946 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947 """Prunes any stale published refs.
948 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900949 if all_refs is None:
950 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951 heads = set()
952 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530953 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954 if name.startswith(R_HEADS):
955 heads.add(name)
956 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900957 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530959 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700960 n = name[len(R_PUB):]
961 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900962 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700964 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700965 """List any branches which can be uploaded for review.
966 """
967 heads = {}
968 pubed = {}
969
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530970 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700971 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900972 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900974 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700975
976 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530977 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900978 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700980 if selected_branch and branch != selected_branch:
981 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800983 rb = self.GetUploadableBranch(branch)
984 if rb:
985 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 return ready
987
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800988 def GetUploadableBranch(self, branch_name):
989 """Get a single uploadable branch, or None.
990 """
991 branch = self.GetBranch(branch_name)
992 base = branch.LocalMerge
993 if branch.LocalMerge:
994 rb = ReviewableBranch(self, branch, base)
995 if rb.commits:
996 return rb
997 return None
998
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700999 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001000 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -05001001 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001002 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001003 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001004 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +02001005 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001006 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001007 wip=False,
William Escandeac76fd32022-08-02 16:05:37 -07001008 ready=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001009 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001010 validate_certs=True,
1011 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001012 """Uploads the named branch for code review.
1013 """
1014 if branch is None:
1015 branch = self.CurrentBranch
1016 if branch is None:
1017 raise GitError('not currently on a branch')
1018
1019 branch = self.GetBranch(branch)
1020 if not branch.LocalMerge:
1021 raise GitError('branch %s does not track a remote' % branch.name)
1022 if not branch.remote.review:
1023 raise GitError('remote %s has no review url' % branch.remote.name)
1024
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001025 # Basic validity check on label syntax.
1026 for label in labels:
1027 if not re.match(r'^.+[+-][0-9]+$', label):
1028 raise UploadError(
1029 f'invalid label syntax "{label}": labels use forms like '
1030 'CodeReview+1 or Verified-1')
1031
Bryan Jacobsf609f912013-05-06 13:36:24 -04001032 if dest_branch is None:
1033 dest_branch = self.dest_branch
1034 if dest_branch is None:
1035 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036 if not dest_branch.startswith(R_HEADS):
1037 dest_branch = R_HEADS + dest_branch
1038
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001039 if not branch.remote.projectname:
1040 branch.remote.projectname = self.name
1041 branch.remote.Save()
1042
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001043 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001044 if url is None:
1045 raise UploadError('review not configured')
1046 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001047 if dryrun:
1048 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001049
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001050 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001051 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001052
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001053 for push_option in (push_options or []):
1054 cmd.append('-o')
1055 cmd.append(push_option)
1056
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001057 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001058
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001059 if dest_branch.startswith(R_HEADS):
1060 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001061
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001062 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001063 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001064 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001065 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001066 opts += ['t=%s' % p for p in hashtags]
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001067 # NB: No need to encode labels as they've been validated above.
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001068 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001069
David Pursehousef25a3702018-11-14 19:01:22 -08001070 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001071 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001072 if notify:
1073 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001074 if private:
1075 opts += ['private']
1076 if wip:
1077 opts += ['wip']
William Escandeac76fd32022-08-02 16:05:37 -07001078 if ready:
1079 opts += ['ready']
Jonathan Nieder713c5872018-11-05 13:21:52 -08001080 if opts:
1081 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001082 cmd.append(ref_spec)
1083
Anthony King7bdac712014-07-16 12:56:40 +01001084 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001085 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086
Mike Frysingerd7f86832020-11-19 19:18:46 -05001087 if not dryrun:
1088 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1089 self.bare_git.UpdateRef(R_PUB + branch.name,
1090 R_HEADS + branch.name,
1091 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001092
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001093# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001094 def _ExtractArchive(self, tarpath, path=None):
1095 """Extract the given tar on its current location
1096
1097 Args:
1098 - tarpath: The path to the actual tar file
1099
1100 """
1101 try:
1102 with tarfile.open(tarpath, 'r') as tar:
1103 tar.extractall(path=path)
1104 return True
1105 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001106 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001107 return False
1108
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001109 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001110 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001111 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001112 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001113 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001114 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001115 force_sync=False,
1116 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001117 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001118 archive=False,
1119 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001120 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001121 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001122 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001123 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001124 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001125 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001126 """Perform only the network IO portion of the sync process.
1127 Local working directory/branch state is not affected.
1128 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001129 if archive and not isinstance(self, MetaProject):
1130 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001131 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001132 return False
1133
1134 name = self.relpath.replace('\\', '/')
1135 name = name.replace('/', '_')
1136 tarpath = '%s.tar' % name
1137 topdir = self.manifest.topdir
1138
1139 try:
1140 self._FetchArchive(tarpath, cwd=topdir)
1141 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001142 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001143 return False
1144
1145 # From now on, we only need absolute tarpath
1146 tarpath = os.path.join(topdir, tarpath)
1147
1148 if not self._ExtractArchive(tarpath, path=topdir):
1149 return False
1150 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001151 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001152 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001153 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001154 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001155 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001156
1157 # If the shared object dir already exists, don't try to rebootstrap with a
1158 # clone bundle download. We should have the majority of objects already.
1159 if clone_bundle and os.path.exists(self.objdir):
1160 clone_bundle = False
1161
Raman Tennetif32f2432021-04-12 20:57:25 -07001162 if self.name in partial_clone_exclude:
1163 clone_bundle = True
1164 clone_filter = None
1165
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001166 if is_new is None:
1167 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001168 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001169 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001170 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001171 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001173
LaMont Jones68d69632022-06-07 18:24:20 +00001174 if self.UseAlternates:
Mike Frysinger1d00a7e2021-12-21 00:40:31 -05001175 # If gitdir/objects is a symlink, migrate it from the old layout.
1176 gitdir_objects = os.path.join(self.gitdir, 'objects')
1177 if platform_utils.islink(gitdir_objects):
1178 platform_utils.remove(gitdir_objects, missing_ok=True)
1179 gitdir_alt = os.path.join(self.gitdir, 'objects/info/alternates')
1180 if not os.path.exists(gitdir_alt):
1181 os.makedirs(os.path.dirname(gitdir_alt), exist_ok=True)
1182 _lwrite(gitdir_alt, os.path.join(
1183 os.path.relpath(self.objdir, gitdir_objects), 'objects') + '\n')
1184
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001185 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001186 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001187 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001188 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001189 # This works for both absolute and relative alternate directories.
1190 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001191 except IOError:
1192 alt_dir = None
1193 else:
1194 alt_dir = None
1195
Mike Frysingere50b6a72020-02-19 01:45:48 -05001196 if (clone_bundle
1197 and alt_dir is None
1198 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001199 is_new = False
1200
Mike Frysinger73561142021-05-03 01:10:09 -04001201 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001202 if self.sync_c:
1203 current_branch_only = True
1204 elif not self.manifest._loaded:
1205 # Manifest cannot check defaults until it syncs.
1206 current_branch_only = False
1207 elif self.manifest.default.sync_c:
1208 current_branch_only = True
1209
Mike Frysingerd68ed632021-05-03 01:21:35 -04001210 if tags is None:
1211 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001212
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001213 if self.clone_depth:
1214 depth = self.clone_depth
1215 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001216 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001217
Mike Frysinger521d01b2020-02-17 01:51:49 -05001218 # See if we can skip the network fetch entirely.
1219 if not (optimized_fetch and
1220 (ID_RE.match(self.revisionExpr) and
1221 self._CheckForImmutableRevision())):
1222 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001223 initial=is_new,
1224 quiet=quiet, verbose=verbose, output_redir=output_redir,
1225 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001226 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001227 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001228 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001229 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001230 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001231
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001232 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001233 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001234 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001235 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001236 if os.path.exists(alternates_file):
1237 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001238 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1239 merge_output=bool(output_redir))
1240 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001241 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001242 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001243 return False
1244 platform_utils.remove(alternates_file)
1245
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001246 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001247 self._InitMRef()
1248 else:
1249 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001250 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1251 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001252 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001253
1254 def PostRepoUpgrade(self):
1255 self._InitHooks()
1256
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001257 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001258 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001259 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001260 for copyfile in self.copyfiles:
1261 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001262 for linkfile in self.linkfiles:
1263 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001264
Julien Camperguedd654222014-01-09 16:21:37 +01001265 def GetCommitRevisionId(self):
1266 """Get revisionId of a commit.
1267
1268 Use this method instead of GetRevisionId to get the id of the commit rather
1269 than the id of the current git object (for example, a tag)
1270
1271 """
1272 if not self.revisionExpr.startswith(R_TAGS):
1273 return self.GetRevisionId(self._allrefs)
1274
1275 try:
1276 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1277 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001278 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1279 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001280
David Pursehouse8a68ff92012-09-24 12:15:13 +09001281 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001282 if self.revisionId:
1283 return self.revisionId
1284
1285 rem = self.GetRemote(self.remote.name)
1286 rev = rem.ToLocal(self.revisionExpr)
1287
David Pursehouse8a68ff92012-09-24 12:15:13 +09001288 if all_refs is not None and rev in all_refs:
1289 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001290
1291 try:
1292 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1293 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001294 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1295 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001296
Raman Tenneti6a872c92021-01-14 19:17:50 -08001297 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001298 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001299 self.upstream = self.revisionExpr
1300
Raman Tenneti6a872c92021-01-14 19:17:50 -08001301 self.revisionId = revisionId
1302
Martin Kellye4e94d22017-03-21 16:05:12 -07001303 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001304 """Perform only the local IO portion of the sync process.
1305 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001307 if not os.path.exists(self.gitdir):
1308 syncbuf.fail(self,
1309 'Cannot checkout %s due to missing network sync; Run '
1310 '`repo sync -n %s` first.' %
1311 (self.name, self.name))
1312 return
1313
Martin Kellye4e94d22017-03-21 16:05:12 -07001314 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001315 all_refs = self.bare_ref.all
1316 self.CleanPublishedCache(all_refs)
1317 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001318
Mike Frysinger0458faa2021-03-10 23:35:44 -05001319 # Special case the root of the repo client checkout. Make sure it doesn't
1320 # contain files being checked out to dirs we don't allow.
1321 if self.relpath == '.':
1322 PROTECTED_PATHS = {'.repo'}
1323 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1324 bad_paths = paths & PROTECTED_PATHS
1325 if bad_paths:
1326 syncbuf.fail(self,
1327 'Refusing to checkout project that writes to protected '
1328 'paths: %s' % (', '.join(bad_paths),))
1329 return
1330
David Pursehouse1d947b32012-10-25 12:23:11 +09001331 def _doff():
1332 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001333 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001334
Martin Kellye4e94d22017-03-21 16:05:12 -07001335 def _dosubmodules():
1336 self._SyncSubmodules(quiet=True)
1337
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001338 head = self.work_git.GetHead()
1339 if head.startswith(R_HEADS):
1340 branch = head[len(R_HEADS):]
1341 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001342 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001343 except KeyError:
1344 head = None
1345 else:
1346 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001347
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001348 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001349 # Currently on a detached HEAD. The user is assumed to
1350 # not have any local modifications worth worrying about.
1351 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001352 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001353 syncbuf.fail(self, _PriorSyncFailedError())
1354 return
1355
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001356 if head == revid:
1357 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001358 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001359 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001360 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001361 # The copy/linkfile config may have changed.
1362 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001363 return
1364 else:
1365 lost = self._revlist(not_rev(revid), HEAD)
1366 if lost:
1367 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001368
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001369 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001370 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001371 if submodules:
1372 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001373 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001374 syncbuf.fail(self, e)
1375 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001376 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001377 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001378
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001379 if head == revid:
1380 # No changes; don't do anything further.
1381 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001382 # The copy/linkfile config may have changed.
1383 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001384 return
1385
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001387
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001388 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001390 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001392 syncbuf.info(self,
1393 "leaving %s; does not track upstream",
1394 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001395 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001396 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001397 if submodules:
1398 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001399 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001400 syncbuf.fail(self, e)
1401 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001402 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001403 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001404
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001405 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001406
1407 # See if we can perform a fast forward merge. This can happen if our
1408 # branch isn't in the exact same state as we last published.
1409 try:
1410 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1411 # Skip the published logic.
1412 pub = False
1413 except GitError:
1414 pub = self.WasPublished(branch.name, all_refs)
1415
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001416 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001417 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001418 if not_merged:
1419 if upstream_gain:
1420 # The user has published this branch and some of those
1421 # commits are not yet merged upstream. We do not want
1422 # to rewrite the published commits so we punt.
1423 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001424 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001425 "branch %s is published (but not merged) and is now "
1426 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001427 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001428 elif pub == head:
1429 # All published commits are merged, and thus we are a
1430 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001431 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001432 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001433 if submodules:
1434 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001435 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001436
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001437 # Examine the local commits not in the remote. Find the
1438 # last one attributed to this user, if any.
1439 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001440 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001441 last_mine = None
1442 cnt_mine = 0
1443 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001444 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001445 if committer_email == self.UserEmail:
1446 last_mine = commit_id
1447 cnt_mine += 1
1448
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001449 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001450 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001451
1452 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001453 syncbuf.fail(self, _DirtyError())
1454 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001455
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001456 # If the upstream switched on us, warn the user.
1457 #
1458 if branch.merge != self.revisionExpr:
1459 if branch.merge and self.revisionExpr:
1460 syncbuf.info(self,
1461 'manifest switched %s...%s',
1462 branch.merge,
1463 self.revisionExpr)
1464 elif branch.merge:
1465 syncbuf.info(self,
1466 'manifest no longer tracks %s',
1467 branch.merge)
1468
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001469 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001470 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001471 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001472 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001473 syncbuf.info(self,
1474 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001475 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001476
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001477 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001478 if not ID_RE.match(self.revisionExpr):
1479 # in case of manifest sync the revisionExpr might be a SHA1
1480 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001481 if not branch.merge.startswith('refs/'):
1482 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001483 branch.Save()
1484
Mike Pontillod3153822012-02-28 11:53:24 -08001485 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001486 def _docopyandlink():
1487 self._CopyAndLinkFiles()
1488
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001489 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001490 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001491 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001492 if submodules:
1493 syncbuf.later2(self, _dosubmodules)
1494 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001495 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001496 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001497 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001498 if submodules:
1499 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001500 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001501 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001502 syncbuf.fail(self, e)
1503 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001504 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001505 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001506 if submodules:
1507 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001508
Mike Frysingere6a202f2019-08-02 15:57:57 -04001509 def AddCopyFile(self, src, dest, topdir):
1510 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001511
Mike Frysingere6a202f2019-08-02 15:57:57 -04001512 No filesystem changes occur here. Actual copying 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 copy happens.
1516 """
1517 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1518
1519 def AddLinkFile(self, src, dest, topdir):
1520 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1521
1522 No filesystem changes occur here. Actual linking happens later on.
1523
1524 Paths should have basic validation run on them before being queued.
1525 Further checking will be handled when the actual link happens.
1526 """
1527 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001528
James W. Mills24c13082012-04-12 15:04:13 -05001529 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001530 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001531
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001532 def DownloadPatchSet(self, change_id, patch_id):
1533 """Download a single patch set of a single change to FETCH_HEAD.
1534 """
1535 remote = self.GetRemote(self.remote.name)
1536
1537 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001538 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001539 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001540 if GitCommand(self, cmd, bare=True).Wait() != 0:
1541 return None
1542 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001543 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001544 change_id,
1545 patch_id,
1546 self.bare_git.rev_parse('FETCH_HEAD'))
1547
Mike Frysingerc0d18662020-02-19 19:19:18 -05001548 def DeleteWorktree(self, quiet=False, force=False):
1549 """Delete the source checkout and any other housekeeping tasks.
1550
1551 This currently leaves behind the internal .repo/ cache state. This helps
1552 when switching branches or manifest changes get reverted as we don't have
1553 to redownload all the git objects. But we should do some GC at some point.
1554
1555 Args:
1556 quiet: Whether to hide normal messages.
1557 force: Always delete tree even if dirty.
1558
1559 Returns:
1560 True if the worktree was completely cleaned out.
1561 """
1562 if self.IsDirty():
1563 if force:
1564 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
LaMont Jones8501d462022-06-22 19:21:15 +00001565 (self.RelPath(local=False),), file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001566 else:
1567 print('error: %s: Cannot remove project: uncommitted changes are '
LaMont Jones8501d462022-06-22 19:21:15 +00001568 'present.\n' % (self.RelPath(local=False),), file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001569 return False
1570
1571 if not quiet:
LaMont Jones8501d462022-06-22 19:21:15 +00001572 print('%s: Deleting obsolete checkout.' % (self.RelPath(local=False),))
Mike Frysingerc0d18662020-02-19 19:19:18 -05001573
1574 # Unlock and delink from the main worktree. We don't use git's worktree
1575 # remove because it will recursively delete projects -- we handle that
1576 # ourselves below. https://crbug.com/git/48
1577 if self.use_git_worktrees:
1578 needle = platform_utils.realpath(self.gitdir)
1579 # Find the git worktree commondir under .repo/worktrees/.
1580 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1581 assert output.startswith('worktree '), output
1582 commondir = output[9:]
1583 # Walk each of the git worktrees to see where they point.
1584 configs = os.path.join(commondir, 'worktrees')
1585 for name in os.listdir(configs):
1586 gitdir = os.path.join(configs, name, 'gitdir')
1587 with open(gitdir) as fp:
1588 relpath = fp.read().strip()
1589 # Resolve the checkout path and see if it matches this project.
1590 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1591 if fullpath == needle:
1592 platform_utils.rmtree(os.path.join(configs, name))
1593
1594 # Delete the .git directory first, so we're less likely to have a partially
1595 # working git repository around. There shouldn't be any git projects here,
1596 # so rmtree works.
1597
1598 # Try to remove plain files first in case of git worktrees. If this fails
1599 # for any reason, we'll fall back to rmtree, and that'll display errors if
1600 # it can't remove things either.
1601 try:
1602 platform_utils.remove(self.gitdir)
1603 except OSError:
1604 pass
1605 try:
1606 platform_utils.rmtree(self.gitdir)
1607 except OSError as e:
1608 if e.errno != errno.ENOENT:
1609 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1610 print('error: %s: Failed to delete obsolete checkout; remove manually, '
LaMont Jones8501d462022-06-22 19:21:15 +00001611 'then run `repo sync -l`.' % (self.RelPath(local=False),),
1612 file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001613 return False
1614
1615 # Delete everything under the worktree, except for directories that contain
1616 # another git project.
1617 dirs_to_remove = []
1618 failed = False
1619 for root, dirs, files in platform_utils.walk(self.worktree):
1620 for f in files:
1621 path = os.path.join(root, f)
1622 try:
1623 platform_utils.remove(path)
1624 except OSError as e:
1625 if e.errno != errno.ENOENT:
1626 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1627 failed = True
1628 dirs[:] = [d for d in dirs
1629 if not os.path.lexists(os.path.join(root, d, '.git'))]
1630 dirs_to_remove += [os.path.join(root, d) for d in dirs
1631 if os.path.join(root, d) not in dirs_to_remove]
1632 for d in reversed(dirs_to_remove):
1633 if platform_utils.islink(d):
1634 try:
1635 platform_utils.remove(d)
1636 except OSError as e:
1637 if e.errno != errno.ENOENT:
1638 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1639 failed = True
1640 elif not platform_utils.listdir(d):
1641 try:
1642 platform_utils.rmdir(d)
1643 except OSError as e:
1644 if e.errno != errno.ENOENT:
1645 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1646 failed = True
1647 if failed:
LaMont Jones8501d462022-06-22 19:21:15 +00001648 print('error: %s: Failed to delete obsolete checkout.' % (self.RelPath(local=False),),
Mike Frysingerc0d18662020-02-19 19:19:18 -05001649 file=sys.stderr)
1650 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1651 return False
1652
1653 # Try deleting parent dirs if they are empty.
1654 path = self.worktree
1655 while path != self.manifest.topdir:
1656 try:
1657 platform_utils.rmdir(path)
1658 except OSError as e:
1659 if e.errno != errno.ENOENT:
1660 break
1661 path = os.path.dirname(path)
1662
1663 return True
1664
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001665# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001666 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001667 """Create a new branch off the manifest's revision.
1668 """
Simran Basib9a1b732015-08-20 12:19:28 -07001669 if not branch_merge:
1670 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001671 head = self.work_git.GetHead()
1672 if head == (R_HEADS + name):
1673 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001674
David Pursehouse8a68ff92012-09-24 12:15:13 +09001675 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001676 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001677 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001678 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001679 capture_stdout=True,
1680 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001681
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001682 branch = self.GetBranch(name)
1683 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001684 branch.merge = branch_merge
1685 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1686 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001687
1688 if revision is None:
1689 revid = self.GetRevisionId(all_refs)
1690 else:
1691 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001692
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001693 if head.startswith(R_HEADS):
1694 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001695 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001696 except KeyError:
1697 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001698 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001699 ref = R_HEADS + name
1700 self.work_git.update_ref(ref, revid)
1701 self.work_git.symbolic_ref(HEAD, ref)
1702 branch.Save()
1703 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001704
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001705 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001706 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001707 capture_stdout=True,
1708 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001709 branch.Save()
1710 return True
1711 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001712
Wink Saville02d79452009-04-10 13:01:24 -07001713 def CheckoutBranch(self, name):
1714 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001715
1716 Args:
1717 name: The name of the branch to checkout.
1718
1719 Returns:
1720 True if the checkout succeeded; False if it didn't; None if the branch
1721 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001722 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001723 rev = R_HEADS + name
1724 head = self.work_git.GetHead()
1725 if head == rev:
1726 # Already on the branch
1727 #
1728 return True
Wink Saville02d79452009-04-10 13:01:24 -07001729
David Pursehouse8a68ff92012-09-24 12:15:13 +09001730 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001731 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001732 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001733 except KeyError:
1734 # Branch does not exist in this project
1735 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001736 return None
Wink Saville02d79452009-04-10 13:01:24 -07001737
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001738 if head.startswith(R_HEADS):
1739 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001740 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001741 except KeyError:
1742 head = None
1743
1744 if head == revid:
1745 # Same revision; just update HEAD to point to the new
1746 # target branch, but otherwise take no other action.
1747 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001748 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1749 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001750 return True
1751
1752 return GitCommand(self,
1753 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001754 capture_stdout=True,
1755 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001756
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001757 def AbandonBranch(self, name):
1758 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001759
1760 Args:
1761 name: The name of the branch to abandon.
1762
1763 Returns:
1764 True if the abandon succeeded; False if it didn't; None if the branch
1765 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001766 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001767 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001768 all_refs = self.bare_ref.all
1769 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001770 # Doesn't exist
1771 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001772
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001773 head = self.work_git.GetHead()
1774 if head == rev:
1775 # We can't destroy the branch while we are sitting
1776 # on it. Switch to a detached HEAD.
1777 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001778 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001779
David Pursehouse8a68ff92012-09-24 12:15:13 +09001780 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001781 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001782 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001783 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001784 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001785
1786 return GitCommand(self,
1787 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001788 capture_stdout=True,
1789 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001790
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001791 def PruneHeads(self):
1792 """Prune any topic branches already merged into upstream.
1793 """
1794 cb = self.CurrentBranch
1795 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001796 left = self._allrefs
1797 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001798 if name.startswith(R_HEADS):
1799 name = name[len(R_HEADS):]
1800 if cb is None or name != cb:
1801 kill.append(name)
1802
Mike Frysingera3794e92021-03-11 23:24:01 -05001803 # Minor optimization: If there's nothing to prune, then don't try to read
1804 # any project state.
1805 if not kill and not cb:
1806 return []
1807
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001808 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001809 if cb is not None \
1810 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001811 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001812 self.work_git.DetachHead(HEAD)
1813 kill.append(cb)
1814
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001815 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001816 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001817
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001818 try:
1819 self.bare_git.DetachHead(rev)
1820
1821 b = ['branch', '-d']
1822 b.extend(kill)
1823 b = GitCommand(self, b, bare=True,
1824 capture_stdout=True,
1825 capture_stderr=True)
1826 b.Wait()
1827 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001828 if ID_RE.match(old):
1829 self.bare_git.DetachHead(old)
1830 else:
1831 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001832 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001833
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001834 for branch in kill:
1835 if (R_HEADS + branch) not in left:
1836 self.CleanPublishedCache()
1837 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001838
1839 if cb and cb not in kill:
1840 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001841 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001842
1843 kept = []
1844 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001845 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001846 branch = self.GetBranch(branch)
1847 base = branch.LocalMerge
1848 if not base:
1849 base = rev
1850 kept.append(ReviewableBranch(self, branch, base))
1851 return kept
1852
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001853# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001854 def GetRegisteredSubprojects(self):
1855 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001856
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001857 def rec(subprojects):
1858 if not subprojects:
1859 return
1860 result.extend(subprojects)
1861 for p in subprojects:
1862 rec(p.subprojects)
1863 rec(self.subprojects)
1864 return result
1865
1866 def _GetSubmodules(self):
1867 # Unfortunately we cannot call `git submodule status --recursive` here
1868 # because the working tree might not exist yet, and it cannot be used
1869 # without a working tree in its current implementation.
1870
1871 def get_submodules(gitdir, rev):
1872 # Parse .gitmodules for submodule sub_paths and sub_urls
1873 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1874 if not sub_paths:
1875 return []
1876 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1877 # revision of submodule repository
1878 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1879 submodules = []
1880 for sub_path, sub_url in zip(sub_paths, sub_urls):
1881 try:
1882 sub_rev = sub_revs[sub_path]
1883 except KeyError:
1884 # Ignore non-exist submodules
1885 continue
1886 submodules.append((sub_rev, sub_path, sub_url))
1887 return submodules
1888
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001889 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1890 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001891
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001892 def parse_gitmodules(gitdir, rev):
1893 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1894 try:
Anthony King7bdac712014-07-16 12:56:40 +01001895 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1896 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001897 except GitError:
1898 return [], []
1899 if p.Wait() != 0:
1900 return [], []
1901
1902 gitmodules_lines = []
1903 fd, temp_gitmodules_path = tempfile.mkstemp()
1904 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001905 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001906 os.close(fd)
1907 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001908 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1909 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001910 if p.Wait() != 0:
1911 return [], []
1912 gitmodules_lines = p.stdout.split('\n')
1913 except GitError:
1914 return [], []
1915 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001916 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001917
1918 names = set()
1919 paths = {}
1920 urls = {}
1921 for line in gitmodules_lines:
1922 if not line:
1923 continue
1924 m = re_path.match(line)
1925 if m:
1926 names.add(m.group(1))
1927 paths[m.group(1)] = m.group(2)
1928 continue
1929 m = re_url.match(line)
1930 if m:
1931 names.add(m.group(1))
1932 urls[m.group(1)] = m.group(2)
1933 continue
1934 names = sorted(names)
1935 return ([paths.get(name, '') for name in names],
1936 [urls.get(name, '') for name in names])
1937
1938 def git_ls_tree(gitdir, rev, paths):
1939 cmd = ['ls-tree', rev, '--']
1940 cmd.extend(paths)
1941 try:
Anthony King7bdac712014-07-16 12:56:40 +01001942 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1943 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001944 except GitError:
1945 return []
1946 if p.Wait() != 0:
1947 return []
1948 objects = {}
1949 for line in p.stdout.split('\n'):
1950 if not line.strip():
1951 continue
1952 object_rev, object_path = line.split()[2:4]
1953 objects[object_path] = object_rev
1954 return objects
1955
1956 try:
1957 rev = self.GetRevisionId()
1958 except GitError:
1959 return []
1960 return get_submodules(self.gitdir, rev)
1961
1962 def GetDerivedSubprojects(self):
1963 result = []
1964 if not self.Exists:
1965 # If git repo does not exist yet, querying its submodules will
1966 # mess up its states; so return here.
1967 return result
1968 for rev, path, url in self._GetSubmodules():
1969 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001970 relpath, worktree, gitdir, objdir = \
1971 self.manifest.GetSubprojectPaths(self, name, path)
1972 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001973 if project:
1974 result.extend(project.GetDerivedSubprojects())
1975 continue
David James8d201162013-10-11 17:03:19 -07001976
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001977 if url.startswith('..'):
1978 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001979 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001980 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001981 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001982 review=self.remote.review,
1983 revision=self.remote.revision)
1984 subproject = Project(manifest=self.manifest,
1985 name=name,
1986 remote=remote,
1987 gitdir=gitdir,
1988 objdir=objdir,
1989 worktree=worktree,
1990 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001991 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001992 revisionId=rev,
1993 rebase=self.rebase,
1994 groups=self.groups,
1995 sync_c=self.sync_c,
1996 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001997 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001998 parent=self,
1999 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002000 result.append(subproject)
2001 result.extend(subproject.GetDerivedSubprojects())
2002 return result
2003
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002004# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002005 def EnableRepositoryExtension(self, key, value='true', version=1):
2006 """Enable git repository extension |key| with |value|.
2007
2008 Args:
2009 key: The extension to enabled. Omit the "extensions." prefix.
2010 value: The value to use for the extension.
2011 version: The minimum git repository version needed.
2012 """
2013 # Make sure the git repo version is new enough already.
2014 found_version = self.config.GetInt('core.repositoryFormatVersion')
2015 if found_version is None:
2016 found_version = 0
2017 if found_version < version:
2018 self.config.SetString('core.repositoryFormatVersion', str(version))
2019
2020 # Enable the extension!
2021 self.config.SetString('extensions.%s' % (key,), value)
2022
Mike Frysinger50a81de2020-09-06 15:51:21 -04002023 def ResolveRemoteHead(self, name=None):
2024 """Find out what the default branch (HEAD) points to.
2025
2026 Normally this points to refs/heads/master, but projects are moving to main.
2027 Support whatever the server uses rather than hardcoding "master" ourselves.
2028 """
2029 if name is None:
2030 name = self.remote.name
2031
2032 # The output will look like (NB: tabs are separators):
2033 # ref: refs/heads/master HEAD
2034 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2035 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2036
2037 for line in output.splitlines():
2038 lhs, rhs = line.split('\t', 1)
2039 if rhs == 'HEAD' and lhs.startswith('ref:'):
2040 return lhs[4:].strip()
2041
2042 return None
2043
Zac Livingstone4332262017-06-16 08:56:09 -06002044 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002045 try:
2046 # if revision (sha or tag) is not present then following function
2047 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002048 self.bare_git.rev_list('-1', '--missing=allow-any',
2049 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00002050 if self.upstream:
2051 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
2052 self.bare_git.rev_list('-1', '--missing=allow-any',
2053 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002054 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002055 return True
2056 except GitError:
2057 # There is no such persistent revision. We have to fetch it.
2058 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002059
Julien Campergue335f5ef2013-10-16 11:02:35 +02002060 def _FetchArchive(self, tarpath, cwd=None):
2061 cmd = ['archive', '-v', '-o', tarpath]
2062 cmd.append('--remote=%s' % self.remote.url)
LaMont Jones8501d462022-06-22 19:21:15 +00002063 cmd.append('--prefix=%s/' % self.RelPath(local=False))
Julien Campergue335f5ef2013-10-16 11:02:35 +02002064 cmd.append(self.revisionExpr)
2065
2066 command = GitCommand(self, cmd, cwd=cwd,
2067 capture_stdout=True,
2068 capture_stderr=True)
2069
2070 if command.Wait() != 0:
2071 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2072
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002073 def _RemoteFetch(self, name=None,
2074 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002075 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002076 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002077 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002078 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002079 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002080 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002081 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002082 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002083 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002084 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002085 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002086 clone_filter=None,
2087 retry_fetches=2,
2088 retry_sleep_initial_sec=4.0,
2089 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002090 is_sha1 = False
2091 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002092 # The depth should not be used when fetching to a mirror because
2093 # it will result in a shallow repository that cannot be cloned or
2094 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002095 # The repo project should also never be synced with partial depth.
2096 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2097 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002098
Shawn Pearce69e04d82014-01-29 12:48:54 -08002099 if depth:
2100 current_branch_only = True
2101
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002102 if ID_RE.match(self.revisionExpr) is not None:
2103 is_sha1 = True
2104
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002105 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002106 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002107 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002108 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002109 elif self.upstream and self.upstream.startswith(R_TAGS):
2110 # This is a tag and its commit id should never change.
2111 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002112
2113 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002114 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002115 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002116 print('Skipped fetching project %s (already have persistent ref)'
2117 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002118 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002119 if is_sha1 and not depth:
2120 # When syncing a specific commit and --depth is not set:
2121 # * if upstream is explicitly specified and is not a sha1, fetch only
2122 # upstream as users expect only upstream to be fetch.
2123 # Note: The commit might not be in upstream in which case the sync
2124 # will fail.
2125 # * otherwise, fetch all branches to make sure we end up with the
2126 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002127 if self.upstream:
2128 current_branch_only = not ID_RE.match(self.upstream)
2129 else:
2130 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002131
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002132 if not name:
2133 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002134
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002135 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002136 if not remote.PreConnectFetch(ssh_proxy):
2137 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002138
Shawn O. Pearce88443382010-10-08 10:02:09 +02002139 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002140 if alt_dir and 'objects' == os.path.basename(alt_dir):
2141 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002142 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002143
David Pursehouse8a68ff92012-09-24 12:15:13 +09002144 all_refs = self.bare_ref.all
2145 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002146 tmp = set()
2147
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302148 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002149 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002150 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002151 all_refs[r] = ref_id
2152 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002153 continue
2154
David Pursehouse8a68ff92012-09-24 12:15:13 +09002155 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002156 continue
2157
David Pursehouse8a68ff92012-09-24 12:15:13 +09002158 r = 'refs/_alt/%s' % ref_id
2159 all_refs[r] = ref_id
2160 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002161 tmp.add(r)
2162
heping3d7bbc92017-04-12 19:51:47 +08002163 tmp_packed_lines = []
2164 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002165
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302166 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002167 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002168 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002169 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002170 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002171
heping3d7bbc92017-04-12 19:51:47 +08002172 tmp_packed = ''.join(tmp_packed_lines)
2173 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002174 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002175 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002176 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002177
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002178 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002179
Xin Li745be2e2019-06-03 11:24:30 -07002180 if clone_filter:
2181 git_require((2, 19, 0), fail=True, msg='partial clones')
2182 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002183 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002184
Conley Owensf97e8382015-01-21 11:12:46 -08002185 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002186 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002187 else:
2188 # If this repo has shallow objects, then we don't know which refs have
2189 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2190 # do this with projects that don't have shallow objects, since it is less
2191 # efficient.
2192 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2193 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002194
Mike Frysinger4847e052020-02-22 00:07:35 -05002195 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002196 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002197 if not quiet and sys.stdout.isatty():
2198 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002199 if not self.worktree:
2200 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002201 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002202
Mike Frysingere57f1142019-03-18 21:27:54 -04002203 if force_sync:
2204 cmd.append('--force')
2205
David Pursehouse74cfd272015-10-14 10:50:15 +09002206 if prune:
2207 cmd.append('--prune')
2208
LaMont Jonesff6b1da2022-06-01 21:03:34 +00002209 # Always pass something for --recurse-submodules, git with GIT_DIR behaves
2210 # incorrectly when not given `--recurse-submodules=no`. (b/218891912)
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002211 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002212
Kuang-che Wu6856f982019-11-25 12:37:55 +08002213 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002214 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002215 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002216 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002217 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002218 spec.append('tag')
2219 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002220
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302221 if self.manifest.IsMirror and not current_branch_only:
2222 branch = None
2223 else:
2224 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002225 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002226 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002227 # Shallow checkout of a specific commit, fetch from that commit and not
2228 # the heads only as the commit might be deeper in the history.
2229 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002230 if self.upstream:
2231 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002232 else:
2233 if is_sha1:
2234 branch = self.upstream
2235 if branch is not None and branch.strip():
2236 if not branch.startswith('refs/'):
2237 branch = R_HEADS + branch
2238 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2239
2240 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2241 # whole repo.
2242 if self.manifest.IsMirror and not spec:
2243 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2244
2245 # If using depth then we should not get all the tags since they may
2246 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002247 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002248 cmd.append('--no-tags')
2249 else:
2250 cmd.append('--tags')
2251 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2252
Conley Owens80b87fe2014-05-09 17:13:44 -07002253 cmd.extend(spec)
2254
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002255 # At least one retry minimum due to git remote prune.
2256 retry_fetches = max(retry_fetches, 2)
2257 retry_cur_sleep = retry_sleep_initial_sec
2258 ok = prune_tried = False
2259 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002260 gitcmd = GitCommand(
2261 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2262 ssh_proxy=ssh_proxy,
2263 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002264 if gitcmd.stdout and not quiet and output_redir:
2265 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002266 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002267 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002268 ok = True
2269 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002270
2271 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002272 elif (gitcmd.stdout and
2273 'error:' in gitcmd.stdout and
2274 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002275 # Fallthru to sleep+retry logic at the bottom.
2276 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002277
Mike Frysinger6823bc22021-04-15 02:06:28 -04002278 # Try to prune remote branches once in case there are conflicts.
2279 # For example, if the remote had refs/heads/upstream, but deleted that and
2280 # now has refs/heads/upstream/foo.
2281 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002282 'error:' in gitcmd.stdout and
2283 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002284 not prune_tried):
2285 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002286 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002287 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002288 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002289 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002290 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002291 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002292 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002293 continue
Brian Harring14a66742012-09-28 20:21:57 -07002294 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002295 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2296 # in sha1 mode, we just tried sync'ing from the upstream field; it
2297 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002298 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002299 elif ret < 0:
2300 # Git died with a signal, exit immediately
2301 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002302
2303 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002304 if not verbose and gitcmd.stdout:
2305 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002306 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002307 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2308 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002309 time.sleep(retry_cur_sleep)
2310 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2311 MAXIMUM_RETRY_SLEEP_SEC)
2312 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2313 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002314
2315 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002316 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002317 if old_packed != '':
2318 _lwrite(packed_refs, old_packed)
2319 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002320 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002321 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002322
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002323 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002324 # We just synced the upstream given branch; verify we
2325 # got what we wanted, else trigger a second run of all
2326 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002327 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002328 # Sync the current branch only with depth set to None.
2329 # We always pass depth=None down to avoid infinite recursion.
2330 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002331 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002332 current_branch_only=current_branch_only and depth,
2333 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002334 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002335
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002336 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002337
Mike Frysingere50b6a72020-02-19 01:45:48 -05002338 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002339 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002340 return False
2341
2342 remote = self.GetRemote(self.remote.name)
2343 bundle_url = remote.url + '/clone.bundle'
2344 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002345 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2346 'persistent-http',
2347 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002348 return False
2349
2350 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2351 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2352
2353 exist_dst = os.path.exists(bundle_dst)
2354 exist_tmp = os.path.exists(bundle_tmp)
2355
2356 if not initial and not exist_dst and not exist_tmp:
2357 return False
2358
2359 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002360 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2361 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002362 if not exist_dst:
2363 return False
2364
2365 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002366 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002367 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002368 if not quiet and sys.stdout.isatty():
2369 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002370 if not self.worktree:
2371 cmd.append('--update-head-ok')
2372 cmd.append(bundle_dst)
2373 for f in remote.fetch:
2374 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002375 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002376
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002377 ok = GitCommand(
2378 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002379 platform_utils.remove(bundle_dst, missing_ok=True)
2380 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002381 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002382
Mike Frysingere50b6a72020-02-19 01:45:48 -05002383 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002384 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002385
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002386 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002387 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002388 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002389 if os.path.exists(tmpPath):
2390 size = os.stat(tmpPath).st_size
2391 if size >= 1024:
2392 cmd += ['--continue-at', '%d' % (size,)]
2393 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002394 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002395 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002396 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002397 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002398 if proxy:
2399 cmd += ['--proxy', proxy]
2400 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2401 cmd += ['--proxy', os.environ['http_proxy']]
2402 if srcUrl.startswith('persistent-https'):
2403 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2404 elif srcUrl.startswith('persistent-http'):
2405 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002406 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002407
Dave Borowitz137d0132015-01-02 11:12:54 -08002408 if IsTrace():
2409 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002410 if verbose:
2411 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2412 stdout = None if verbose else subprocess.PIPE
2413 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002414 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002415 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002416 except OSError:
2417 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002418
Mike Frysingere50b6a72020-02-19 01:45:48 -05002419 (output, _) = proc.communicate()
2420 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002421
Dave Borowitz137d0132015-01-02 11:12:54 -08002422 if curlret == 22:
2423 # From curl man page:
2424 # 22: HTTP page not retrieved. The requested url was not found or
2425 # returned another error with the HTTP error code being 400 or above.
2426 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002427 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002428 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2429 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002430 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002431 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002432 elif curlret and not verbose and output:
2433 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002434
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002435 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002436 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002437 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002438 return True
2439 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002440 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002441 return False
2442 else:
2443 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002444
Kris Giesingc8d882a2014-12-23 13:02:32 -08002445 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002446 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002447 with open(path, 'rb') as f:
2448 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002449 return True
2450 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002451 if not quiet:
2452 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002453 return False
2454 except OSError:
2455 return False
2456
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002457 def _Checkout(self, rev, quiet=False):
2458 cmd = ['checkout']
2459 if quiet:
2460 cmd.append('-q')
2461 cmd.append(rev)
2462 cmd.append('--')
2463 if GitCommand(self, cmd).Wait() != 0:
2464 if self._allrefs:
2465 raise GitError('%s checkout %s ' % (self.name, rev))
2466
Mike Frysinger915fda12020-03-22 12:15:20 -04002467 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002468 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002469 if ffonly:
2470 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002471 if record_origin:
2472 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002473 cmd.append(rev)
2474 cmd.append('--')
2475 if GitCommand(self, cmd).Wait() != 0:
2476 if self._allrefs:
2477 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2478
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302479 def _LsRemote(self, refs):
2480 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302481 p = GitCommand(self, cmd, capture_stdout=True)
2482 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002483 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302484 return None
2485
Anthony King7bdac712014-07-16 12:56:40 +01002486 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002487 cmd = ['revert']
2488 cmd.append('--no-edit')
2489 cmd.append(rev)
2490 cmd.append('--')
2491 if GitCommand(self, cmd).Wait() != 0:
2492 if self._allrefs:
2493 raise GitError('%s revert %s ' % (self.name, rev))
2494
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002495 def _ResetHard(self, rev, quiet=True):
2496 cmd = ['reset', '--hard']
2497 if quiet:
2498 cmd.append('-q')
2499 cmd.append(rev)
2500 if GitCommand(self, cmd).Wait() != 0:
2501 raise GitError('%s reset --hard %s ' % (self.name, rev))
2502
Martin Kellye4e94d22017-03-21 16:05:12 -07002503 def _SyncSubmodules(self, quiet=True):
2504 cmd = ['submodule', 'update', '--init', '--recursive']
2505 if quiet:
2506 cmd.append('-q')
2507 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002508 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002509
Anthony King7bdac712014-07-16 12:56:40 +01002510 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002511 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002512 if onto is not None:
2513 cmd.extend(['--onto', onto])
2514 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002515 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002516 raise GitError('%s rebase %s ' % (self.name, upstream))
2517
Pierre Tardy3d125942012-05-04 12:18:12 +02002518 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002519 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002520 if ffonly:
2521 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002522 if GitCommand(self, cmd).Wait() != 0:
2523 raise GitError('%s merge %s ' % (self.name, head))
2524
David Pursehousee8ace262020-02-13 12:41:15 +09002525 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002526 init_git_dir = not os.path.exists(self.gitdir)
2527 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002528 try:
2529 # Initialize the bare repository, which contains all of the objects.
2530 if init_obj_dir:
2531 os.makedirs(self.objdir)
2532 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002533
Mike Frysinger333c0a42021-11-15 12:39:00 -05002534 self._UpdateHooks(quiet=quiet)
2535
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002536 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002537 # Enable per-worktree config file support if possible. This is more a
2538 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002539 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002540 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002541
Kevin Degib1a07b82015-07-27 13:33:43 -06002542 # If we have a separate directory to hold refs, initialize it as well.
2543 if self.objdir != self.gitdir:
2544 if init_git_dir:
2545 os.makedirs(self.gitdir)
2546
2547 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002548 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002549 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002550 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002551 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002552 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002553 print("Retrying clone after deleting %s" %
2554 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002555 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002556 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2557 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002558 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002559 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002560 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2561 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002562 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002563 raise e
2564 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002565
Kevin Degi384b3c52014-10-16 16:02:58 -06002566 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002567 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002568 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002569
LaMont Jonescc879a92021-11-18 22:40:18 +00002570 def _expanded_ref_dirs():
2571 """Iterate through the possible git reference directory paths."""
2572 name = self.name + '.git'
2573 yield mirror_git or os.path.join(ref_dir, name)
2574 for prefix in '', self.remote.name:
2575 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2576 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002577
LaMont Jonescc879a92021-11-18 22:40:18 +00002578 if ref_dir or mirror_git:
2579 found_ref_dir = None
2580 for path in _expanded_ref_dirs():
2581 if os.path.exists(path):
2582 found_ref_dir = path
2583 break
2584 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002585
Kevin Degib1a07b82015-07-27 13:33:43 -06002586 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002587 if not os.path.isabs(ref_dir):
2588 # The alternate directory is relative to the object database.
2589 ref_dir = os.path.relpath(ref_dir,
2590 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002591 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002592 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002593
Kevin Degib1a07b82015-07-27 13:33:43 -06002594 m = self.manifest.manifestProject.config
2595 for key in ['user.name', 'user.email']:
2596 if m.Has(key, include_defaults=False):
2597 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002598 if not self.manifest.EnableGitLfs:
2599 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2600 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002601 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002602 except Exception:
2603 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002604 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002605 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002606 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002607 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608
David Pursehousee8ace262020-02-13 12:41:15 +09002609 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002610 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002611 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002612
David Pursehousee8ace262020-02-13 12:41:15 +09002613 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002614 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002615 if not os.path.exists(hooks):
2616 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002617
2618 # Delete sample hooks. They're noise.
2619 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002620 try:
2621 platform_utils.remove(hook, missing_ok=True)
2622 except PermissionError:
2623 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002624
Jonathan Nieder93719792015-03-17 11:29:58 -07002625 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002626 name = os.path.basename(stock_hook)
2627
Victor Boivie65e0f352011-04-18 11:23:29 +02002628 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002629 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002630 # Don't install a Gerrit Code Review hook if this
2631 # project does not appear to use it for reviews.
2632 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002633 # Since the manifest project is one of those, but also
2634 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002635 continue
2636
2637 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002638 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002639 continue
2640 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002641 # If the files are the same, we'll leave it alone. We create symlinks
2642 # below by default but fallback to hardlinks if the OS blocks them.
2643 # So if we're here, it's probably because we made a hardlink below.
2644 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002645 if not quiet:
2646 _warn("%s: Not replacing locally modified %s hook",
LaMont Jones8501d462022-06-22 19:21:15 +00002647 self.RelPath(local=False), name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002648 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002649 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002650 platform_utils.symlink(
2651 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002652 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002653 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002654 try:
2655 os.link(stock_hook, dst)
2656 except OSError:
2657 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002658 else:
2659 raise
2660
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002661 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002662 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002663 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002664 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002665 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002666 remote.review = self.remote.review
2667 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002668
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002669 if self.worktree:
2670 remote.ResetFetch(mirror=False)
2671 else:
2672 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002673 remote.Save()
2674
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002675 def _InitMRef(self):
2676 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002677 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002678 # Set up the m/ space to point to the worktree-specific ref space.
2679 # We'll update the worktree-specific ref space on each checkout.
2680 ref = R_M + self.manifest.branch
2681 if not self.bare_ref.symref(ref):
2682 self.bare_git.symbolic_ref(
2683 '-m', 'redirecting to worktree scope',
2684 ref, R_WORKTREE_M + self.manifest.branch)
2685
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002686 # We can't update this ref with git worktrees until it exists.
2687 # We'll wait until the initial checkout to set it.
2688 if not os.path.exists(self.worktree):
2689 return
2690
2691 base = R_WORKTREE_M
2692 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002693
2694 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002695 else:
2696 base = R_M
2697 active_git = self.bare_git
2698
2699 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002700
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002701 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002702 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002703
Remy Böhmer1469c282020-12-15 18:49:02 +01002704 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002705 cur = self.bare_ref.symref(ref)
2706
2707 if self.revisionId:
2708 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2709 msg = 'manifest set to %s' % self.revisionId
2710 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002711 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002712 else:
2713 remote = self.GetRemote(self.remote.name)
2714 dst = remote.ToLocal(self.revisionExpr)
2715 if cur != dst:
2716 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002717 if detach:
2718 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2719 else:
2720 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002721
Mike Frysingerc72bd842021-11-14 03:58:00 -05002722 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002723 # Git worktrees don't use symlinks to share at all.
2724 if self.use_git_worktrees:
2725 return
2726
Mike Frysingerd33dce02021-12-20 18:16:33 -05002727 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002728 # Try to self-heal a bit in simple cases.
2729 dst_path = os.path.join(destdir, name)
2730 src_path = os.path.join(srcdir, name)
2731
Mike Frysingered4f2112020-02-11 23:06:29 -05002732 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002733 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002734 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002735 # Fail if the links are pointing to the wrong place
2736 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002737 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002738 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002739 'work tree. If you\'re comfortable with the '
2740 'possibility of losing the work tree\'s git metadata,'
2741 ' use `repo sync --force-sync {0}` to '
LaMont Jones8501d462022-06-22 19:21:15 +00002742 'proceed.'.format(self.RelPath(local=False)))
Kevin Degi384b3c52014-10-16 16:02:58 -06002743
Mike Frysingerc72bd842021-11-14 03:58:00 -05002744 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002745 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2746
2747 Args:
2748 gitdir: The bare git repository. Must already be initialized.
2749 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002750 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2751 This saves you the effort of initializing |dotgit| yourself.
2752 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002753 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002754 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002755
2756 to_copy = []
2757 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002758 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002759
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002760 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002761 for name in set(to_copy).union(to_symlink):
2762 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002763 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002764 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002765
Kevin Degi384b3c52014-10-16 16:02:58 -06002766 if os.path.lexists(dst):
2767 continue
David James8d201162013-10-11 17:03:19 -07002768
2769 # If the source dir doesn't exist, create an empty dir.
2770 if name in symlink_dirs and not os.path.lexists(src):
2771 os.makedirs(src)
2772
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002773 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002774 platform_utils.symlink(
2775 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002776 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002777 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002778 shutil.copytree(src, dst)
2779 elif os.path.isfile(src):
2780 shutil.copy(src, dst)
2781
David James8d201162013-10-11 17:03:19 -07002782 except OSError as e:
2783 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002784 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002785 else:
2786 raise
2787
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002788 def _InitGitWorktree(self):
2789 """Init the project using git worktrees."""
2790 self.bare_git.worktree('prune')
2791 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2792 self.worktree, self.GetRevisionId())
2793
2794 # Rewrite the internal state files to use relative paths between the
2795 # checkouts & worktrees.
2796 dotgit = os.path.join(self.worktree, '.git')
2797 with open(dotgit, 'r') as fp:
2798 # Figure out the checkout->worktree path.
2799 setting = fp.read()
2800 assert setting.startswith('gitdir:')
2801 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002802 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2803 # of file permissions. Delete it and recreate it from scratch to avoid.
2804 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002805 # Use relative path from checkout->worktree & maintain Unix line endings
2806 # on all OS's to match git behavior.
2807 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002808 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2809 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002810 # Use relative path from worktree->checkout & maintain Unix line endings
2811 # on all OS's to match git behavior.
2812 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002813 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2814
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002815 self._InitMRef()
2816
Martin Kellye4e94d22017-03-21 16:05:12 -07002817 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002818 """Setup the worktree .git path.
2819
2820 This is the user-visible path like src/foo/.git/.
2821
2822 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2823 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2824
2825 Older checkouts had .git/ directories. If we see that, migrate it.
2826
2827 This also handles changes in the manifest. Maybe this project was backed
2828 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2829 the path we point to under .repo/projects/ to match.
2830 """
2831 dotgit = os.path.join(self.worktree, '.git')
2832
2833 # If using an old layout style (a directory), migrate it.
2834 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2835 self._MigrateOldWorkTreeGitDir(dotgit)
2836
2837 init_dotgit = not os.path.exists(dotgit)
2838 if self.use_git_worktrees:
2839 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002840 self._InitGitWorktree()
2841 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002842 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002843 if not init_dotgit:
2844 # See if the project has changed.
2845 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2846 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002847
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002848 if init_dotgit or not os.path.exists(dotgit):
2849 os.makedirs(self.worktree, exist_ok=True)
2850 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002851
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002852 if init_dotgit:
2853 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002854
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002855 # Finish checking out the worktree.
2856 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2857 if GitCommand(self, cmd).Wait() != 0:
2858 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002859
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002860 if submodules:
2861 self._SyncSubmodules(quiet=True)
2862 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002863
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002864 @classmethod
2865 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2866 """Migrate the old worktree .git/ dir style to a symlink.
2867
2868 This logic specifically only uses state from |dotgit| to figure out where to
2869 move content and not |self|. This way if the backing project also changed
2870 places, we only do the .git/ dir to .git symlink migration here. The path
2871 updates will happen independently.
2872 """
2873 # Figure out where in .repo/projects/ it's pointing to.
2874 if not os.path.islink(os.path.join(dotgit, 'refs')):
2875 raise GitError(f'{dotgit}: unsupported checkout state')
2876 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2877
2878 # Remove known symlink paths that exist in .repo/projects/.
2879 KNOWN_LINKS = {
2880 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2881 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2882 }
2883 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2884 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002885 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2886 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002887 }
2888
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002889 # First see if we'd succeed before starting the migration.
2890 unknown_paths = []
2891 for name in platform_utils.listdir(dotgit):
2892 # Ignore all temporary/backup names. These are common with vim & emacs.
2893 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2894 continue
2895
2896 dotgit_path = os.path.join(dotgit, name)
2897 if name in KNOWN_LINKS:
2898 if not platform_utils.islink(dotgit_path):
2899 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2900 else:
2901 gitdir_path = os.path.join(gitdir, name)
2902 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2903 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2904 if unknown_paths:
2905 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2906
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002907 # Now walk the paths and sync the .git/ to .repo/projects/.
2908 for name in platform_utils.listdir(dotgit):
2909 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002910
2911 # Ignore all temporary/backup names. These are common with vim & emacs.
2912 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2913 platform_utils.remove(dotgit_path)
2914 elif name in KNOWN_LINKS:
2915 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002916 else:
2917 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002918 platform_utils.remove(gitdir_path, missing_ok=True)
2919 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002920
2921 # Now that the dir should be empty, clear it out, and symlink it over.
2922 platform_utils.rmdir(dotgit)
2923 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002924
Renaud Paquay788e9622017-01-27 11:41:12 -08002925 def _get_symlink_error_message(self):
2926 if platform_utils.isWindows():
2927 return ('Unable to create symbolic link. Please re-run the command as '
2928 'Administrator, or see '
2929 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2930 'for other options.')
2931 return 'filesystem must support symlinks'
2932
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002933 def _revlist(self, *args, **kw):
2934 a = []
2935 a.extend(args)
2936 a.append('--')
2937 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002938
2939 @property
2940 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002941 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002942
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002943 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002944 """Get logs between two revisions of this project."""
2945 comp = '..'
2946 if rev1:
2947 revs = [rev1]
2948 if rev2:
2949 revs.extend([comp, rev2])
2950 cmd = ['log', ''.join(revs)]
2951 out = DiffColoring(self.config)
2952 if out.is_on and color:
2953 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002954 if pretty_format is not None:
2955 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002956 if oneline:
2957 cmd.append('--oneline')
2958
2959 try:
2960 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2961 if log.Wait() == 0:
2962 return log.stdout
2963 except GitError:
2964 # worktree may not exist if groups changed for example. In that case,
2965 # try in gitdir instead.
2966 if not os.path.exists(self.worktree):
2967 return self.bare_git.log(*cmd[1:])
2968 else:
2969 raise
2970 return None
2971
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002972 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2973 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002974 """Get the list of logs from this revision to given revisionId"""
2975 logs = {}
2976 selfId = self.GetRevisionId(self._allrefs)
2977 toId = toProject.GetRevisionId(toProject._allrefs)
2978
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002979 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2980 pretty_format=pretty_format)
2981 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2982 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002983 return logs
2984
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002985 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002986
David James8d201162013-10-11 17:03:19 -07002987 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002988 self._project = project
2989 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002990 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002991
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002992 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2993 def __getstate__(self):
2994 return (self._project, self._bare, self._gitdir)
2995
2996 def __setstate__(self, state):
2997 self._project, self._bare, self._gitdir = state
2998
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002999 def LsOthers(self):
3000 p = GitCommand(self._project,
3001 ['ls-files',
3002 '-z',
3003 '--others',
3004 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003005 bare=False,
David James8d201162013-10-11 17:03:19 -07003006 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003007 capture_stdout=True,
3008 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003009 if p.Wait() == 0:
3010 out = p.stdout
3011 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003012 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003013 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003014 return []
3015
3016 def DiffZ(self, name, *args):
3017 cmd = [name]
3018 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003019 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003020 cmd.extend(args)
3021 p = GitCommand(self._project,
3022 cmd,
David James8d201162013-10-11 17:03:19 -07003023 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003024 bare=False,
3025 capture_stdout=True,
3026 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05003027 p.Wait()
3028 r = {}
3029 out = p.stdout
3030 if out:
3031 out = iter(out[:-1].split('\0'))
3032 while out:
3033 try:
3034 info = next(out)
3035 path = next(out)
3036 except StopIteration:
3037 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003038
Mike Frysinger84230002021-02-16 17:08:35 -05003039 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003040
Mike Frysinger84230002021-02-16 17:08:35 -05003041 def __init__(self, path, omode, nmode, oid, nid, state):
3042 self.path = path
3043 self.src_path = None
3044 self.old_mode = omode
3045 self.new_mode = nmode
3046 self.old_id = oid
3047 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003048
Mike Frysinger84230002021-02-16 17:08:35 -05003049 if len(state) == 1:
3050 self.status = state
3051 self.level = None
3052 else:
3053 self.status = state[:1]
3054 self.level = state[1:]
3055 while self.level.startswith('0'):
3056 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003057
Mike Frysinger84230002021-02-16 17:08:35 -05003058 info = info[1:].split(' ')
3059 info = _Info(path, *info)
3060 if info.status in ('R', 'C'):
3061 info.src_path = info.path
3062 info.path = next(out)
3063 r[info.path] = info
3064 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003065
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003066 def GetDotgitPath(self, subpath=None):
3067 """Return the full path to the .git dir.
3068
3069 As a convenience, append |subpath| if provided.
3070 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003071 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003072 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003073 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003074 dotgit = os.path.join(self._project.worktree, '.git')
3075 if os.path.isfile(dotgit):
3076 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3077 with open(dotgit) as fp:
3078 setting = fp.read()
3079 assert setting.startswith('gitdir:')
3080 gitdir = setting.split(':', 1)[1].strip()
3081 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3082
3083 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3084
3085 def GetHead(self):
3086 """Return the ref that HEAD points to."""
3087 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003088 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003089 with open(path) as fd:
3090 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003091 except IOError as e:
3092 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003093 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303094 line = line.decode()
3095 except AttributeError:
3096 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003097 if line.startswith('ref: '):
3098 return line[5:-1]
3099 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003100
3101 def SetHead(self, ref, message=None):
3102 cmdv = []
3103 if message is not None:
3104 cmdv.extend(['-m', message])
3105 cmdv.append(HEAD)
3106 cmdv.append(ref)
3107 self.symbolic_ref(*cmdv)
3108
3109 def DetachHead(self, new, message=None):
3110 cmdv = ['--no-deref']
3111 if message is not None:
3112 cmdv.extend(['-m', message])
3113 cmdv.append(HEAD)
3114 cmdv.append(new)
3115 self.update_ref(*cmdv)
3116
3117 def UpdateRef(self, name, new, old=None,
3118 message=None,
3119 detach=False):
3120 cmdv = []
3121 if message is not None:
3122 cmdv.extend(['-m', message])
3123 if detach:
3124 cmdv.append('--no-deref')
3125 cmdv.append(name)
3126 cmdv.append(new)
3127 if old is not None:
3128 cmdv.append(old)
3129 self.update_ref(*cmdv)
3130
3131 def DeleteRef(self, name, old=None):
3132 if not old:
3133 old = self.rev_parse(name)
3134 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003135 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003136
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003137 def rev_list(self, *args, **kw):
3138 if 'format' in kw:
3139 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3140 else:
3141 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003142 cmdv.extend(args)
3143 p = GitCommand(self._project,
3144 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003145 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003146 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003147 capture_stdout=True,
3148 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003149 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003150 raise GitError('%s rev-list %s: %s' %
3151 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003152 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003153
3154 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003155 """Allow arbitrary git commands using pythonic syntax.
3156
3157 This allows you to do things like:
3158 git_obj.rev_parse('HEAD')
3159
3160 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3161 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003162 Any other positional arguments will be passed to the git command, and the
3163 following keyword arguments are supported:
3164 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003165
3166 Args:
3167 name: The name of the git command to call. Any '_' characters will
3168 be replaced with '-'.
3169
3170 Returns:
3171 A callable object that will try to call git with the named command.
3172 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003173 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003174
Dave Borowitz091f8932012-10-23 17:01:04 -07003175 def runner(*args, **kwargs):
3176 cmdv = []
3177 config = kwargs.pop('config', None)
3178 for k in kwargs:
3179 raise TypeError('%s() got an unexpected keyword argument %r'
3180 % (name, k))
3181 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303182 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003183 cmdv.append('-c')
3184 cmdv.append('%s=%s' % (k, v))
3185 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003186 cmdv.extend(args)
3187 p = GitCommand(self._project,
3188 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003189 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003190 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003191 capture_stdout=True,
3192 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003193 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003194 raise GitError('%s %s: %s' %
3195 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003196 r = p.stdout
3197 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3198 return r[:-1]
3199 return r
3200 return runner
3201
3202
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003203class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003204
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003205 def __str__(self):
3206 return 'prior sync failed; rebase still in progress'
3207
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003208
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003209class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003210
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003211 def __str__(self):
3212 return 'contains uncommitted changes'
3213
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003214
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003215class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003216
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003217 def __init__(self, project, text):
3218 self.project = project
3219 self.text = text
3220
3221 def Print(self, syncbuf):
LaMont Jones8501d462022-06-22 19:21:15 +00003222 syncbuf.out.info('%s/: %s', self.project.RelPath(local=False), self.text)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003223 syncbuf.out.nl()
3224
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003225
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003226class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003227
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003228 def __init__(self, project, why):
3229 self.project = project
3230 self.why = why
3231
3232 def Print(self, syncbuf):
3233 syncbuf.out.fail('error: %s/: %s',
LaMont Jones8501d462022-06-22 19:21:15 +00003234 self.project.RelPath(local=False),
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003235 str(self.why))
3236 syncbuf.out.nl()
3237
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003238
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003239class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003240
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003241 def __init__(self, project, action):
3242 self.project = project
3243 self.action = action
3244
3245 def Run(self, syncbuf):
3246 out = syncbuf.out
LaMont Jones8501d462022-06-22 19:21:15 +00003247 out.project('project %s/', self.project.RelPath(local=False))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003248 out.nl()
3249 try:
3250 self.action()
3251 out.nl()
3252 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003253 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003254 out.nl()
3255 return False
3256
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003257
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003258class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003259
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003260 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003261 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003262 self.project = self.printer('header', attr='bold')
3263 self.info = self.printer('info')
3264 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003265
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003266
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003267class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003268
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003269 def __init__(self, config, detach_head=False):
3270 self._messages = []
3271 self._failures = []
3272 self._later_queue1 = []
3273 self._later_queue2 = []
3274
3275 self.out = _SyncColoring(config)
3276 self.out.redirect(sys.stderr)
3277
3278 self.detach_head = detach_head
3279 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003280 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003281
3282 def info(self, project, fmt, *args):
3283 self._messages.append(_InfoMessage(project, fmt % args))
3284
3285 def fail(self, project, err=None):
3286 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003287 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003288
3289 def later1(self, project, what):
3290 self._later_queue1.append(_Later(project, what))
3291
3292 def later2(self, project, what):
3293 self._later_queue2.append(_Later(project, what))
3294
3295 def Finish(self):
3296 self._PrintMessages()
3297 self._RunLater()
3298 self._PrintMessages()
3299 return self.clean
3300
David Rileye0684ad2017-04-05 00:02:59 -07003301 def Recently(self):
3302 recent_clean = self.recent_clean
3303 self.recent_clean = True
3304 return recent_clean
3305
3306 def _MarkUnclean(self):
3307 self.clean = False
3308 self.recent_clean = False
3309
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003310 def _RunLater(self):
3311 for q in ['_later_queue1', '_later_queue2']:
3312 if not self._RunQueue(q):
3313 return
3314
3315 def _RunQueue(self, queue):
3316 for m in getattr(self, queue):
3317 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003318 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003319 return False
3320 setattr(self, queue, [])
3321 return True
3322
3323 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003324 if self._messages or self._failures:
3325 if os.isatty(2):
3326 self.out.write(progress.CSI_ERASE_LINE)
3327 self.out.write('\r')
3328
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003329 for m in self._messages:
3330 m.Print(self)
3331 for m in self._failures:
3332 m.Print(self)
3333
3334 self._messages = []
3335 self._failures = []
3336
3337
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003338class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003339 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003340
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003341 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003342 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003343 manifest=manifest,
3344 name=name,
3345 gitdir=gitdir,
3346 objdir=gitdir,
3347 worktree=worktree,
3348 remote=RemoteSpec('origin'),
3349 relpath='.repo/%s' % name,
3350 revisionExpr='refs/heads/master',
3351 revisionId=None,
3352 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003353
3354 def PreSync(self):
3355 if self.Exists:
3356 cb = self.CurrentBranch
3357 if cb:
3358 base = self.GetBranch(cb).merge
3359 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003360 self.revisionExpr = base
3361 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003362
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003363 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003364 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003365 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003366 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003367 return False
3368
David Pursehouse8a68ff92012-09-24 12:15:13 +09003369 all_refs = self.bare_ref.all
3370 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003371 head = self.work_git.GetHead()
3372 if head.startswith(R_HEADS):
3373 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003374 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003375 except KeyError:
3376 head = None
3377
3378 if revid == head:
3379 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003380 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003381 return True
3382 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003383
3384
3385class RepoProject(MetaProject):
3386 """The MetaProject for repo itself."""
3387
3388 @property
3389 def LastFetch(self):
3390 try:
3391 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3392 return os.path.getmtime(fh)
3393 except OSError:
3394 return 0
3395
3396class ManifestProject(MetaProject):
3397 """The MetaProject for manifests."""
3398
3399 def MetaBranchSwitch(self, submodules=False):
3400 """Prepare for manifest branch switch."""
3401
3402 # detach and delete manifest branch, allowing a new
3403 # branch to take over
3404 syncbuf = SyncBuffer(self.config, detach_head=True)
3405 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3406 syncbuf.Finish()
3407
3408 return GitCommand(self,
3409 ['update-ref', '-d', 'refs/heads/default'],
3410 capture_stdout=True,
3411 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003412
3413 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003414 def standalone_manifest_url(self):
3415 """The URL of the standalone manifest, or None."""
LaMont Jones55ee3042022-04-06 17:10:21 +00003416 return self.config.GetString('manifest.standalone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003417
3418 @property
3419 def manifest_groups(self):
3420 """The manifest groups string."""
3421 return self.config.GetString('manifest.groups')
3422
3423 @property
3424 def reference(self):
3425 """The --reference for this manifest."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003426 return self.config.GetString('repo.reference')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003427
3428 @property
3429 def dissociate(self):
3430 """Whether to dissociate."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003431 return self.config.GetBoolean('repo.dissociate')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003432
3433 @property
3434 def archive(self):
3435 """Whether we use archive."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003436 return self.config.GetBoolean('repo.archive')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003437
3438 @property
3439 def mirror(self):
3440 """Whether we use mirror."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003441 return self.config.GetBoolean('repo.mirror')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003442
3443 @property
3444 def use_worktree(self):
3445 """Whether we use worktree."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003446 return self.config.GetBoolean('repo.worktree')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003447
3448 @property
3449 def clone_bundle(self):
3450 """Whether we use clone_bundle."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003451 return self.config.GetBoolean('repo.clonebundle')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003452
3453 @property
3454 def submodules(self):
3455 """Whether we use submodules."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003456 return self.config.GetBoolean('repo.submodules')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003457
3458 @property
3459 def git_lfs(self):
3460 """Whether we use git_lfs."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003461 return self.config.GetBoolean('repo.git-lfs')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003462
3463 @property
3464 def use_superproject(self):
3465 """Whether we use superproject."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003466 return self.config.GetBoolean('repo.superproject')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003467
3468 @property
3469 def partial_clone(self):
3470 """Whether this is a partial clone."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003471 return self.config.GetBoolean('repo.partialclone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003472
3473 @property
3474 def depth(self):
3475 """Partial clone depth."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003476 return self.config.GetString('repo.depth')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003477
3478 @property
3479 def clone_filter(self):
3480 """The clone filter."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003481 return self.config.GetString('repo.clonefilter')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003482
3483 @property
3484 def partial_clone_exclude(self):
3485 """Partial clone exclude string"""
LaMont Jones4ada0432022-04-14 15:10:43 +00003486 return self.config.GetBoolean('repo.partialcloneexclude')
3487
3488 @property
3489 def manifest_platform(self):
3490 """The --platform argument from `repo init`."""
3491 return self.config.GetString('manifest.platform')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003492
3493 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003494 def _platform_name(self):
3495 """Return the name of the platform."""
3496 return platform.system().lower()
3497
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003498 def SyncWithPossibleInit(self, submanifest, verbose=False,
3499 current_branch_only=False, tags='', git_event_log=None):
3500 """Sync a manifestProject, possibly for the first time.
3501
3502 Call Sync() with arguments from the most recent `repo init`. If this is a
3503 new sub manifest, then inherit options from the parent's manifestProject.
3504
3505 This is used by subcmds.Sync() to do an initial download of new sub
3506 manifests.
3507
3508 Args:
3509 submanifest: an XmlSubmanifest, the submanifest to re-sync.
3510 verbose: a boolean, whether to show all output, rather than only errors.
3511 current_branch_only: a boolean, whether to only fetch the current manifest
3512 branch from the server.
3513 tags: a boolean, whether to fetch tags.
3514 git_event_log: an EventLog, for git tracing.
3515 """
3516 # TODO(lamontjones): when refactoring sync (and init?) consider how to
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003517 # better get the init options that we should use for new submanifests that
3518 # are added when syncing an existing workspace.
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003519 git_event_log = git_event_log or EventLog()
3520 spec = submanifest.ToSubmanifestSpec()
3521 # Use the init options from the existing manifestProject, or the parent if
3522 # it doesn't exist.
3523 #
3524 # Today, we only support changing manifest_groups on the sub-manifest, with
3525 # no supported-for-the-user way to change the other arguments from those
3526 # specified by the outermost manifest.
3527 #
3528 # TODO(lamontjones): determine which of these should come from the outermost
3529 # manifest and which should come from the parent manifest.
3530 mp = self if self.Exists else submanifest.parent.manifestProject
3531 return self.Sync(
3532 manifest_url=spec.manifestUrl,
3533 manifest_branch=spec.revision,
3534 standalone_manifest=mp.standalone_manifest_url,
3535 groups=mp.manifest_groups,
3536 platform=mp.manifest_platform,
3537 mirror=mp.mirror,
3538 dissociate=mp.dissociate,
3539 reference=mp.reference,
3540 worktree=mp.use_worktree,
3541 submodules=mp.submodules,
3542 archive=mp.archive,
3543 partial_clone=mp.partial_clone,
3544 clone_filter=mp.clone_filter,
3545 partial_clone_exclude=mp.partial_clone_exclude,
3546 clone_bundle=mp.clone_bundle,
3547 git_lfs=mp.git_lfs,
3548 use_superproject=mp.use_superproject,
3549 verbose=verbose,
3550 current_branch_only=current_branch_only,
3551 tags=tags,
3552 depth=mp.depth,
3553 git_event_log=git_event_log,
3554 manifest_name=spec.manifestName,
3555 this_manifest_only=True,
3556 outer_manifest=False,
3557 )
3558
LaMont Jones9b03f152022-03-29 23:01:18 +00003559 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003560 standalone_manifest=False, groups='', mirror=False, reference='',
3561 dissociate=False, worktree=False, submodules=False, archive=False,
3562 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003563 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3564 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jones55ee3042022-04-06 17:10:21 +00003565 git_event_log=None, platform='', manifest_name='default.xml',
3566 tags='', this_manifest_only=False, outer_manifest=True):
LaMont Jones9b03f152022-03-29 23:01:18 +00003567 """Sync the manifest and all submanifests.
3568
3569 Args:
3570 manifest_url: a string, the URL of the manifest project.
3571 manifest_branch: a string, the manifest branch to use.
3572 standalone_manifest: a boolean, whether to store the manifest as a static
3573 file.
3574 groups: a string, restricts the checkout to projects with the specified
3575 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003576 mirror: a boolean, whether to create a mirror of the remote repository.
3577 reference: a string, location of a repo instance to use as a reference.
3578 dissociate: a boolean, whether to dissociate from reference mirrors after
3579 clone.
3580 worktree: a boolean, whether to use git-worktree to manage projects.
3581 submodules: a boolean, whether sync submodules associated with the
3582 manifest project.
3583 archive: a boolean, whether to checkout each project as an archive. See
3584 git-archive.
3585 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003586 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003587 clone_filter: a string, filter to use with partial_clone.
3588 partial_clone_exclude : a string, comma-delimeted list of project namess
3589 to exclude from partial clone.
3590 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3591 git_lfs: a boolean, whether to enable git LFS support.
3592 use_superproject: a boolean, whether to use the manifest superproject to
3593 sync projects.
3594 verbose: a boolean, whether to show all output, rather than only errors.
3595 current_branch_only: a boolean, whether to only fetch the current manifest
3596 branch from the server.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003597 platform: a string, restrict the checkout to projects with the specified
3598 platform group.
LaMont Jones55ee3042022-04-06 17:10:21 +00003599 git_event_log: an EventLog, for git tracing.
LaMont Jones4ada0432022-04-14 15:10:43 +00003600 tags: a boolean, whether to fetch tags.
LaMont Jones409407a2022-04-05 21:21:56 +00003601 manifest_name: a string, the name of the manifest file to use.
3602 this_manifest_only: a boolean, whether to only operate on the current sub
3603 manifest.
3604 outer_manifest: a boolean, whether to start at the outermost manifest.
LaMont Jones9b03f152022-03-29 23:01:18 +00003605
3606 Returns:
3607 a boolean, whether the sync was successful.
3608 """
3609 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3610
LaMont Jones501733c2022-04-20 16:42:32 +00003611 groups = groups or self.manifest.GetDefaultGroupsStr(with_platform=False)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003612 platform = platform or 'auto'
LaMont Jones55ee3042022-04-06 17:10:21 +00003613 git_event_log = git_event_log or EventLog()
LaMont Jones409407a2022-04-05 21:21:56 +00003614 if outer_manifest and self.manifest.is_submanifest:
3615 # In a multi-manifest checkout, use the outer manifest unless we are told
3616 # not to.
3617 return self.client.outer_manifest.manifestProject.Sync(
3618 manifest_url=manifest_url,
3619 manifest_branch=manifest_branch,
3620 standalone_manifest=standalone_manifest,
3621 groups=groups,
3622 platform=platform,
3623 mirror=mirror,
3624 dissociate=dissociate,
3625 reference=reference,
3626 worktree=worktree,
3627 submodules=submodules,
3628 archive=archive,
3629 partial_clone=partial_clone,
3630 clone_filter=clone_filter,
3631 partial_clone_exclude=partial_clone_exclude,
3632 clone_bundle=clone_bundle,
3633 git_lfs=git_lfs,
3634 use_superproject=use_superproject,
3635 verbose=verbose,
3636 current_branch_only=current_branch_only,
3637 tags=tags,
3638 depth=depth,
LaMont Jones55ee3042022-04-06 17:10:21 +00003639 git_event_log=git_event_log,
LaMont Jones409407a2022-04-05 21:21:56 +00003640 manifest_name=manifest_name,
3641 this_manifest_only=this_manifest_only,
3642 outer_manifest=False)
3643
LaMont Jones9b03f152022-03-29 23:01:18 +00003644 # If repo has already been initialized, we take -u with the absence of
3645 # --standalone-manifest to mean "transition to a standard repo set up",
3646 # which necessitates starting fresh.
3647 # If --standalone-manifest is set, we always tear everything down and start
3648 # anew.
3649 if self.Exists:
3650 was_standalone_manifest = self.config.GetString('manifest.standalone')
3651 if was_standalone_manifest and not manifest_url:
3652 print('fatal: repo was initialized with a standlone manifest, '
3653 'cannot be re-initialized without --manifest-url/-u')
3654 return False
3655
3656 if standalone_manifest or (was_standalone_manifest and manifest_url):
3657 self.config.ClearCache()
3658 if self.gitdir and os.path.exists(self.gitdir):
3659 platform_utils.rmtree(self.gitdir)
3660 if self.worktree and os.path.exists(self.worktree):
3661 platform_utils.rmtree(self.worktree)
3662
3663 is_new = not self.Exists
3664 if is_new:
3665 if not manifest_url:
3666 print('fatal: manifest url is required.', file=sys.stderr)
3667 return False
3668
LaMont Jones409407a2022-04-05 21:21:56 +00003669 if verbose:
LaMont Jones9b03f152022-03-29 23:01:18 +00003670 print('Downloading manifest from %s' %
3671 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3672 file=sys.stderr)
3673
3674 # The manifest project object doesn't keep track of the path on the
3675 # server where this git is located, so let's save that here.
3676 mirrored_manifest_git = None
3677 if reference:
3678 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3679 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3680 if not mirrored_manifest_git.endswith(".git"):
3681 mirrored_manifest_git += ".git"
3682 if not os.path.exists(mirrored_manifest_git):
3683 mirrored_manifest_git = os.path.join(reference,
3684 '.repo/manifests.git')
3685
3686 self._InitGitDir(mirror_git=mirrored_manifest_git)
3687
3688 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3689 # still do much of the manifests.git set up, but will avoid actual syncs to
3690 # a remote.
3691 if standalone_manifest:
3692 self.config.SetString('manifest.standalone', manifest_url)
3693 elif not manifest_url and not manifest_branch:
3694 # If -u is set and --standalone-manifest is not, then we're not in
3695 # standalone mode. Otherwise, use config to infer what we were in the last
3696 # init.
3697 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3698 if not standalone_manifest:
3699 self.config.SetString('manifest.standalone', None)
3700
3701 self._ConfigureDepth(depth)
3702
3703 # Set the remote URL before the remote branch as we might need it below.
3704 if manifest_url:
3705 r = self.GetRemote(self.remote.name)
3706 r.url = manifest_url
3707 r.ResetFetch()
3708 r.Save()
3709
3710 if not standalone_manifest:
3711 if manifest_branch:
3712 if manifest_branch == 'HEAD':
3713 manifest_branch = self.ResolveRemoteHead()
3714 if manifest_branch is None:
3715 print('fatal: unable to resolve HEAD', file=sys.stderr)
3716 return False
3717 self.revisionExpr = manifest_branch
3718 else:
3719 if is_new:
3720 default_branch = self.ResolveRemoteHead()
3721 if default_branch is None:
3722 # If the remote doesn't have HEAD configured, default to master.
3723 default_branch = 'refs/heads/master'
3724 self.revisionExpr = default_branch
3725 else:
3726 self.PreSync()
3727
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003728 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003729 all_platforms = ['linux', 'darwin', 'windows']
3730 platformize = lambda x: 'platform-' + x
3731 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003732 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003733 groups.append(platformize(self._platform_name))
3734 elif platform == 'all':
3735 groups.extend(map(platformize, all_platforms))
3736 elif platform in all_platforms:
3737 groups.append(platformize(platform))
3738 elif platform != 'none':
3739 print('fatal: invalid platform flag', file=sys.stderr)
3740 return False
LaMont Jones4ada0432022-04-14 15:10:43 +00003741 self.config.SetString('manifest.platform', platform)
LaMont Jones9b03f152022-03-29 23:01:18 +00003742
3743 groups = [x for x in groups if x]
3744 groupstr = ','.join(groups)
3745 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3746 groupstr = None
3747 self.config.SetString('manifest.groups', groupstr)
3748
3749 if reference:
3750 self.config.SetString('repo.reference', reference)
3751
3752 if dissociate:
3753 self.config.SetBoolean('repo.dissociate', dissociate)
3754
3755 if worktree:
3756 if mirror:
3757 print('fatal: --mirror and --worktree are incompatible',
3758 file=sys.stderr)
3759 return False
3760 if submodules:
3761 print('fatal: --submodules and --worktree are incompatible',
3762 file=sys.stderr)
3763 return False
3764 self.config.SetBoolean('repo.worktree', worktree)
3765 if is_new:
3766 self.use_git_worktrees = True
3767 print('warning: --worktree is experimental!', file=sys.stderr)
3768
3769 if archive:
3770 if is_new:
3771 self.config.SetBoolean('repo.archive', archive)
3772 else:
3773 print('fatal: --archive is only supported when initializing a new '
3774 'workspace.', file=sys.stderr)
3775 print('Either delete the .repo folder in this workspace, or initialize '
3776 'in another location.', file=sys.stderr)
3777 return False
3778
3779 if mirror:
3780 if is_new:
3781 self.config.SetBoolean('repo.mirror', mirror)
3782 else:
3783 print('fatal: --mirror is only supported when initializing a new '
3784 'workspace.', file=sys.stderr)
3785 print('Either delete the .repo folder in this workspace, or initialize '
3786 'in another location.', file=sys.stderr)
3787 return False
3788
3789 if partial_clone is not None:
3790 if mirror:
3791 print('fatal: --mirror and --partial-clone are mutually exclusive',
3792 file=sys.stderr)
3793 return False
3794 self.config.SetBoolean('repo.partialclone', partial_clone)
3795 if clone_filter:
3796 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jones55ee3042022-04-06 17:10:21 +00003797 elif self.partial_clone:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003798 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003799 else:
3800 clone_filter = None
3801
3802 if partial_clone_exclude is not None:
3803 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3804
3805 if clone_bundle is None:
3806 clone_bundle = False if partial_clone else True
3807 else:
3808 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3809
3810 if submodules:
3811 self.config.SetBoolean('repo.submodules', submodules)
3812
3813 if git_lfs is not None:
3814 if git_lfs:
3815 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3816
3817 self.config.SetBoolean('repo.git-lfs', git_lfs)
3818 if not is_new:
3819 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3820 ' Existing projects will require manual updates.\n', file=sys.stderr)
3821
3822 if use_superproject is not None:
3823 self.config.SetBoolean('repo.superproject', use_superproject)
3824
LaMont Jones0165e202022-04-27 17:34:42 +00003825 if not standalone_manifest:
3826 if not self.Sync_NetworkHalf(
3827 is_new=is_new, quiet=not verbose, verbose=verbose,
3828 clone_bundle=clone_bundle, current_branch_only=current_branch_only,
3829 tags=tags, submodules=submodules, clone_filter=clone_filter,
3830 partial_clone_exclude=self.manifest.PartialCloneExclude):
3831 r = self.GetRemote(self.remote.name)
3832 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
LaMont Jones9b03f152022-03-29 23:01:18 +00003833
LaMont Jones0165e202022-04-27 17:34:42 +00003834 # Better delete the manifest git dir if we created it; otherwise next
3835 # time (when user fixes problems) we won't go through the "is_new" logic.
3836 if is_new:
3837 platform_utils.rmtree(self.gitdir)
LaMont Jones9b03f152022-03-29 23:01:18 +00003838 return False
3839
LaMont Jones0165e202022-04-27 17:34:42 +00003840 if manifest_branch:
3841 self.MetaBranchSwitch(submodules=submodules)
3842
3843 syncbuf = SyncBuffer(self.config)
3844 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3845 syncbuf.Finish()
3846
3847 if is_new or self.CurrentBranch is None:
3848 if not self.StartBranch('default'):
3849 print('fatal: cannot create default in manifest', file=sys.stderr)
3850 return False
3851
3852 if not manifest_name:
3853 print('fatal: manifest name (-m) is required.', file=sys.stderr)
3854 return False
3855
3856 elif is_new:
3857 # This is a new standalone manifest.
3858 manifest_name = 'default.xml'
3859 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3860 dest = os.path.join(self.worktree, manifest_name)
3861 os.makedirs(os.path.dirname(dest), exist_ok=True)
3862 with open(dest, 'wb') as f:
3863 f.write(manifest_data)
LaMont Jones409407a2022-04-05 21:21:56 +00003864
3865 try:
3866 self.manifest.Link(manifest_name)
3867 except ManifestParseError as e:
3868 print("fatal: manifest '%s' not available" % manifest_name,
3869 file=sys.stderr)
3870 print('fatal: %s' % str(e), file=sys.stderr)
3871 return False
3872
LaMont Jones55ee3042022-04-06 17:10:21 +00003873 if not this_manifest_only:
3874 for submanifest in self.manifest.submanifests.values():
LaMont Jonesb90a4222022-04-14 15:00:09 +00003875 spec = submanifest.ToSubmanifestSpec()
LaMont Jones55ee3042022-04-06 17:10:21 +00003876 submanifest.repo_client.manifestProject.Sync(
3877 manifest_url=spec.manifestUrl,
3878 manifest_branch=spec.revision,
3879 standalone_manifest=standalone_manifest,
3880 groups=self.manifest_groups,
3881 platform=platform,
3882 mirror=mirror,
3883 dissociate=dissociate,
3884 reference=reference,
3885 worktree=worktree,
3886 submodules=submodules,
3887 archive=archive,
3888 partial_clone=partial_clone,
3889 clone_filter=clone_filter,
3890 partial_clone_exclude=partial_clone_exclude,
3891 clone_bundle=clone_bundle,
3892 git_lfs=git_lfs,
3893 use_superproject=use_superproject,
3894 verbose=verbose,
3895 current_branch_only=current_branch_only,
3896 tags=tags,
3897 depth=depth,
3898 git_event_log=git_event_log,
3899 manifest_name=spec.manifestName,
3900 this_manifest_only=False,
3901 outer_manifest=False,
3902 )
LaMont Jones409407a2022-04-05 21:21:56 +00003903
LaMont Jones0ddb6772022-05-20 09:11:54 +00003904 # Lastly, if the manifest has a <superproject> then have the superproject
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003905 # sync it (if it will be used).
3906 if git_superproject.UseSuperproject(use_superproject, self.manifest):
LaMont Jones0ddb6772022-05-20 09:11:54 +00003907 sync_result = self.manifest.superproject.Sync(git_event_log)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003908 if not sync_result.success:
3909 print('warning: git update of superproject for '
3910 f'{self.manifest.path_prefix} failed, repo sync will not use '
3911 'superproject to fetch source; while this error is not fatal, '
3912 'and you can continue to run repo sync, please run repo init '
3913 'with the --no-use-superproject option to stop seeing this '
3914 'warning', file=sys.stderr)
3915 if sync_result.fatal and use_superproject is not None:
3916 return False
LaMont Jones409407a2022-04-05 21:21:56 +00003917
LaMont Jones9b03f152022-03-29 23:01:18 +00003918 return True
3919
3920 def _ConfigureDepth(self, depth):
3921 """Configure the depth we'll sync down.
3922
3923 Args:
3924 depth: an int, how deep of a partial clone to create.
3925 """
3926 # Opt.depth will be non-None if user actually passed --depth to repo init.
3927 if depth is not None:
3928 if depth > 0:
3929 # Positive values will set the depth.
3930 depth = str(depth)
3931 else:
3932 # Negative numbers will clear the depth; passing None to SetString
3933 # will do that.
3934 depth = None
3935
3936 # We store the depth in the main manifest project.
3937 self.config.SetString('repo.depth', depth)