blob: 2b57a5fb7ccd86f5e6004d80d86d6532e25de09d [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
Mike Frysingerdede5642022-07-10 04:56:04 -0400689 def GetRemote(self, name=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700690 """Get the configuration for a single remote.
Mike Frysingerdede5642022-07-10 04:56:04 -0400691
692 Defaults to the current project's remote.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700693 """
Mike Frysingerdede5642022-07-10 04:56:04 -0400694 if name is None:
695 name = self.remote.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696 return self.config.GetRemote(name)
697
698 def GetBranch(self, name):
699 """Get the configuration for a single branch.
700 """
701 return self.config.GetBranch(name)
702
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700703 def GetBranches(self):
704 """Get all existing local branches.
705 """
706 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900707 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700708 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700709
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530710 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700711 if name.startswith(R_HEADS):
712 name = name[len(R_HEADS):]
713 b = self.GetBranch(name)
714 b.current = name == current
715 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900716 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700717 heads[name] = b
718
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530719 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700720 if name.startswith(R_PUB):
721 name = name[len(R_PUB):]
722 b = heads.get(name)
723 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900724 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700725
726 return heads
727
Colin Cross5acde752012-03-28 20:15:45 -0700728 def MatchesGroups(self, manifest_groups):
729 """Returns true if the manifest groups specified at init should cause
730 this project to be synced.
731 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700732 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700733
734 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700735 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700736 manifest_groups: "-group1,group2"
737 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500738
739 The special manifest group "default" will match any project that
740 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700741 """
LaMont Jones501733c2022-04-20 16:42:32 +0000742 default_groups = self.manifest.default_groups or ['default']
743 expanded_manifest_groups = manifest_groups or default_groups
Conley Owensbb1b5f52012-08-13 13:11:18 -0700744 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700745 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500746 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700747
Conley Owens971de8e2012-04-16 10:36:08 -0700748 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700749 for group in expanded_manifest_groups:
750 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700751 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700752 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700753 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700754
Conley Owens971de8e2012-04-16 10:36:08 -0700755 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700756
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700757# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700758 def UncommitedFiles(self, get_all=True):
759 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700760
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700761 Args:
762 get_all: a boolean, if True - get information about all different
763 uncommitted files. If False - return as soon as any kind of
764 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500765 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700766 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500767 self.work_git.update_index('-q',
768 '--unmerged',
769 '--ignore-missing',
770 '--refresh')
771 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700772 details.append("rebase in progress")
773 if not get_all:
774 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500775
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700776 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
777 if changes:
778 details.extend(changes)
779 if not get_all:
780 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500781
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700782 changes = self.work_git.DiffZ('diff-files').keys()
783 if changes:
784 details.extend(changes)
785 if not get_all:
786 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500787
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200788 changes = self.UntrackedFiles()
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700789 if changes:
790 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500791
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700792 return details
793
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200794 def UntrackedFiles(self):
795 """Returns a list of strings, untracked files in the git tree."""
796 return self.work_git.LsOthers()
797
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700798 def HasChanges(self):
799 """Returns true if there are uncommitted changes.
800 """
Martin Geisler8db78c72022-07-08 11:05:24 +0200801 return bool(self.UncommitedFiles(get_all=False))
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500802
LaMont Jones8501d462022-06-22 19:21:15 +0000803 def PrintWorkTreeStatus(self, output_redir=None, quiet=False, local=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700804 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200805
806 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200807 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600808 quiet: If True then only print the project name. Do not print
809 the modified files, branch name, etc.
LaMont Jones8501d462022-06-22 19:21:15 +0000810 local: a boolean, if True, the path is relative to the local
811 (sub)manifest. If false, the path is relative to the
812 outermost manifest.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700813 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700814 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700815 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200816 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700817 print(file=output_redir)
LaMont Jones8501d462022-06-22 19:21:15 +0000818 print('project %s/' % self.RelPath(local), file=output_redir)
Sarah Owenscecd1d82012-11-01 22:59:27 -0700819 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700820 return
821
822 self.work_git.update_index('-q',
823 '--unmerged',
824 '--ignore-missing',
825 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700826 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700827 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
828 df = self.work_git.DiffZ('diff-files')
829 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100830 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700831 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832
833 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700834 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200835 out.redirect(output_redir)
LaMont Jones8501d462022-06-22 19:21:15 +0000836 out.project('project %-40s', self.RelPath(local) + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700837
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600838 if quiet:
839 out.nl()
840 return 'DIRTY'
841
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700842 branch = self.CurrentBranch
843 if branch is None:
844 out.nobranch('(*** NO BRANCH ***)')
845 else:
846 out.branch('branch %s', branch)
847 out.nl()
848
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700849 if rb:
850 out.important('prior sync failed; rebase still in progress')
851 out.nl()
852
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853 paths = list()
854 paths.extend(di.keys())
855 paths.extend(df.keys())
856 paths.extend(do)
857
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530858 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900859 try:
860 i = di[p]
861 except KeyError:
862 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700863
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900864 try:
865 f = df[p]
866 except KeyError:
867 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200868
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900869 if i:
870 i_status = i.status.upper()
871 else:
872 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700873
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900874 if f:
875 f_status = f.status.lower()
876 else:
877 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878
879 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800880 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700881 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700882 else:
883 line = ' %s%s\t%s' % (i_status, f_status, p)
884
885 if i and not f:
886 out.added('%s', line)
887 elif (i and f) or (not i and f):
888 out.changed('%s', line)
889 elif not i and not f:
890 out.untracked('%s', line)
891 else:
892 out.write('%s', line)
893 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200894
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700895 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896
LaMont Jones8501d462022-06-22 19:21:15 +0000897 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None,
898 local=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899 """Prints the status of the repository to stdout.
900 """
901 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500902 if output_redir:
903 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904 cmd = ['diff']
905 if out.is_on:
906 cmd.append('--color')
907 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300908 if absolute_paths:
LaMont Jones8501d462022-06-22 19:21:15 +0000909 cmd.append('--src-prefix=a/%s/' % self.RelPath(local))
910 cmd.append('--dst-prefix=b/%s/' % self.RelPath(local))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400912 try:
913 p = GitCommand(self,
914 cmd,
915 capture_stdout=True,
916 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500917 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400918 except GitError as e:
919 out.nl()
LaMont Jones8501d462022-06-22 19:21:15 +0000920 out.project('project %s/' % self.RelPath(local))
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400921 out.nl()
922 out.fail('%s', str(e))
923 out.nl()
924 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500925 if p.stdout:
926 out.nl()
LaMont Jones8501d462022-06-22 19:21:15 +0000927 out.project('project %s/' % self.RelPath(local))
Mike Frysinger84230002021-02-16 17:08:35 -0500928 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500929 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400930 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700932# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900933 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934 """Was the branch published (uploaded) for code review?
935 If so, returns the SHA-1 hash of the last published
936 state for the branch.
937 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700938 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900939 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700940 try:
941 return self.bare_git.rev_parse(key)
942 except GitError:
943 return None
944 else:
945 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900946 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700947 except KeyError:
948 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949
David Pursehouse8a68ff92012-09-24 12:15:13 +0900950 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951 """Prunes any stale published refs.
952 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900953 if all_refs is None:
954 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700955 heads = set()
956 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530957 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958 if name.startswith(R_HEADS):
959 heads.add(name)
960 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900961 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530963 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964 n = name[len(R_PUB):]
965 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900966 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700968 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 """List any branches which can be uploaded for review.
970 """
971 heads = {}
972 pubed = {}
973
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530974 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700975 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900976 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700977 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900978 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979
980 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530981 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900982 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700984 if selected_branch and branch != selected_branch:
985 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800987 rb = self.GetUploadableBranch(branch)
988 if rb:
989 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990 return ready
991
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800992 def GetUploadableBranch(self, branch_name):
993 """Get a single uploadable branch, or None.
994 """
995 branch = self.GetBranch(branch_name)
996 base = branch.LocalMerge
997 if branch.LocalMerge:
998 rb = ReviewableBranch(self, branch, base)
999 if rb.commits:
1000 return rb
1001 return None
1002
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001003 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001004 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -05001005 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001006 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001007 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001008 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +02001009 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001010 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001011 wip=False,
William Escandeac76fd32022-08-02 16:05:37 -07001012 ready=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001013 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001014 validate_certs=True,
1015 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016 """Uploads the named branch for code review.
1017 """
1018 if branch is None:
1019 branch = self.CurrentBranch
1020 if branch is None:
1021 raise GitError('not currently on a branch')
1022
1023 branch = self.GetBranch(branch)
1024 if not branch.LocalMerge:
1025 raise GitError('branch %s does not track a remote' % branch.name)
1026 if not branch.remote.review:
1027 raise GitError('remote %s has no review url' % branch.remote.name)
1028
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001029 # Basic validity check on label syntax.
1030 for label in labels:
1031 if not re.match(r'^.+[+-][0-9]+$', label):
1032 raise UploadError(
1033 f'invalid label syntax "{label}": labels use forms like '
1034 'CodeReview+1 or Verified-1')
1035
Bryan Jacobsf609f912013-05-06 13:36:24 -04001036 if dest_branch is None:
1037 dest_branch = self.dest_branch
1038 if dest_branch is None:
1039 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001040 if not dest_branch.startswith(R_HEADS):
1041 dest_branch = R_HEADS + dest_branch
1042
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001043 if not branch.remote.projectname:
1044 branch.remote.projectname = self.name
1045 branch.remote.Save()
1046
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001047 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001048 if url is None:
1049 raise UploadError('review not configured')
1050 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001051 if dryrun:
1052 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001053
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001054 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001055 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001056
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001057 for push_option in (push_options or []):
1058 cmd.append('-o')
1059 cmd.append(push_option)
1060
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001061 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001062
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001063 if dest_branch.startswith(R_HEADS):
1064 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001065
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001066 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001067 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001068 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001069 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001070 opts += ['t=%s' % p for p in hashtags]
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001071 # NB: No need to encode labels as they've been validated above.
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001072 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001073
David Pursehousef25a3702018-11-14 19:01:22 -08001074 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001075 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001076 if notify:
1077 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001078 if private:
1079 opts += ['private']
1080 if wip:
1081 opts += ['wip']
William Escandeac76fd32022-08-02 16:05:37 -07001082 if ready:
1083 opts += ['ready']
Jonathan Nieder713c5872018-11-05 13:21:52 -08001084 if opts:
1085 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001086 cmd.append(ref_spec)
1087
Anthony King7bdac712014-07-16 12:56:40 +01001088 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001089 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090
Mike Frysingerd7f86832020-11-19 19:18:46 -05001091 if not dryrun:
1092 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1093 self.bare_git.UpdateRef(R_PUB + branch.name,
1094 R_HEADS + branch.name,
1095 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001096
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001097# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001098 def _ExtractArchive(self, tarpath, path=None):
1099 """Extract the given tar on its current location
1100
1101 Args:
1102 - tarpath: The path to the actual tar file
1103
1104 """
1105 try:
1106 with tarfile.open(tarpath, 'r') as tar:
1107 tar.extractall(path=path)
1108 return True
1109 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001110 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001111 return False
1112
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001113 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001114 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001115 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001116 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001117 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001118 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001119 force_sync=False,
1120 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001121 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001122 archive=False,
1123 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001124 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001125 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001126 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001127 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001128 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001129 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001130 """Perform only the network IO portion of the sync process.
1131 Local working directory/branch state is not affected.
1132 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001133 if archive and not isinstance(self, MetaProject):
1134 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001135 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001136 return False
1137
1138 name = self.relpath.replace('\\', '/')
1139 name = name.replace('/', '_')
1140 tarpath = '%s.tar' % name
1141 topdir = self.manifest.topdir
1142
1143 try:
1144 self._FetchArchive(tarpath, cwd=topdir)
1145 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001146 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001147 return False
1148
1149 # From now on, we only need absolute tarpath
1150 tarpath = os.path.join(topdir, tarpath)
1151
1152 if not self._ExtractArchive(tarpath, path=topdir):
1153 return False
1154 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001155 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001156 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001157 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001158 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001159 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001160
1161 # If the shared object dir already exists, don't try to rebootstrap with a
1162 # clone bundle download. We should have the majority of objects already.
1163 if clone_bundle and os.path.exists(self.objdir):
1164 clone_bundle = False
1165
Raman Tennetif32f2432021-04-12 20:57:25 -07001166 if self.name in partial_clone_exclude:
1167 clone_bundle = True
1168 clone_filter = None
1169
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001170 if is_new is None:
1171 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001172 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001173 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001174 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001175 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001177
LaMont Jones68d69632022-06-07 18:24:20 +00001178 if self.UseAlternates:
Mike Frysinger1d00a7e2021-12-21 00:40:31 -05001179 # If gitdir/objects is a symlink, migrate it from the old layout.
1180 gitdir_objects = os.path.join(self.gitdir, 'objects')
1181 if platform_utils.islink(gitdir_objects):
1182 platform_utils.remove(gitdir_objects, missing_ok=True)
1183 gitdir_alt = os.path.join(self.gitdir, 'objects/info/alternates')
1184 if not os.path.exists(gitdir_alt):
1185 os.makedirs(os.path.dirname(gitdir_alt), exist_ok=True)
1186 _lwrite(gitdir_alt, os.path.join(
1187 os.path.relpath(self.objdir, gitdir_objects), 'objects') + '\n')
1188
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001189 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001190 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001191 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001192 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001193 # This works for both absolute and relative alternate directories.
1194 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001195 except IOError:
1196 alt_dir = None
1197 else:
1198 alt_dir = None
1199
Mike Frysingere50b6a72020-02-19 01:45:48 -05001200 if (clone_bundle
1201 and alt_dir is None
1202 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001203 is_new = False
1204
Mike Frysinger73561142021-05-03 01:10:09 -04001205 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001206 if self.sync_c:
1207 current_branch_only = True
1208 elif not self.manifest._loaded:
1209 # Manifest cannot check defaults until it syncs.
1210 current_branch_only = False
1211 elif self.manifest.default.sync_c:
1212 current_branch_only = True
1213
Mike Frysingerd68ed632021-05-03 01:21:35 -04001214 if tags is None:
1215 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001216
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001217 if self.clone_depth:
1218 depth = self.clone_depth
1219 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001220 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001221
Mike Frysinger521d01b2020-02-17 01:51:49 -05001222 # See if we can skip the network fetch entirely.
1223 if not (optimized_fetch and
1224 (ID_RE.match(self.revisionExpr) and
1225 self._CheckForImmutableRevision())):
1226 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001227 initial=is_new,
1228 quiet=quiet, verbose=verbose, output_redir=output_redir,
1229 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001230 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001231 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001232 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001233 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001234 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001235
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001236 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001237 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001238 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001239 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001240 if os.path.exists(alternates_file):
1241 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001242 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1243 merge_output=bool(output_redir))
1244 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001245 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001246 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001247 return False
1248 platform_utils.remove(alternates_file)
1249
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001250 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001251 self._InitMRef()
1252 else:
1253 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001254 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1255 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001256 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001257
1258 def PostRepoUpgrade(self):
1259 self._InitHooks()
1260
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001261 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001262 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001263 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001264 for copyfile in self.copyfiles:
1265 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001266 for linkfile in self.linkfiles:
1267 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001268
Julien Camperguedd654222014-01-09 16:21:37 +01001269 def GetCommitRevisionId(self):
1270 """Get revisionId of a commit.
1271
1272 Use this method instead of GetRevisionId to get the id of the commit rather
1273 than the id of the current git object (for example, a tag)
1274
1275 """
1276 if not self.revisionExpr.startswith(R_TAGS):
1277 return self.GetRevisionId(self._allrefs)
1278
1279 try:
1280 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1281 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001282 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1283 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001284
David Pursehouse8a68ff92012-09-24 12:15:13 +09001285 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001286 if self.revisionId:
1287 return self.revisionId
1288
Mike Frysingerdede5642022-07-10 04:56:04 -04001289 rem = self.GetRemote()
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001290 rev = rem.ToLocal(self.revisionExpr)
1291
David Pursehouse8a68ff92012-09-24 12:15:13 +09001292 if all_refs is not None and rev in all_refs:
1293 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001294
1295 try:
1296 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1297 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001298 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1299 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001300
Raman Tenneti6a872c92021-01-14 19:17:50 -08001301 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001302 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001303 self.upstream = self.revisionExpr
1304
Raman Tenneti6a872c92021-01-14 19:17:50 -08001305 self.revisionId = revisionId
1306
Martin Kellye4e94d22017-03-21 16:05:12 -07001307 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308 """Perform only the local IO portion of the sync process.
1309 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001311 if not os.path.exists(self.gitdir):
1312 syncbuf.fail(self,
1313 'Cannot checkout %s due to missing network sync; Run '
1314 '`repo sync -n %s` first.' %
1315 (self.name, self.name))
1316 return
1317
Martin Kellye4e94d22017-03-21 16:05:12 -07001318 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001319 all_refs = self.bare_ref.all
1320 self.CleanPublishedCache(all_refs)
1321 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001322
Mike Frysinger0458faa2021-03-10 23:35:44 -05001323 # Special case the root of the repo client checkout. Make sure it doesn't
1324 # contain files being checked out to dirs we don't allow.
1325 if self.relpath == '.':
1326 PROTECTED_PATHS = {'.repo'}
1327 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1328 bad_paths = paths & PROTECTED_PATHS
1329 if bad_paths:
1330 syncbuf.fail(self,
1331 'Refusing to checkout project that writes to protected '
1332 'paths: %s' % (', '.join(bad_paths),))
1333 return
1334
David Pursehouse1d947b32012-10-25 12:23:11 +09001335 def _doff():
1336 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001337 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001338
Martin Kellye4e94d22017-03-21 16:05:12 -07001339 def _dosubmodules():
1340 self._SyncSubmodules(quiet=True)
1341
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001342 head = self.work_git.GetHead()
1343 if head.startswith(R_HEADS):
1344 branch = head[len(R_HEADS):]
1345 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001346 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001347 except KeyError:
1348 head = None
1349 else:
1350 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001351
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001352 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353 # Currently on a detached HEAD. The user is assumed to
1354 # not have any local modifications worth worrying about.
1355 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001356 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001357 syncbuf.fail(self, _PriorSyncFailedError())
1358 return
1359
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001360 if head == revid:
1361 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001362 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001363 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001364 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001365 # The copy/linkfile config may have changed.
1366 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001367 return
1368 else:
1369 lost = self._revlist(not_rev(revid), HEAD)
1370 if lost:
1371 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001372
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001374 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001375 if submodules:
1376 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001377 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001378 syncbuf.fail(self, e)
1379 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001380 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001381 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001382
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001383 if head == revid:
1384 # No changes; don't do anything further.
1385 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001386 # The copy/linkfile config may have changed.
1387 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001388 return
1389
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001390 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001392 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001394 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001395 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001396 syncbuf.info(self,
1397 "leaving %s; does not track upstream",
1398 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001400 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001401 if submodules:
1402 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001403 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001404 syncbuf.fail(self, e)
1405 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001406 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001407 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001408
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001409 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001410
1411 # See if we can perform a fast forward merge. This can happen if our
1412 # branch isn't in the exact same state as we last published.
1413 try:
1414 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1415 # Skip the published logic.
1416 pub = False
1417 except GitError:
1418 pub = self.WasPublished(branch.name, all_refs)
1419
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001420 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001421 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001422 if not_merged:
1423 if upstream_gain:
1424 # The user has published this branch and some of those
1425 # commits are not yet merged upstream. We do not want
1426 # to rewrite the published commits so we punt.
1427 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001428 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001429 "branch %s is published (but not merged) and is now "
1430 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001431 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001432 elif pub == head:
1433 # All published commits are merged, and thus we are a
1434 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001435 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001436 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001437 if submodules:
1438 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001439 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001440
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001441 # Examine the local commits not in the remote. Find the
1442 # last one attributed to this user, if any.
1443 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001444 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001445 last_mine = None
1446 cnt_mine = 0
1447 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001448 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001449 if committer_email == self.UserEmail:
1450 last_mine = commit_id
1451 cnt_mine += 1
1452
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001453 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001454 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001455
1456 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001457 syncbuf.fail(self, _DirtyError())
1458 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001459
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001460 # If the upstream switched on us, warn the user.
1461 #
1462 if branch.merge != self.revisionExpr:
1463 if branch.merge and self.revisionExpr:
1464 syncbuf.info(self,
1465 'manifest switched %s...%s',
1466 branch.merge,
1467 self.revisionExpr)
1468 elif branch.merge:
1469 syncbuf.info(self,
1470 'manifest no longer tracks %s',
1471 branch.merge)
1472
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001473 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001474 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001475 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001476 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001477 syncbuf.info(self,
1478 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001479 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001480
Mike Frysingerdede5642022-07-10 04:56:04 -04001481 branch.remote = self.GetRemote()
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001482 if not ID_RE.match(self.revisionExpr):
1483 # in case of manifest sync the revisionExpr might be a SHA1
1484 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001485 if not branch.merge.startswith('refs/'):
1486 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001487 branch.Save()
1488
Mike Pontillod3153822012-02-28 11:53:24 -08001489 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001490 def _docopyandlink():
1491 self._CopyAndLinkFiles()
1492
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001493 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001494 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001495 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001496 if submodules:
1497 syncbuf.later2(self, _dosubmodules)
1498 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001499 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001500 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001501 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001502 if submodules:
1503 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001504 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001505 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001506 syncbuf.fail(self, e)
1507 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001508 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001509 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001510 if submodules:
1511 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001512
Mike Frysingere6a202f2019-08-02 15:57:57 -04001513 def AddCopyFile(self, src, dest, topdir):
1514 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001515
Mike Frysingere6a202f2019-08-02 15:57:57 -04001516 No filesystem changes occur here. Actual copying happens later on.
1517
1518 Paths should have basic validation run on them before being queued.
1519 Further checking will be handled when the actual copy happens.
1520 """
1521 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1522
1523 def AddLinkFile(self, src, dest, topdir):
1524 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1525
1526 No filesystem changes occur here. Actual linking happens later on.
1527
1528 Paths should have basic validation run on them before being queued.
1529 Further checking will be handled when the actual link happens.
1530 """
1531 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001532
James W. Mills24c13082012-04-12 15:04:13 -05001533 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001534 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001535
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001536 def DownloadPatchSet(self, change_id, patch_id):
1537 """Download a single patch set of a single change to FETCH_HEAD.
1538 """
Mike Frysingerdede5642022-07-10 04:56:04 -04001539 remote = self.GetRemote()
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001540
1541 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001542 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001543 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001544 if GitCommand(self, cmd, bare=True).Wait() != 0:
1545 return None
1546 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001547 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001548 change_id,
1549 patch_id,
1550 self.bare_git.rev_parse('FETCH_HEAD'))
1551
Mike Frysingerc0d18662020-02-19 19:19:18 -05001552 def DeleteWorktree(self, quiet=False, force=False):
1553 """Delete the source checkout and any other housekeeping tasks.
1554
1555 This currently leaves behind the internal .repo/ cache state. This helps
1556 when switching branches or manifest changes get reverted as we don't have
1557 to redownload all the git objects. But we should do some GC at some point.
1558
1559 Args:
1560 quiet: Whether to hide normal messages.
1561 force: Always delete tree even if dirty.
1562
1563 Returns:
1564 True if the worktree was completely cleaned out.
1565 """
1566 if self.IsDirty():
1567 if force:
1568 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
LaMont Jones8501d462022-06-22 19:21:15 +00001569 (self.RelPath(local=False),), file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001570 else:
1571 print('error: %s: Cannot remove project: uncommitted changes are '
LaMont Jones8501d462022-06-22 19:21:15 +00001572 'present.\n' % (self.RelPath(local=False),), file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001573 return False
1574
1575 if not quiet:
LaMont Jones8501d462022-06-22 19:21:15 +00001576 print('%s: Deleting obsolete checkout.' % (self.RelPath(local=False),))
Mike Frysingerc0d18662020-02-19 19:19:18 -05001577
1578 # Unlock and delink from the main worktree. We don't use git's worktree
1579 # remove because it will recursively delete projects -- we handle that
1580 # ourselves below. https://crbug.com/git/48
1581 if self.use_git_worktrees:
1582 needle = platform_utils.realpath(self.gitdir)
1583 # Find the git worktree commondir under .repo/worktrees/.
1584 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1585 assert output.startswith('worktree '), output
1586 commondir = output[9:]
1587 # Walk each of the git worktrees to see where they point.
1588 configs = os.path.join(commondir, 'worktrees')
1589 for name in os.listdir(configs):
1590 gitdir = os.path.join(configs, name, 'gitdir')
1591 with open(gitdir) as fp:
1592 relpath = fp.read().strip()
1593 # Resolve the checkout path and see if it matches this project.
1594 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1595 if fullpath == needle:
1596 platform_utils.rmtree(os.path.join(configs, name))
1597
1598 # Delete the .git directory first, so we're less likely to have a partially
1599 # working git repository around. There shouldn't be any git projects here,
1600 # so rmtree works.
1601
1602 # Try to remove plain files first in case of git worktrees. If this fails
1603 # for any reason, we'll fall back to rmtree, and that'll display errors if
1604 # it can't remove things either.
1605 try:
1606 platform_utils.remove(self.gitdir)
1607 except OSError:
1608 pass
1609 try:
1610 platform_utils.rmtree(self.gitdir)
1611 except OSError as e:
1612 if e.errno != errno.ENOENT:
1613 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1614 print('error: %s: Failed to delete obsolete checkout; remove manually, '
LaMont Jones8501d462022-06-22 19:21:15 +00001615 'then run `repo sync -l`.' % (self.RelPath(local=False),),
1616 file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001617 return False
1618
1619 # Delete everything under the worktree, except for directories that contain
1620 # another git project.
1621 dirs_to_remove = []
1622 failed = False
1623 for root, dirs, files in platform_utils.walk(self.worktree):
1624 for f in files:
1625 path = os.path.join(root, f)
1626 try:
1627 platform_utils.remove(path)
1628 except OSError as e:
1629 if e.errno != errno.ENOENT:
1630 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1631 failed = True
1632 dirs[:] = [d for d in dirs
1633 if not os.path.lexists(os.path.join(root, d, '.git'))]
1634 dirs_to_remove += [os.path.join(root, d) for d in dirs
1635 if os.path.join(root, d) not in dirs_to_remove]
1636 for d in reversed(dirs_to_remove):
1637 if platform_utils.islink(d):
1638 try:
1639 platform_utils.remove(d)
1640 except OSError as e:
1641 if e.errno != errno.ENOENT:
1642 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1643 failed = True
1644 elif not platform_utils.listdir(d):
1645 try:
1646 platform_utils.rmdir(d)
1647 except OSError as e:
1648 if e.errno != errno.ENOENT:
1649 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1650 failed = True
1651 if failed:
LaMont Jones8501d462022-06-22 19:21:15 +00001652 print('error: %s: Failed to delete obsolete checkout.' % (self.RelPath(local=False),),
Mike Frysingerc0d18662020-02-19 19:19:18 -05001653 file=sys.stderr)
1654 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1655 return False
1656
1657 # Try deleting parent dirs if they are empty.
1658 path = self.worktree
1659 while path != self.manifest.topdir:
1660 try:
1661 platform_utils.rmdir(path)
1662 except OSError as e:
1663 if e.errno != errno.ENOENT:
1664 break
1665 path = os.path.dirname(path)
1666
1667 return True
1668
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001669# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001670 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001671 """Create a new branch off the manifest's revision.
1672 """
Simran Basib9a1b732015-08-20 12:19:28 -07001673 if not branch_merge:
1674 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001675 head = self.work_git.GetHead()
1676 if head == (R_HEADS + name):
1677 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001678
David Pursehouse8a68ff92012-09-24 12:15:13 +09001679 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001680 if R_HEADS + name in all_refs:
Mike Frysingerad1b7bd2022-08-18 09:17:09 -04001681 return GitCommand(self, ['checkout', '-q', name, '--']).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001682
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001683 branch = self.GetBranch(name)
Mike Frysingerdede5642022-07-10 04:56:04 -04001684 branch.remote = self.GetRemote()
Simran Basib9a1b732015-08-20 12:19:28 -07001685 branch.merge = branch_merge
1686 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1687 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001688
1689 if revision is None:
1690 revid = self.GetRevisionId(all_refs)
1691 else:
1692 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001693
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001694 if head.startswith(R_HEADS):
1695 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001696 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001697 except KeyError:
1698 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001699 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001700 ref = R_HEADS + name
1701 self.work_git.update_ref(ref, revid)
1702 self.work_git.symbolic_ref(HEAD, ref)
1703 branch.Save()
1704 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001705
Mike Frysingerad1b7bd2022-08-18 09:17:09 -04001706 if GitCommand(self, ['checkout', '-q', '-b', branch.name, revid]).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001707 branch.Save()
1708 return True
1709 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001710
Wink Saville02d79452009-04-10 13:01:24 -07001711 def CheckoutBranch(self, name):
1712 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001713
1714 Args:
1715 name: The name of the branch to checkout.
1716
1717 Returns:
1718 True if the checkout succeeded; False if it didn't; None if the branch
1719 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001720 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001721 rev = R_HEADS + name
1722 head = self.work_git.GetHead()
1723 if head == rev:
1724 # Already on the branch
1725 #
1726 return True
Wink Saville02d79452009-04-10 13:01:24 -07001727
David Pursehouse8a68ff92012-09-24 12:15:13 +09001728 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001729 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001730 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001731 except KeyError:
1732 # Branch does not exist in this project
1733 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001734 return None
Wink Saville02d79452009-04-10 13:01:24 -07001735
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001736 if head.startswith(R_HEADS):
1737 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001738 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001739 except KeyError:
1740 head = None
1741
1742 if head == revid:
1743 # Same revision; just update HEAD to point to the new
1744 # target branch, but otherwise take no other action.
1745 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001746 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1747 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001748 return True
1749
1750 return GitCommand(self,
1751 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001752 capture_stdout=True,
1753 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001754
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001755 def AbandonBranch(self, name):
1756 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001757
1758 Args:
1759 name: The name of the branch to abandon.
1760
1761 Returns:
1762 True if the abandon succeeded; False if it didn't; None if the branch
1763 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001764 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001765 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001766 all_refs = self.bare_ref.all
1767 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001768 # Doesn't exist
1769 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001770
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001771 head = self.work_git.GetHead()
1772 if head == rev:
1773 # We can't destroy the branch while we are sitting
1774 # on it. Switch to a detached HEAD.
1775 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001776 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001777
David Pursehouse8a68ff92012-09-24 12:15:13 +09001778 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001779 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001780 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001781 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001782 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001783
1784 return GitCommand(self,
1785 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001786 capture_stdout=True,
1787 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001788
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001789 def PruneHeads(self):
1790 """Prune any topic branches already merged into upstream.
1791 """
1792 cb = self.CurrentBranch
1793 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001794 left = self._allrefs
1795 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001796 if name.startswith(R_HEADS):
1797 name = name[len(R_HEADS):]
1798 if cb is None or name != cb:
1799 kill.append(name)
1800
Mike Frysingera3794e92021-03-11 23:24:01 -05001801 # Minor optimization: If there's nothing to prune, then don't try to read
1802 # any project state.
1803 if not kill and not cb:
1804 return []
1805
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001806 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001807 if cb is not None \
1808 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001809 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001810 self.work_git.DetachHead(HEAD)
1811 kill.append(cb)
1812
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001813 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001814 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001815
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001816 try:
1817 self.bare_git.DetachHead(rev)
1818
1819 b = ['branch', '-d']
1820 b.extend(kill)
1821 b = GitCommand(self, b, bare=True,
1822 capture_stdout=True,
1823 capture_stderr=True)
1824 b.Wait()
1825 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001826 if ID_RE.match(old):
1827 self.bare_git.DetachHead(old)
1828 else:
1829 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001830 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001831
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001832 for branch in kill:
1833 if (R_HEADS + branch) not in left:
1834 self.CleanPublishedCache()
1835 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001836
1837 if cb and cb not in kill:
1838 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001839 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001840
1841 kept = []
1842 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001843 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001844 branch = self.GetBranch(branch)
1845 base = branch.LocalMerge
1846 if not base:
1847 base = rev
1848 kept.append(ReviewableBranch(self, branch, base))
1849 return kept
1850
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001851# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001852 def GetRegisteredSubprojects(self):
1853 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001854
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001855 def rec(subprojects):
1856 if not subprojects:
1857 return
1858 result.extend(subprojects)
1859 for p in subprojects:
1860 rec(p.subprojects)
1861 rec(self.subprojects)
1862 return result
1863
1864 def _GetSubmodules(self):
1865 # Unfortunately we cannot call `git submodule status --recursive` here
1866 # because the working tree might not exist yet, and it cannot be used
1867 # without a working tree in its current implementation.
1868
1869 def get_submodules(gitdir, rev):
1870 # Parse .gitmodules for submodule sub_paths and sub_urls
1871 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1872 if not sub_paths:
1873 return []
1874 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1875 # revision of submodule repository
1876 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1877 submodules = []
1878 for sub_path, sub_url in zip(sub_paths, sub_urls):
1879 try:
1880 sub_rev = sub_revs[sub_path]
1881 except KeyError:
1882 # Ignore non-exist submodules
1883 continue
1884 submodules.append((sub_rev, sub_path, sub_url))
1885 return submodules
1886
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001887 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1888 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001889
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001890 def parse_gitmodules(gitdir, rev):
1891 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1892 try:
Anthony King7bdac712014-07-16 12:56:40 +01001893 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1894 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001895 except GitError:
1896 return [], []
1897 if p.Wait() != 0:
1898 return [], []
1899
1900 gitmodules_lines = []
1901 fd, temp_gitmodules_path = tempfile.mkstemp()
1902 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001903 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001904 os.close(fd)
1905 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001906 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1907 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001908 if p.Wait() != 0:
1909 return [], []
1910 gitmodules_lines = p.stdout.split('\n')
1911 except GitError:
1912 return [], []
1913 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001914 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001915
1916 names = set()
1917 paths = {}
1918 urls = {}
1919 for line in gitmodules_lines:
1920 if not line:
1921 continue
1922 m = re_path.match(line)
1923 if m:
1924 names.add(m.group(1))
1925 paths[m.group(1)] = m.group(2)
1926 continue
1927 m = re_url.match(line)
1928 if m:
1929 names.add(m.group(1))
1930 urls[m.group(1)] = m.group(2)
1931 continue
1932 names = sorted(names)
1933 return ([paths.get(name, '') for name in names],
1934 [urls.get(name, '') for name in names])
1935
1936 def git_ls_tree(gitdir, rev, paths):
1937 cmd = ['ls-tree', rev, '--']
1938 cmd.extend(paths)
1939 try:
Anthony King7bdac712014-07-16 12:56:40 +01001940 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1941 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001942 except GitError:
1943 return []
1944 if p.Wait() != 0:
1945 return []
1946 objects = {}
1947 for line in p.stdout.split('\n'):
1948 if not line.strip():
1949 continue
1950 object_rev, object_path = line.split()[2:4]
1951 objects[object_path] = object_rev
1952 return objects
1953
1954 try:
1955 rev = self.GetRevisionId()
1956 except GitError:
1957 return []
1958 return get_submodules(self.gitdir, rev)
1959
1960 def GetDerivedSubprojects(self):
1961 result = []
1962 if not self.Exists:
1963 # If git repo does not exist yet, querying its submodules will
1964 # mess up its states; so return here.
1965 return result
1966 for rev, path, url in self._GetSubmodules():
1967 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001968 relpath, worktree, gitdir, objdir = \
1969 self.manifest.GetSubprojectPaths(self, name, path)
1970 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001971 if project:
1972 result.extend(project.GetDerivedSubprojects())
1973 continue
David James8d201162013-10-11 17:03:19 -07001974
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001975 if url.startswith('..'):
1976 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001977 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001978 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001979 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001980 review=self.remote.review,
1981 revision=self.remote.revision)
1982 subproject = Project(manifest=self.manifest,
1983 name=name,
1984 remote=remote,
1985 gitdir=gitdir,
1986 objdir=objdir,
1987 worktree=worktree,
1988 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001989 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001990 revisionId=rev,
1991 rebase=self.rebase,
1992 groups=self.groups,
1993 sync_c=self.sync_c,
1994 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001995 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001996 parent=self,
1997 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001998 result.append(subproject)
1999 result.extend(subproject.GetDerivedSubprojects())
2000 return result
2001
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002002# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002003 def EnableRepositoryExtension(self, key, value='true', version=1):
2004 """Enable git repository extension |key| with |value|.
2005
2006 Args:
2007 key: The extension to enabled. Omit the "extensions." prefix.
2008 value: The value to use for the extension.
2009 version: The minimum git repository version needed.
2010 """
2011 # Make sure the git repo version is new enough already.
2012 found_version = self.config.GetInt('core.repositoryFormatVersion')
2013 if found_version is None:
2014 found_version = 0
2015 if found_version < version:
2016 self.config.SetString('core.repositoryFormatVersion', str(version))
2017
2018 # Enable the extension!
2019 self.config.SetString('extensions.%s' % (key,), value)
2020
Mike Frysinger50a81de2020-09-06 15:51:21 -04002021 def ResolveRemoteHead(self, name=None):
2022 """Find out what the default branch (HEAD) points to.
2023
2024 Normally this points to refs/heads/master, but projects are moving to main.
2025 Support whatever the server uses rather than hardcoding "master" ourselves.
2026 """
2027 if name is None:
2028 name = self.remote.name
2029
2030 # The output will look like (NB: tabs are separators):
2031 # ref: refs/heads/master HEAD
2032 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2033 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2034
2035 for line in output.splitlines():
2036 lhs, rhs = line.split('\t', 1)
2037 if rhs == 'HEAD' and lhs.startswith('ref:'):
2038 return lhs[4:].strip()
2039
2040 return None
2041
Zac Livingstone4332262017-06-16 08:56:09 -06002042 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002043 try:
2044 # if revision (sha or tag) is not present then following function
2045 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002046 self.bare_git.rev_list('-1', '--missing=allow-any',
2047 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00002048 if self.upstream:
Mike Frysingerdede5642022-07-10 04:56:04 -04002049 rev = self.GetRemote().ToLocal(self.upstream)
Xin Li0e776a52021-06-29 21:42:34 +00002050 self.bare_git.rev_list('-1', '--missing=allow-any',
2051 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002052 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002053 return True
2054 except GitError:
2055 # There is no such persistent revision. We have to fetch it.
2056 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002057
Julien Campergue335f5ef2013-10-16 11:02:35 +02002058 def _FetchArchive(self, tarpath, cwd=None):
2059 cmd = ['archive', '-v', '-o', tarpath]
2060 cmd.append('--remote=%s' % self.remote.url)
LaMont Jones8501d462022-06-22 19:21:15 +00002061 cmd.append('--prefix=%s/' % self.RelPath(local=False))
Julien Campergue335f5ef2013-10-16 11:02:35 +02002062 cmd.append(self.revisionExpr)
2063
2064 command = GitCommand(self, cmd, cwd=cwd,
2065 capture_stdout=True,
2066 capture_stderr=True)
2067
2068 if command.Wait() != 0:
2069 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2070
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002071 def _RemoteFetch(self, name=None,
2072 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002073 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002074 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002075 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002076 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002077 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002078 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002079 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002080 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002081 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002082 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002083 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002084 clone_filter=None,
2085 retry_fetches=2,
2086 retry_sleep_initial_sec=4.0,
2087 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002088 is_sha1 = False
2089 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002090 # The depth should not be used when fetching to a mirror because
2091 # it will result in a shallow repository that cannot be cloned or
2092 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002093 # The repo project should also never be synced with partial depth.
2094 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2095 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002096
Shawn Pearce69e04d82014-01-29 12:48:54 -08002097 if depth:
2098 current_branch_only = True
2099
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002100 if ID_RE.match(self.revisionExpr) is not None:
2101 is_sha1 = True
2102
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002103 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002104 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002105 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002106 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002107 elif self.upstream and self.upstream.startswith(R_TAGS):
2108 # This is a tag and its commit id should never change.
2109 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002110
2111 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002112 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002113 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002114 print('Skipped fetching project %s (already have persistent ref)'
2115 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002116 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002117 if is_sha1 and not depth:
2118 # When syncing a specific commit and --depth is not set:
2119 # * if upstream is explicitly specified and is not a sha1, fetch only
2120 # upstream as users expect only upstream to be fetch.
2121 # Note: The commit might not be in upstream in which case the sync
2122 # will fail.
2123 # * otherwise, fetch all branches to make sure we end up with the
2124 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002125 if self.upstream:
2126 current_branch_only = not ID_RE.match(self.upstream)
2127 else:
2128 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002129
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002130 if not name:
2131 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002132
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002133 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002134 if not remote.PreConnectFetch(ssh_proxy):
2135 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002136
Shawn O. Pearce88443382010-10-08 10:02:09 +02002137 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002138 if alt_dir and 'objects' == os.path.basename(alt_dir):
2139 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002140 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002141
David Pursehouse8a68ff92012-09-24 12:15:13 +09002142 all_refs = self.bare_ref.all
2143 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002144 tmp = set()
2145
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302146 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002147 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002148 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002149 all_refs[r] = ref_id
2150 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002151 continue
2152
David Pursehouse8a68ff92012-09-24 12:15:13 +09002153 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002154 continue
2155
David Pursehouse8a68ff92012-09-24 12:15:13 +09002156 r = 'refs/_alt/%s' % ref_id
2157 all_refs[r] = ref_id
2158 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002159 tmp.add(r)
2160
heping3d7bbc92017-04-12 19:51:47 +08002161 tmp_packed_lines = []
2162 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002163
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302164 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002165 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002166 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002167 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002168 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002169
heping3d7bbc92017-04-12 19:51:47 +08002170 tmp_packed = ''.join(tmp_packed_lines)
2171 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002172 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002173 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002174 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002175
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002176 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002177
Xin Li745be2e2019-06-03 11:24:30 -07002178 if clone_filter:
2179 git_require((2, 19, 0), fail=True, msg='partial clones')
2180 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002181 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002182
Conley Owensf97e8382015-01-21 11:12:46 -08002183 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002184 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002185 else:
2186 # If this repo has shallow objects, then we don't know which refs have
2187 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2188 # do this with projects that don't have shallow objects, since it is less
2189 # efficient.
2190 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2191 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002192
Mike Frysinger4847e052020-02-22 00:07:35 -05002193 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002194 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002195 if not quiet and sys.stdout.isatty():
2196 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002197 if not self.worktree:
2198 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002199 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002200
Mike Frysingere57f1142019-03-18 21:27:54 -04002201 if force_sync:
2202 cmd.append('--force')
2203
David Pursehouse74cfd272015-10-14 10:50:15 +09002204 if prune:
2205 cmd.append('--prune')
2206
LaMont Jonesff6b1da2022-06-01 21:03:34 +00002207 # Always pass something for --recurse-submodules, git with GIT_DIR behaves
2208 # incorrectly when not given `--recurse-submodules=no`. (b/218891912)
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002209 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002210
Kuang-che Wu6856f982019-11-25 12:37:55 +08002211 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002212 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002213 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002214 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002215 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002216 spec.append('tag')
2217 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002218
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302219 if self.manifest.IsMirror and not current_branch_only:
2220 branch = None
2221 else:
2222 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002223 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002224 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002225 # Shallow checkout of a specific commit, fetch from that commit and not
2226 # the heads only as the commit might be deeper in the history.
2227 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002228 if self.upstream:
2229 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002230 else:
2231 if is_sha1:
2232 branch = self.upstream
2233 if branch is not None and branch.strip():
2234 if not branch.startswith('refs/'):
2235 branch = R_HEADS + branch
2236 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2237
2238 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2239 # whole repo.
2240 if self.manifest.IsMirror and not spec:
2241 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2242
2243 # If using depth then we should not get all the tags since they may
2244 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002245 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002246 cmd.append('--no-tags')
2247 else:
2248 cmd.append('--tags')
2249 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2250
Conley Owens80b87fe2014-05-09 17:13:44 -07002251 cmd.extend(spec)
2252
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002253 # At least one retry minimum due to git remote prune.
2254 retry_fetches = max(retry_fetches, 2)
2255 retry_cur_sleep = retry_sleep_initial_sec
2256 ok = prune_tried = False
2257 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002258 gitcmd = GitCommand(
2259 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2260 ssh_proxy=ssh_proxy,
2261 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002262 if gitcmd.stdout and not quiet and output_redir:
2263 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002264 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002265 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002266 ok = True
2267 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002268
2269 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002270 elif (gitcmd.stdout and
2271 'error:' in gitcmd.stdout and
2272 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002273 # Fallthru to sleep+retry logic at the bottom.
2274 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002275
Mike Frysinger6823bc22021-04-15 02:06:28 -04002276 # Try to prune remote branches once in case there are conflicts.
2277 # For example, if the remote had refs/heads/upstream, but deleted that and
2278 # now has refs/heads/upstream/foo.
2279 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002280 'error:' in gitcmd.stdout and
2281 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002282 not prune_tried):
2283 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002284 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002285 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002286 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002287 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002288 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002289 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002290 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002291 continue
Brian Harring14a66742012-09-28 20:21:57 -07002292 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002293 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2294 # in sha1 mode, we just tried sync'ing from the upstream field; it
2295 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002296 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002297 elif ret < 0:
2298 # Git died with a signal, exit immediately
2299 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002300
2301 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002302 if not verbose and gitcmd.stdout:
2303 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002304 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002305 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2306 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002307 time.sleep(retry_cur_sleep)
2308 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2309 MAXIMUM_RETRY_SLEEP_SEC)
2310 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2311 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002312
2313 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002314 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002315 if old_packed != '':
2316 _lwrite(packed_refs, old_packed)
2317 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002318 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002319 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002320
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002321 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002322 # We just synced the upstream given branch; verify we
2323 # got what we wanted, else trigger a second run of all
2324 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002325 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002326 # Sync the current branch only with depth set to None.
2327 # We always pass depth=None down to avoid infinite recursion.
2328 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002329 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002330 current_branch_only=current_branch_only and depth,
2331 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002332 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002333
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002334 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002335
Mike Frysingere50b6a72020-02-19 01:45:48 -05002336 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002337 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002338 return False
2339
Mike Frysingerdede5642022-07-10 04:56:04 -04002340 remote = self.GetRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002341 bundle_url = remote.url + '/clone.bundle'
2342 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002343 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2344 'persistent-http',
2345 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002346 return False
2347
2348 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2349 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2350
2351 exist_dst = os.path.exists(bundle_dst)
2352 exist_tmp = os.path.exists(bundle_tmp)
2353
2354 if not initial and not exist_dst and not exist_tmp:
2355 return False
2356
2357 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002358 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2359 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002360 if not exist_dst:
2361 return False
2362
2363 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002364 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002365 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002366 if not quiet and sys.stdout.isatty():
2367 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002368 if not self.worktree:
2369 cmd.append('--update-head-ok')
2370 cmd.append(bundle_dst)
2371 for f in remote.fetch:
2372 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002373 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002374
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002375 ok = GitCommand(
2376 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002377 platform_utils.remove(bundle_dst, missing_ok=True)
2378 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002379 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002380
Mike Frysingere50b6a72020-02-19 01:45:48 -05002381 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002382 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002383
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002384 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002385 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002386 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002387 if os.path.exists(tmpPath):
2388 size = os.stat(tmpPath).st_size
2389 if size >= 1024:
2390 cmd += ['--continue-at', '%d' % (size,)]
2391 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002392 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002393 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002394 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002395 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002396 if proxy:
2397 cmd += ['--proxy', proxy]
2398 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2399 cmd += ['--proxy', os.environ['http_proxy']]
2400 if srcUrl.startswith('persistent-https'):
2401 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2402 elif srcUrl.startswith('persistent-http'):
2403 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002404 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002405
Dave Borowitz137d0132015-01-02 11:12:54 -08002406 if IsTrace():
2407 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002408 if verbose:
2409 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2410 stdout = None if verbose else subprocess.PIPE
2411 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002412 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002413 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002414 except OSError:
2415 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002416
Mike Frysingere50b6a72020-02-19 01:45:48 -05002417 (output, _) = proc.communicate()
2418 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002419
Dave Borowitz137d0132015-01-02 11:12:54 -08002420 if curlret == 22:
2421 # From curl man page:
2422 # 22: HTTP page not retrieved. The requested url was not found or
2423 # returned another error with the HTTP error code being 400 or above.
2424 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002425 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002426 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2427 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002428 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002429 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002430 elif curlret and not verbose and output:
2431 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002432
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002433 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002434 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002435 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002436 return True
2437 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002438 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002439 return False
2440 else:
2441 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002442
Kris Giesingc8d882a2014-12-23 13:02:32 -08002443 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002444 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002445 with open(path, 'rb') as f:
2446 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002447 return True
2448 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002449 if not quiet:
2450 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002451 return False
2452 except OSError:
2453 return False
2454
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002455 def _Checkout(self, rev, quiet=False):
2456 cmd = ['checkout']
2457 if quiet:
2458 cmd.append('-q')
2459 cmd.append(rev)
2460 cmd.append('--')
2461 if GitCommand(self, cmd).Wait() != 0:
2462 if self._allrefs:
2463 raise GitError('%s checkout %s ' % (self.name, rev))
2464
Mike Frysinger915fda12020-03-22 12:15:20 -04002465 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002466 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002467 if ffonly:
2468 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002469 if record_origin:
2470 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002471 cmd.append(rev)
2472 cmd.append('--')
2473 if GitCommand(self, cmd).Wait() != 0:
2474 if self._allrefs:
2475 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2476
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302477 def _LsRemote(self, refs):
2478 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302479 p = GitCommand(self, cmd, capture_stdout=True)
2480 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002481 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302482 return None
2483
Anthony King7bdac712014-07-16 12:56:40 +01002484 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002485 cmd = ['revert']
2486 cmd.append('--no-edit')
2487 cmd.append(rev)
2488 cmd.append('--')
2489 if GitCommand(self, cmd).Wait() != 0:
2490 if self._allrefs:
2491 raise GitError('%s revert %s ' % (self.name, rev))
2492
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002493 def _ResetHard(self, rev, quiet=True):
2494 cmd = ['reset', '--hard']
2495 if quiet:
2496 cmd.append('-q')
2497 cmd.append(rev)
2498 if GitCommand(self, cmd).Wait() != 0:
2499 raise GitError('%s reset --hard %s ' % (self.name, rev))
2500
Martin Kellye4e94d22017-03-21 16:05:12 -07002501 def _SyncSubmodules(self, quiet=True):
2502 cmd = ['submodule', 'update', '--init', '--recursive']
2503 if quiet:
2504 cmd.append('-q')
2505 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002506 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002507
Anthony King7bdac712014-07-16 12:56:40 +01002508 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002509 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002510 if onto is not None:
2511 cmd.extend(['--onto', onto])
2512 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002513 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002514 raise GitError('%s rebase %s ' % (self.name, upstream))
2515
Pierre Tardy3d125942012-05-04 12:18:12 +02002516 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002517 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002518 if ffonly:
2519 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002520 if GitCommand(self, cmd).Wait() != 0:
2521 raise GitError('%s merge %s ' % (self.name, head))
2522
David Pursehousee8ace262020-02-13 12:41:15 +09002523 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002524 init_git_dir = not os.path.exists(self.gitdir)
2525 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002526 try:
2527 # Initialize the bare repository, which contains all of the objects.
2528 if init_obj_dir:
2529 os.makedirs(self.objdir)
2530 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002531
Mike Frysinger333c0a42021-11-15 12:39:00 -05002532 self._UpdateHooks(quiet=quiet)
2533
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002534 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002535 # Enable per-worktree config file support if possible. This is more a
2536 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002537 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002538 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002539
Kevin Degib1a07b82015-07-27 13:33:43 -06002540 # If we have a separate directory to hold refs, initialize it as well.
2541 if self.objdir != self.gitdir:
2542 if init_git_dir:
2543 os.makedirs(self.gitdir)
2544
2545 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002546 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002547 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002548 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002549 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002550 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002551 print("Retrying clone after deleting %s" %
2552 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002553 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002554 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2555 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002556 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002557 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002558 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2559 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002560 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002561 raise e
2562 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002563
Kevin Degi384b3c52014-10-16 16:02:58 -06002564 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002565 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002566 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002567
LaMont Jonescc879a92021-11-18 22:40:18 +00002568 def _expanded_ref_dirs():
2569 """Iterate through the possible git reference directory paths."""
2570 name = self.name + '.git'
2571 yield mirror_git or os.path.join(ref_dir, name)
2572 for prefix in '', self.remote.name:
2573 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2574 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002575
LaMont Jonescc879a92021-11-18 22:40:18 +00002576 if ref_dir or mirror_git:
2577 found_ref_dir = None
2578 for path in _expanded_ref_dirs():
2579 if os.path.exists(path):
2580 found_ref_dir = path
2581 break
2582 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002583
Kevin Degib1a07b82015-07-27 13:33:43 -06002584 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002585 if not os.path.isabs(ref_dir):
2586 # The alternate directory is relative to the object database.
2587 ref_dir = os.path.relpath(ref_dir,
2588 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002589 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002590 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002591
Kevin Degib1a07b82015-07-27 13:33:43 -06002592 m = self.manifest.manifestProject.config
2593 for key in ['user.name', 'user.email']:
2594 if m.Has(key, include_defaults=False):
2595 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002596 if not self.manifest.EnableGitLfs:
2597 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2598 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002599 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002600 except Exception:
2601 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002602 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002603 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002604 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002605 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002606
David Pursehousee8ace262020-02-13 12:41:15 +09002607 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002608 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002609 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002610
David Pursehousee8ace262020-02-13 12:41:15 +09002611 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002612 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002613 if not os.path.exists(hooks):
2614 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002615
2616 # Delete sample hooks. They're noise.
2617 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002618 try:
2619 platform_utils.remove(hook, missing_ok=True)
2620 except PermissionError:
2621 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002622
Jonathan Nieder93719792015-03-17 11:29:58 -07002623 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002624 name = os.path.basename(stock_hook)
2625
Victor Boivie65e0f352011-04-18 11:23:29 +02002626 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002627 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002628 # Don't install a Gerrit Code Review hook if this
2629 # project does not appear to use it for reviews.
2630 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002631 # Since the manifest project is one of those, but also
2632 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002633 continue
2634
2635 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002636 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002637 continue
2638 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002639 # If the files are the same, we'll leave it alone. We create symlinks
2640 # below by default but fallback to hardlinks if the OS blocks them.
2641 # So if we're here, it's probably because we made a hardlink below.
2642 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002643 if not quiet:
2644 _warn("%s: Not replacing locally modified %s hook",
LaMont Jones8501d462022-06-22 19:21:15 +00002645 self.RelPath(local=False), name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002646 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002647 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002648 platform_utils.symlink(
2649 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002650 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002651 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002652 try:
2653 os.link(stock_hook, dst)
2654 except OSError:
2655 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002656 else:
2657 raise
2658
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002659 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002660 if self.remote.url:
Mike Frysingerdede5642022-07-10 04:56:04 -04002661 remote = self.GetRemote()
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002662 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002663 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002664 remote.review = self.remote.review
2665 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002666
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002667 if self.worktree:
2668 remote.ResetFetch(mirror=False)
2669 else:
2670 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002671 remote.Save()
2672
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002673 def _InitMRef(self):
Mike Frysinger790f4ce2020-12-07 22:04:55 -05002674 """Initialize the pseudo m/<manifest branch> ref."""
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002675 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002676 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002677 # Set up the m/ space to point to the worktree-specific ref space.
2678 # We'll update the worktree-specific ref space on each checkout.
2679 ref = R_M + self.manifest.branch
2680 if not self.bare_ref.symref(ref):
2681 self.bare_git.symbolic_ref(
2682 '-m', 'redirecting to worktree scope',
2683 ref, R_WORKTREE_M + self.manifest.branch)
2684
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002685 # We can't update this ref with git worktrees until it exists.
2686 # We'll wait until the initial checkout to set it.
2687 if not os.path.exists(self.worktree):
2688 return
2689
2690 base = R_WORKTREE_M
2691 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002692
2693 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002694 else:
2695 base = R_M
2696 active_git = self.bare_git
2697
2698 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002699
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002700 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002701 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002702
Remy Böhmer1469c282020-12-15 18:49:02 +01002703 def _InitAnyMRef(self, ref, active_git, detach=False):
Mike Frysinger790f4ce2020-12-07 22:04:55 -05002704 """Initialize |ref| in |active_git| to the value in the manifest.
2705
2706 This points |ref| to the <project> setting in the manifest.
2707
2708 Args:
2709 ref: The branch to update.
2710 active_git: The git repository to make updates in.
2711 detach: Whether to update target of symbolic refs, or overwrite the ref
2712 directly (and thus make it non-symbolic).
2713 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002714 cur = self.bare_ref.symref(ref)
2715
2716 if self.revisionId:
2717 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2718 msg = 'manifest set to %s' % self.revisionId
2719 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002720 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002721 else:
Mike Frysingerdede5642022-07-10 04:56:04 -04002722 remote = self.GetRemote()
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002723 dst = remote.ToLocal(self.revisionExpr)
2724 if cur != dst:
2725 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002726 if detach:
2727 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2728 else:
2729 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002730
Mike Frysingerc72bd842021-11-14 03:58:00 -05002731 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002732 # Git worktrees don't use symlinks to share at all.
2733 if self.use_git_worktrees:
2734 return
2735
Mike Frysingerd33dce02021-12-20 18:16:33 -05002736 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002737 # Try to self-heal a bit in simple cases.
2738 dst_path = os.path.join(destdir, name)
2739 src_path = os.path.join(srcdir, name)
2740
Mike Frysingered4f2112020-02-11 23:06:29 -05002741 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002742 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002743 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002744 # Fail if the links are pointing to the wrong place
2745 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002746 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002747 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002748 'work tree. If you\'re comfortable with the '
2749 'possibility of losing the work tree\'s git metadata,'
2750 ' use `repo sync --force-sync {0}` to '
LaMont Jones8501d462022-06-22 19:21:15 +00002751 'proceed.'.format(self.RelPath(local=False)))
Kevin Degi384b3c52014-10-16 16:02:58 -06002752
Mike Frysingerc72bd842021-11-14 03:58:00 -05002753 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002754 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2755
2756 Args:
2757 gitdir: The bare git repository. Must already be initialized.
2758 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002759 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2760 This saves you the effort of initializing |dotgit| yourself.
2761 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002762 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002763 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002764
2765 to_copy = []
2766 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002767 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002768
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002769 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002770 for name in set(to_copy).union(to_symlink):
2771 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002772 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002773 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002774
Kevin Degi384b3c52014-10-16 16:02:58 -06002775 if os.path.lexists(dst):
2776 continue
David James8d201162013-10-11 17:03:19 -07002777
2778 # If the source dir doesn't exist, create an empty dir.
2779 if name in symlink_dirs and not os.path.lexists(src):
2780 os.makedirs(src)
2781
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002782 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002783 platform_utils.symlink(
2784 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002785 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002786 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002787 shutil.copytree(src, dst)
2788 elif os.path.isfile(src):
2789 shutil.copy(src, dst)
2790
David James8d201162013-10-11 17:03:19 -07002791 except OSError as e:
2792 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002793 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002794 else:
2795 raise
2796
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002797 def _InitGitWorktree(self):
2798 """Init the project using git worktrees."""
2799 self.bare_git.worktree('prune')
2800 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2801 self.worktree, self.GetRevisionId())
2802
2803 # Rewrite the internal state files to use relative paths between the
2804 # checkouts & worktrees.
2805 dotgit = os.path.join(self.worktree, '.git')
2806 with open(dotgit, 'r') as fp:
2807 # Figure out the checkout->worktree path.
2808 setting = fp.read()
2809 assert setting.startswith('gitdir:')
2810 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002811 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2812 # of file permissions. Delete it and recreate it from scratch to avoid.
2813 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002814 # Use relative path from checkout->worktree & maintain Unix line endings
2815 # on all OS's to match git behavior.
2816 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002817 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2818 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002819 # Use relative path from worktree->checkout & maintain Unix line endings
2820 # on all OS's to match git behavior.
2821 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002822 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2823
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002824 self._InitMRef()
2825
Martin Kellye4e94d22017-03-21 16:05:12 -07002826 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002827 """Setup the worktree .git path.
2828
2829 This is the user-visible path like src/foo/.git/.
2830
2831 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2832 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2833
2834 Older checkouts had .git/ directories. If we see that, migrate it.
2835
2836 This also handles changes in the manifest. Maybe this project was backed
2837 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2838 the path we point to under .repo/projects/ to match.
2839 """
2840 dotgit = os.path.join(self.worktree, '.git')
2841
2842 # If using an old layout style (a directory), migrate it.
2843 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2844 self._MigrateOldWorkTreeGitDir(dotgit)
2845
2846 init_dotgit = not os.path.exists(dotgit)
2847 if self.use_git_worktrees:
2848 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002849 self._InitGitWorktree()
2850 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002851 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002852 if not init_dotgit:
2853 # See if the project has changed.
2854 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2855 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002856
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002857 if init_dotgit or not os.path.exists(dotgit):
2858 os.makedirs(self.worktree, exist_ok=True)
2859 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002860
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002861 if init_dotgit:
2862 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002863
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002864 # Finish checking out the worktree.
2865 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2866 if GitCommand(self, cmd).Wait() != 0:
2867 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002868
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002869 if submodules:
2870 self._SyncSubmodules(quiet=True)
2871 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002872
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002873 @classmethod
2874 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2875 """Migrate the old worktree .git/ dir style to a symlink.
2876
2877 This logic specifically only uses state from |dotgit| to figure out where to
2878 move content and not |self|. This way if the backing project also changed
2879 places, we only do the .git/ dir to .git symlink migration here. The path
2880 updates will happen independently.
2881 """
2882 # Figure out where in .repo/projects/ it's pointing to.
2883 if not os.path.islink(os.path.join(dotgit, 'refs')):
2884 raise GitError(f'{dotgit}: unsupported checkout state')
2885 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2886
2887 # Remove known symlink paths that exist in .repo/projects/.
2888 KNOWN_LINKS = {
2889 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2890 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2891 }
2892 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2893 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002894 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2895 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002896 }
2897
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002898 # First see if we'd succeed before starting the migration.
2899 unknown_paths = []
2900 for name in platform_utils.listdir(dotgit):
2901 # Ignore all temporary/backup names. These are common with vim & emacs.
2902 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2903 continue
2904
2905 dotgit_path = os.path.join(dotgit, name)
2906 if name in KNOWN_LINKS:
2907 if not platform_utils.islink(dotgit_path):
2908 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2909 else:
2910 gitdir_path = os.path.join(gitdir, name)
2911 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2912 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2913 if unknown_paths:
2914 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2915
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002916 # Now walk the paths and sync the .git/ to .repo/projects/.
2917 for name in platform_utils.listdir(dotgit):
2918 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002919
2920 # Ignore all temporary/backup names. These are common with vim & emacs.
2921 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2922 platform_utils.remove(dotgit_path)
2923 elif name in KNOWN_LINKS:
2924 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002925 else:
2926 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002927 platform_utils.remove(gitdir_path, missing_ok=True)
2928 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002929
2930 # Now that the dir should be empty, clear it out, and symlink it over.
2931 platform_utils.rmdir(dotgit)
2932 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002933
Renaud Paquay788e9622017-01-27 11:41:12 -08002934 def _get_symlink_error_message(self):
2935 if platform_utils.isWindows():
2936 return ('Unable to create symbolic link. Please re-run the command as '
2937 'Administrator, or see '
2938 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2939 'for other options.')
2940 return 'filesystem must support symlinks'
2941
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002942 def _revlist(self, *args, **kw):
2943 a = []
2944 a.extend(args)
2945 a.append('--')
2946 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002947
2948 @property
2949 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002950 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002951
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002952 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002953 """Get logs between two revisions of this project."""
2954 comp = '..'
2955 if rev1:
2956 revs = [rev1]
2957 if rev2:
2958 revs.extend([comp, rev2])
2959 cmd = ['log', ''.join(revs)]
2960 out = DiffColoring(self.config)
2961 if out.is_on and color:
2962 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002963 if pretty_format is not None:
2964 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002965 if oneline:
2966 cmd.append('--oneline')
2967
2968 try:
2969 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2970 if log.Wait() == 0:
2971 return log.stdout
2972 except GitError:
2973 # worktree may not exist if groups changed for example. In that case,
2974 # try in gitdir instead.
2975 if not os.path.exists(self.worktree):
2976 return self.bare_git.log(*cmd[1:])
2977 else:
2978 raise
2979 return None
2980
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002981 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2982 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002983 """Get the list of logs from this revision to given revisionId"""
2984 logs = {}
2985 selfId = self.GetRevisionId(self._allrefs)
2986 toId = toProject.GetRevisionId(toProject._allrefs)
2987
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002988 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2989 pretty_format=pretty_format)
2990 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2991 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002992 return logs
2993
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002994 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002995
David James8d201162013-10-11 17:03:19 -07002996 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002997 self._project = project
2998 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002999 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003000
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09003001 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
3002 def __getstate__(self):
3003 return (self._project, self._bare, self._gitdir)
3004
3005 def __setstate__(self, state):
3006 self._project, self._bare, self._gitdir = state
3007
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003008 def LsOthers(self):
3009 p = GitCommand(self._project,
3010 ['ls-files',
3011 '-z',
3012 '--others',
3013 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003014 bare=False,
David James8d201162013-10-11 17:03:19 -07003015 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003016 capture_stdout=True,
3017 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003018 if p.Wait() == 0:
3019 out = p.stdout
3020 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003021 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003022 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003023 return []
3024
3025 def DiffZ(self, name, *args):
3026 cmd = [name]
3027 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003028 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003029 cmd.extend(args)
3030 p = GitCommand(self._project,
3031 cmd,
David James8d201162013-10-11 17:03:19 -07003032 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003033 bare=False,
3034 capture_stdout=True,
3035 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05003036 p.Wait()
3037 r = {}
3038 out = p.stdout
3039 if out:
3040 out = iter(out[:-1].split('\0'))
3041 while out:
3042 try:
3043 info = next(out)
3044 path = next(out)
3045 except StopIteration:
3046 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003047
Mike Frysinger84230002021-02-16 17:08:35 -05003048 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003049
Mike Frysinger84230002021-02-16 17:08:35 -05003050 def __init__(self, path, omode, nmode, oid, nid, state):
3051 self.path = path
3052 self.src_path = None
3053 self.old_mode = omode
3054 self.new_mode = nmode
3055 self.old_id = oid
3056 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003057
Mike Frysinger84230002021-02-16 17:08:35 -05003058 if len(state) == 1:
3059 self.status = state
3060 self.level = None
3061 else:
3062 self.status = state[:1]
3063 self.level = state[1:]
3064 while self.level.startswith('0'):
3065 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003066
Mike Frysinger84230002021-02-16 17:08:35 -05003067 info = info[1:].split(' ')
3068 info = _Info(path, *info)
3069 if info.status in ('R', 'C'):
3070 info.src_path = info.path
3071 info.path = next(out)
3072 r[info.path] = info
3073 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003074
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003075 def GetDotgitPath(self, subpath=None):
3076 """Return the full path to the .git dir.
3077
3078 As a convenience, append |subpath| if provided.
3079 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003080 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003081 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003082 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003083 dotgit = os.path.join(self._project.worktree, '.git')
3084 if os.path.isfile(dotgit):
3085 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3086 with open(dotgit) as fp:
3087 setting = fp.read()
3088 assert setting.startswith('gitdir:')
3089 gitdir = setting.split(':', 1)[1].strip()
3090 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3091
3092 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3093
3094 def GetHead(self):
3095 """Return the ref that HEAD points to."""
3096 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003097 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003098 with open(path) as fd:
3099 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003100 except IOError as e:
3101 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003102 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303103 line = line.decode()
3104 except AttributeError:
3105 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003106 if line.startswith('ref: '):
3107 return line[5:-1]
3108 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003109
3110 def SetHead(self, ref, message=None):
3111 cmdv = []
3112 if message is not None:
3113 cmdv.extend(['-m', message])
3114 cmdv.append(HEAD)
3115 cmdv.append(ref)
3116 self.symbolic_ref(*cmdv)
3117
3118 def DetachHead(self, new, message=None):
3119 cmdv = ['--no-deref']
3120 if message is not None:
3121 cmdv.extend(['-m', message])
3122 cmdv.append(HEAD)
3123 cmdv.append(new)
3124 self.update_ref(*cmdv)
3125
3126 def UpdateRef(self, name, new, old=None,
3127 message=None,
3128 detach=False):
3129 cmdv = []
3130 if message is not None:
3131 cmdv.extend(['-m', message])
3132 if detach:
3133 cmdv.append('--no-deref')
3134 cmdv.append(name)
3135 cmdv.append(new)
3136 if old is not None:
3137 cmdv.append(old)
3138 self.update_ref(*cmdv)
3139
3140 def DeleteRef(self, name, old=None):
3141 if not old:
3142 old = self.rev_parse(name)
3143 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003144 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003145
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003146 def rev_list(self, *args, **kw):
3147 if 'format' in kw:
3148 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3149 else:
3150 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003151 cmdv.extend(args)
3152 p = GitCommand(self._project,
3153 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003154 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003155 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003156 capture_stdout=True,
3157 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003158 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003159 raise GitError('%s rev-list %s: %s' %
3160 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003161 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003162
3163 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003164 """Allow arbitrary git commands using pythonic syntax.
3165
3166 This allows you to do things like:
3167 git_obj.rev_parse('HEAD')
3168
3169 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3170 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003171 Any other positional arguments will be passed to the git command, and the
3172 following keyword arguments are supported:
3173 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003174
3175 Args:
3176 name: The name of the git command to call. Any '_' characters will
3177 be replaced with '-'.
3178
3179 Returns:
3180 A callable object that will try to call git with the named command.
3181 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003182 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003183
Dave Borowitz091f8932012-10-23 17:01:04 -07003184 def runner(*args, **kwargs):
3185 cmdv = []
3186 config = kwargs.pop('config', None)
3187 for k in kwargs:
3188 raise TypeError('%s() got an unexpected keyword argument %r'
3189 % (name, k))
3190 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303191 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003192 cmdv.append('-c')
3193 cmdv.append('%s=%s' % (k, v))
3194 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003195 cmdv.extend(args)
3196 p = GitCommand(self._project,
3197 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003198 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003199 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003200 capture_stdout=True,
3201 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003202 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003203 raise GitError('%s %s: %s' %
3204 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003205 r = p.stdout
3206 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3207 return r[:-1]
3208 return r
3209 return runner
3210
3211
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003212class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003213
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003214 def __str__(self):
3215 return 'prior sync failed; rebase still in progress'
3216
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003217
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003218class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003219
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003220 def __str__(self):
3221 return 'contains uncommitted changes'
3222
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003223
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003224class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003225
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003226 def __init__(self, project, text):
3227 self.project = project
3228 self.text = text
3229
3230 def Print(self, syncbuf):
LaMont Jones8501d462022-06-22 19:21:15 +00003231 syncbuf.out.info('%s/: %s', self.project.RelPath(local=False), self.text)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003232 syncbuf.out.nl()
3233
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003234
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003235class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003236
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003237 def __init__(self, project, why):
3238 self.project = project
3239 self.why = why
3240
3241 def Print(self, syncbuf):
3242 syncbuf.out.fail('error: %s/: %s',
LaMont Jones8501d462022-06-22 19:21:15 +00003243 self.project.RelPath(local=False),
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003244 str(self.why))
3245 syncbuf.out.nl()
3246
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003247
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003248class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003249
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003250 def __init__(self, project, action):
3251 self.project = project
3252 self.action = action
3253
3254 def Run(self, syncbuf):
3255 out = syncbuf.out
LaMont Jones8501d462022-06-22 19:21:15 +00003256 out.project('project %s/', self.project.RelPath(local=False))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003257 out.nl()
3258 try:
3259 self.action()
3260 out.nl()
3261 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003262 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003263 out.nl()
3264 return False
3265
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003266
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003267class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003268
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003269 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003270 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003271 self.project = self.printer('header', attr='bold')
3272 self.info = self.printer('info')
3273 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003274
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003275
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003276class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003277
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003278 def __init__(self, config, detach_head=False):
3279 self._messages = []
3280 self._failures = []
3281 self._later_queue1 = []
3282 self._later_queue2 = []
3283
3284 self.out = _SyncColoring(config)
3285 self.out.redirect(sys.stderr)
3286
3287 self.detach_head = detach_head
3288 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003289 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003290
3291 def info(self, project, fmt, *args):
3292 self._messages.append(_InfoMessage(project, fmt % args))
3293
3294 def fail(self, project, err=None):
3295 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003296 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003297
3298 def later1(self, project, what):
3299 self._later_queue1.append(_Later(project, what))
3300
3301 def later2(self, project, what):
3302 self._later_queue2.append(_Later(project, what))
3303
3304 def Finish(self):
3305 self._PrintMessages()
3306 self._RunLater()
3307 self._PrintMessages()
3308 return self.clean
3309
David Rileye0684ad2017-04-05 00:02:59 -07003310 def Recently(self):
3311 recent_clean = self.recent_clean
3312 self.recent_clean = True
3313 return recent_clean
3314
3315 def _MarkUnclean(self):
3316 self.clean = False
3317 self.recent_clean = False
3318
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003319 def _RunLater(self):
3320 for q in ['_later_queue1', '_later_queue2']:
3321 if not self._RunQueue(q):
3322 return
3323
3324 def _RunQueue(self, queue):
3325 for m in getattr(self, queue):
3326 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003327 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003328 return False
3329 setattr(self, queue, [])
3330 return True
3331
3332 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003333 if self._messages or self._failures:
3334 if os.isatty(2):
3335 self.out.write(progress.CSI_ERASE_LINE)
3336 self.out.write('\r')
3337
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003338 for m in self._messages:
3339 m.Print(self)
3340 for m in self._failures:
3341 m.Print(self)
3342
3343 self._messages = []
3344 self._failures = []
3345
3346
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003347class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003348 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003349
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003350 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003351 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003352 manifest=manifest,
3353 name=name,
3354 gitdir=gitdir,
3355 objdir=gitdir,
3356 worktree=worktree,
3357 remote=RemoteSpec('origin'),
3358 relpath='.repo/%s' % name,
3359 revisionExpr='refs/heads/master',
3360 revisionId=None,
3361 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003362
3363 def PreSync(self):
3364 if self.Exists:
3365 cb = self.CurrentBranch
3366 if cb:
3367 base = self.GetBranch(cb).merge
3368 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003369 self.revisionExpr = base
3370 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003371
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003372 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003373 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003374 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003375 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003376 return False
3377
David Pursehouse8a68ff92012-09-24 12:15:13 +09003378 all_refs = self.bare_ref.all
3379 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003380 head = self.work_git.GetHead()
3381 if head.startswith(R_HEADS):
3382 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003383 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003384 except KeyError:
3385 head = None
3386
3387 if revid == head:
3388 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003389 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003390 return True
3391 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003392
3393
3394class RepoProject(MetaProject):
3395 """The MetaProject for repo itself."""
3396
3397 @property
3398 def LastFetch(self):
3399 try:
3400 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3401 return os.path.getmtime(fh)
3402 except OSError:
3403 return 0
3404
3405class ManifestProject(MetaProject):
3406 """The MetaProject for manifests."""
3407
3408 def MetaBranchSwitch(self, submodules=False):
3409 """Prepare for manifest branch switch."""
3410
3411 # detach and delete manifest branch, allowing a new
3412 # branch to take over
3413 syncbuf = SyncBuffer(self.config, detach_head=True)
3414 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3415 syncbuf.Finish()
3416
3417 return GitCommand(self,
3418 ['update-ref', '-d', 'refs/heads/default'],
3419 capture_stdout=True,
3420 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003421
3422 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003423 def standalone_manifest_url(self):
3424 """The URL of the standalone manifest, or None."""
LaMont Jones55ee3042022-04-06 17:10:21 +00003425 return self.config.GetString('manifest.standalone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003426
3427 @property
3428 def manifest_groups(self):
3429 """The manifest groups string."""
3430 return self.config.GetString('manifest.groups')
3431
3432 @property
3433 def reference(self):
3434 """The --reference for this manifest."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003435 return self.config.GetString('repo.reference')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003436
3437 @property
3438 def dissociate(self):
3439 """Whether to dissociate."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003440 return self.config.GetBoolean('repo.dissociate')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003441
3442 @property
3443 def archive(self):
3444 """Whether we use archive."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003445 return self.config.GetBoolean('repo.archive')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003446
3447 @property
3448 def mirror(self):
3449 """Whether we use mirror."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003450 return self.config.GetBoolean('repo.mirror')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003451
3452 @property
3453 def use_worktree(self):
3454 """Whether we use worktree."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003455 return self.config.GetBoolean('repo.worktree')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003456
3457 @property
3458 def clone_bundle(self):
3459 """Whether we use clone_bundle."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003460 return self.config.GetBoolean('repo.clonebundle')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003461
3462 @property
3463 def submodules(self):
3464 """Whether we use submodules."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003465 return self.config.GetBoolean('repo.submodules')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003466
3467 @property
3468 def git_lfs(self):
3469 """Whether we use git_lfs."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003470 return self.config.GetBoolean('repo.git-lfs')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003471
3472 @property
3473 def use_superproject(self):
3474 """Whether we use superproject."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003475 return self.config.GetBoolean('repo.superproject')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003476
3477 @property
3478 def partial_clone(self):
3479 """Whether this is a partial clone."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003480 return self.config.GetBoolean('repo.partialclone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003481
3482 @property
3483 def depth(self):
3484 """Partial clone depth."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003485 return self.config.GetString('repo.depth')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003486
3487 @property
3488 def clone_filter(self):
3489 """The clone filter."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003490 return self.config.GetString('repo.clonefilter')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003491
3492 @property
3493 def partial_clone_exclude(self):
3494 """Partial clone exclude string"""
LaMont Jones4ada0432022-04-14 15:10:43 +00003495 return self.config.GetBoolean('repo.partialcloneexclude')
3496
3497 @property
3498 def manifest_platform(self):
3499 """The --platform argument from `repo init`."""
3500 return self.config.GetString('manifest.platform')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003501
3502 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003503 def _platform_name(self):
3504 """Return the name of the platform."""
3505 return platform.system().lower()
3506
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003507 def SyncWithPossibleInit(self, submanifest, verbose=False,
3508 current_branch_only=False, tags='', git_event_log=None):
3509 """Sync a manifestProject, possibly for the first time.
3510
3511 Call Sync() with arguments from the most recent `repo init`. If this is a
3512 new sub manifest, then inherit options from the parent's manifestProject.
3513
3514 This is used by subcmds.Sync() to do an initial download of new sub
3515 manifests.
3516
3517 Args:
3518 submanifest: an XmlSubmanifest, the submanifest to re-sync.
3519 verbose: a boolean, whether to show all output, rather than only errors.
3520 current_branch_only: a boolean, whether to only fetch the current manifest
3521 branch from the server.
3522 tags: a boolean, whether to fetch tags.
3523 git_event_log: an EventLog, for git tracing.
3524 """
3525 # TODO(lamontjones): when refactoring sync (and init?) consider how to
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003526 # better get the init options that we should use for new submanifests that
3527 # are added when syncing an existing workspace.
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003528 git_event_log = git_event_log or EventLog()
3529 spec = submanifest.ToSubmanifestSpec()
3530 # Use the init options from the existing manifestProject, or the parent if
3531 # it doesn't exist.
3532 #
3533 # Today, we only support changing manifest_groups on the sub-manifest, with
3534 # no supported-for-the-user way to change the other arguments from those
3535 # specified by the outermost manifest.
3536 #
3537 # TODO(lamontjones): determine which of these should come from the outermost
3538 # manifest and which should come from the parent manifest.
3539 mp = self if self.Exists else submanifest.parent.manifestProject
3540 return self.Sync(
3541 manifest_url=spec.manifestUrl,
3542 manifest_branch=spec.revision,
3543 standalone_manifest=mp.standalone_manifest_url,
3544 groups=mp.manifest_groups,
3545 platform=mp.manifest_platform,
3546 mirror=mp.mirror,
3547 dissociate=mp.dissociate,
3548 reference=mp.reference,
3549 worktree=mp.use_worktree,
3550 submodules=mp.submodules,
3551 archive=mp.archive,
3552 partial_clone=mp.partial_clone,
3553 clone_filter=mp.clone_filter,
3554 partial_clone_exclude=mp.partial_clone_exclude,
3555 clone_bundle=mp.clone_bundle,
3556 git_lfs=mp.git_lfs,
3557 use_superproject=mp.use_superproject,
3558 verbose=verbose,
3559 current_branch_only=current_branch_only,
3560 tags=tags,
3561 depth=mp.depth,
3562 git_event_log=git_event_log,
3563 manifest_name=spec.manifestName,
3564 this_manifest_only=True,
3565 outer_manifest=False,
3566 )
3567
LaMont Jones9b03f152022-03-29 23:01:18 +00003568 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003569 standalone_manifest=False, groups='', mirror=False, reference='',
3570 dissociate=False, worktree=False, submodules=False, archive=False,
3571 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003572 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3573 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jones55ee3042022-04-06 17:10:21 +00003574 git_event_log=None, platform='', manifest_name='default.xml',
3575 tags='', this_manifest_only=False, outer_manifest=True):
LaMont Jones9b03f152022-03-29 23:01:18 +00003576 """Sync the manifest and all submanifests.
3577
3578 Args:
3579 manifest_url: a string, the URL of the manifest project.
3580 manifest_branch: a string, the manifest branch to use.
3581 standalone_manifest: a boolean, whether to store the manifest as a static
3582 file.
3583 groups: a string, restricts the checkout to projects with the specified
3584 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003585 mirror: a boolean, whether to create a mirror of the remote repository.
3586 reference: a string, location of a repo instance to use as a reference.
3587 dissociate: a boolean, whether to dissociate from reference mirrors after
3588 clone.
3589 worktree: a boolean, whether to use git-worktree to manage projects.
3590 submodules: a boolean, whether sync submodules associated with the
3591 manifest project.
3592 archive: a boolean, whether to checkout each project as an archive. See
3593 git-archive.
3594 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003595 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003596 clone_filter: a string, filter to use with partial_clone.
3597 partial_clone_exclude : a string, comma-delimeted list of project namess
3598 to exclude from partial clone.
3599 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3600 git_lfs: a boolean, whether to enable git LFS support.
3601 use_superproject: a boolean, whether to use the manifest superproject to
3602 sync projects.
3603 verbose: a boolean, whether to show all output, rather than only errors.
3604 current_branch_only: a boolean, whether to only fetch the current manifest
3605 branch from the server.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003606 platform: a string, restrict the checkout to projects with the specified
3607 platform group.
LaMont Jones55ee3042022-04-06 17:10:21 +00003608 git_event_log: an EventLog, for git tracing.
LaMont Jones4ada0432022-04-14 15:10:43 +00003609 tags: a boolean, whether to fetch tags.
LaMont Jones409407a2022-04-05 21:21:56 +00003610 manifest_name: a string, the name of the manifest file to use.
3611 this_manifest_only: a boolean, whether to only operate on the current sub
3612 manifest.
3613 outer_manifest: a boolean, whether to start at the outermost manifest.
LaMont Jones9b03f152022-03-29 23:01:18 +00003614
3615 Returns:
3616 a boolean, whether the sync was successful.
3617 """
3618 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3619
LaMont Jones501733c2022-04-20 16:42:32 +00003620 groups = groups or self.manifest.GetDefaultGroupsStr(with_platform=False)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003621 platform = platform or 'auto'
LaMont Jones55ee3042022-04-06 17:10:21 +00003622 git_event_log = git_event_log or EventLog()
LaMont Jones409407a2022-04-05 21:21:56 +00003623 if outer_manifest and self.manifest.is_submanifest:
3624 # In a multi-manifest checkout, use the outer manifest unless we are told
3625 # not to.
3626 return self.client.outer_manifest.manifestProject.Sync(
3627 manifest_url=manifest_url,
3628 manifest_branch=manifest_branch,
3629 standalone_manifest=standalone_manifest,
3630 groups=groups,
3631 platform=platform,
3632 mirror=mirror,
3633 dissociate=dissociate,
3634 reference=reference,
3635 worktree=worktree,
3636 submodules=submodules,
3637 archive=archive,
3638 partial_clone=partial_clone,
3639 clone_filter=clone_filter,
3640 partial_clone_exclude=partial_clone_exclude,
3641 clone_bundle=clone_bundle,
3642 git_lfs=git_lfs,
3643 use_superproject=use_superproject,
3644 verbose=verbose,
3645 current_branch_only=current_branch_only,
3646 tags=tags,
3647 depth=depth,
LaMont Jones55ee3042022-04-06 17:10:21 +00003648 git_event_log=git_event_log,
LaMont Jones409407a2022-04-05 21:21:56 +00003649 manifest_name=manifest_name,
3650 this_manifest_only=this_manifest_only,
3651 outer_manifest=False)
3652
LaMont Jones9b03f152022-03-29 23:01:18 +00003653 # If repo has already been initialized, we take -u with the absence of
3654 # --standalone-manifest to mean "transition to a standard repo set up",
3655 # which necessitates starting fresh.
3656 # If --standalone-manifest is set, we always tear everything down and start
3657 # anew.
3658 if self.Exists:
3659 was_standalone_manifest = self.config.GetString('manifest.standalone')
3660 if was_standalone_manifest and not manifest_url:
3661 print('fatal: repo was initialized with a standlone manifest, '
3662 'cannot be re-initialized without --manifest-url/-u')
3663 return False
3664
3665 if standalone_manifest or (was_standalone_manifest and manifest_url):
3666 self.config.ClearCache()
3667 if self.gitdir and os.path.exists(self.gitdir):
3668 platform_utils.rmtree(self.gitdir)
3669 if self.worktree and os.path.exists(self.worktree):
3670 platform_utils.rmtree(self.worktree)
3671
3672 is_new = not self.Exists
3673 if is_new:
3674 if not manifest_url:
3675 print('fatal: manifest url is required.', file=sys.stderr)
3676 return False
3677
LaMont Jones409407a2022-04-05 21:21:56 +00003678 if verbose:
LaMont Jones9b03f152022-03-29 23:01:18 +00003679 print('Downloading manifest from %s' %
3680 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3681 file=sys.stderr)
3682
3683 # The manifest project object doesn't keep track of the path on the
3684 # server where this git is located, so let's save that here.
3685 mirrored_manifest_git = None
3686 if reference:
3687 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3688 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3689 if not mirrored_manifest_git.endswith(".git"):
3690 mirrored_manifest_git += ".git"
3691 if not os.path.exists(mirrored_manifest_git):
3692 mirrored_manifest_git = os.path.join(reference,
3693 '.repo/manifests.git')
3694
3695 self._InitGitDir(mirror_git=mirrored_manifest_git)
3696
3697 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3698 # still do much of the manifests.git set up, but will avoid actual syncs to
3699 # a remote.
3700 if standalone_manifest:
3701 self.config.SetString('manifest.standalone', manifest_url)
3702 elif not manifest_url and not manifest_branch:
3703 # If -u is set and --standalone-manifest is not, then we're not in
3704 # standalone mode. Otherwise, use config to infer what we were in the last
3705 # init.
3706 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3707 if not standalone_manifest:
3708 self.config.SetString('manifest.standalone', None)
3709
3710 self._ConfigureDepth(depth)
3711
3712 # Set the remote URL before the remote branch as we might need it below.
3713 if manifest_url:
Mike Frysingerdede5642022-07-10 04:56:04 -04003714 r = self.GetRemote()
LaMont Jones9b03f152022-03-29 23:01:18 +00003715 r.url = manifest_url
3716 r.ResetFetch()
3717 r.Save()
3718
3719 if not standalone_manifest:
3720 if manifest_branch:
3721 if manifest_branch == 'HEAD':
3722 manifest_branch = self.ResolveRemoteHead()
3723 if manifest_branch is None:
3724 print('fatal: unable to resolve HEAD', file=sys.stderr)
3725 return False
3726 self.revisionExpr = manifest_branch
3727 else:
3728 if is_new:
3729 default_branch = self.ResolveRemoteHead()
3730 if default_branch is None:
3731 # If the remote doesn't have HEAD configured, default to master.
3732 default_branch = 'refs/heads/master'
3733 self.revisionExpr = default_branch
3734 else:
3735 self.PreSync()
3736
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003737 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003738 all_platforms = ['linux', 'darwin', 'windows']
3739 platformize = lambda x: 'platform-' + x
3740 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003741 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003742 groups.append(platformize(self._platform_name))
3743 elif platform == 'all':
3744 groups.extend(map(platformize, all_platforms))
3745 elif platform in all_platforms:
3746 groups.append(platformize(platform))
3747 elif platform != 'none':
3748 print('fatal: invalid platform flag', file=sys.stderr)
3749 return False
LaMont Jones4ada0432022-04-14 15:10:43 +00003750 self.config.SetString('manifest.platform', platform)
LaMont Jones9b03f152022-03-29 23:01:18 +00003751
3752 groups = [x for x in groups if x]
3753 groupstr = ','.join(groups)
3754 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3755 groupstr = None
3756 self.config.SetString('manifest.groups', groupstr)
3757
3758 if reference:
3759 self.config.SetString('repo.reference', reference)
3760
3761 if dissociate:
3762 self.config.SetBoolean('repo.dissociate', dissociate)
3763
3764 if worktree:
3765 if mirror:
3766 print('fatal: --mirror and --worktree are incompatible',
3767 file=sys.stderr)
3768 return False
3769 if submodules:
3770 print('fatal: --submodules and --worktree are incompatible',
3771 file=sys.stderr)
3772 return False
3773 self.config.SetBoolean('repo.worktree', worktree)
3774 if is_new:
3775 self.use_git_worktrees = True
3776 print('warning: --worktree is experimental!', file=sys.stderr)
3777
3778 if archive:
3779 if is_new:
3780 self.config.SetBoolean('repo.archive', archive)
3781 else:
3782 print('fatal: --archive is only supported when initializing a new '
3783 'workspace.', file=sys.stderr)
3784 print('Either delete the .repo folder in this workspace, or initialize '
3785 'in another location.', file=sys.stderr)
3786 return False
3787
3788 if mirror:
3789 if is_new:
3790 self.config.SetBoolean('repo.mirror', mirror)
3791 else:
3792 print('fatal: --mirror is only supported when initializing a new '
3793 'workspace.', file=sys.stderr)
3794 print('Either delete the .repo folder in this workspace, or initialize '
3795 'in another location.', file=sys.stderr)
3796 return False
3797
3798 if partial_clone is not None:
3799 if mirror:
3800 print('fatal: --mirror and --partial-clone are mutually exclusive',
3801 file=sys.stderr)
3802 return False
3803 self.config.SetBoolean('repo.partialclone', partial_clone)
3804 if clone_filter:
3805 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jones55ee3042022-04-06 17:10:21 +00003806 elif self.partial_clone:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003807 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003808 else:
3809 clone_filter = None
3810
3811 if partial_clone_exclude is not None:
3812 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3813
3814 if clone_bundle is None:
3815 clone_bundle = False if partial_clone else True
3816 else:
3817 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3818
3819 if submodules:
3820 self.config.SetBoolean('repo.submodules', submodules)
3821
3822 if git_lfs is not None:
3823 if git_lfs:
3824 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3825
3826 self.config.SetBoolean('repo.git-lfs', git_lfs)
3827 if not is_new:
3828 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3829 ' Existing projects will require manual updates.\n', file=sys.stderr)
3830
3831 if use_superproject is not None:
3832 self.config.SetBoolean('repo.superproject', use_superproject)
3833
LaMont Jones0165e202022-04-27 17:34:42 +00003834 if not standalone_manifest:
3835 if not self.Sync_NetworkHalf(
3836 is_new=is_new, quiet=not verbose, verbose=verbose,
3837 clone_bundle=clone_bundle, current_branch_only=current_branch_only,
3838 tags=tags, submodules=submodules, clone_filter=clone_filter,
3839 partial_clone_exclude=self.manifest.PartialCloneExclude):
Mike Frysingerdede5642022-07-10 04:56:04 -04003840 r = self.GetRemote()
LaMont Jones0165e202022-04-27 17:34:42 +00003841 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
LaMont Jonesaf8fb132022-08-31 20:21:25 +00003842
3843 # Better delete the manifest git dir if we created it; otherwise next
3844 # time (when user fixes problems) we won't go through the "is_new" logic.
3845 if is_new:
3846 platform_utils.rmtree(self.gitdir)
LaMont Jones9b03f152022-03-29 23:01:18 +00003847 return False
3848
LaMont Jones0165e202022-04-27 17:34:42 +00003849 if manifest_branch:
3850 self.MetaBranchSwitch(submodules=submodules)
3851
3852 syncbuf = SyncBuffer(self.config)
3853 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3854 syncbuf.Finish()
3855
3856 if is_new or self.CurrentBranch is None:
3857 if not self.StartBranch('default'):
3858 print('fatal: cannot create default in manifest', file=sys.stderr)
3859 return False
3860
3861 if not manifest_name:
3862 print('fatal: manifest name (-m) is required.', file=sys.stderr)
3863 return False
3864
3865 elif is_new:
3866 # This is a new standalone manifest.
3867 manifest_name = 'default.xml'
3868 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3869 dest = os.path.join(self.worktree, manifest_name)
3870 os.makedirs(os.path.dirname(dest), exist_ok=True)
3871 with open(dest, 'wb') as f:
3872 f.write(manifest_data)
LaMont Jones409407a2022-04-05 21:21:56 +00003873
3874 try:
3875 self.manifest.Link(manifest_name)
3876 except ManifestParseError as e:
3877 print("fatal: manifest '%s' not available" % manifest_name,
3878 file=sys.stderr)
3879 print('fatal: %s' % str(e), file=sys.stderr)
3880 return False
3881
LaMont Jones55ee3042022-04-06 17:10:21 +00003882 if not this_manifest_only:
3883 for submanifest in self.manifest.submanifests.values():
LaMont Jonesb90a4222022-04-14 15:00:09 +00003884 spec = submanifest.ToSubmanifestSpec()
LaMont Jones55ee3042022-04-06 17:10:21 +00003885 submanifest.repo_client.manifestProject.Sync(
3886 manifest_url=spec.manifestUrl,
3887 manifest_branch=spec.revision,
3888 standalone_manifest=standalone_manifest,
3889 groups=self.manifest_groups,
3890 platform=platform,
3891 mirror=mirror,
3892 dissociate=dissociate,
3893 reference=reference,
3894 worktree=worktree,
3895 submodules=submodules,
3896 archive=archive,
3897 partial_clone=partial_clone,
3898 clone_filter=clone_filter,
3899 partial_clone_exclude=partial_clone_exclude,
3900 clone_bundle=clone_bundle,
3901 git_lfs=git_lfs,
3902 use_superproject=use_superproject,
3903 verbose=verbose,
3904 current_branch_only=current_branch_only,
3905 tags=tags,
3906 depth=depth,
3907 git_event_log=git_event_log,
3908 manifest_name=spec.manifestName,
3909 this_manifest_only=False,
3910 outer_manifest=False,
3911 )
LaMont Jones409407a2022-04-05 21:21:56 +00003912
LaMont Jones0ddb6772022-05-20 09:11:54 +00003913 # Lastly, if the manifest has a <superproject> then have the superproject
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003914 # sync it (if it will be used).
3915 if git_superproject.UseSuperproject(use_superproject, self.manifest):
LaMont Jones0ddb6772022-05-20 09:11:54 +00003916 sync_result = self.manifest.superproject.Sync(git_event_log)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003917 if not sync_result.success:
3918 print('warning: git update of superproject for '
3919 f'{self.manifest.path_prefix} failed, repo sync will not use '
3920 'superproject to fetch source; while this error is not fatal, '
3921 'and you can continue to run repo sync, please run repo init '
3922 'with the --no-use-superproject option to stop seeing this '
3923 'warning', file=sys.stderr)
3924 if sync_result.fatal and use_superproject is not None:
3925 return False
LaMont Jones409407a2022-04-05 21:21:56 +00003926
LaMont Jones9b03f152022-03-29 23:01:18 +00003927 return True
3928
3929 def _ConfigureDepth(self, depth):
3930 """Configure the depth we'll sync down.
3931
3932 Args:
3933 depth: an int, how deep of a partial clone to create.
3934 """
3935 # Opt.depth will be non-None if user actually passed --depth to repo init.
3936 if depth is not None:
3937 if depth > 0:
3938 # Positive values will set the depth.
3939 depth = str(depth)
3940 else:
3941 # Negative numbers will clear the depth; passing None to SetString
3942 # will do that.
3943 depth = None
3944
3945 # We store the depth in the main manifest project.
3946 self.config.SetString('repo.depth', depth)