blob: df627d41fd2793e854f29ce71dea63f846b5c431 [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
LaMont Jones1eddca82022-09-01 15:15:04 +000029from typing import NamedTuple
Mike Frysingeracf63b22019-06-13 02:24:21 -040030import urllib.parse
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070031
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032from color import Coloring
LaMont Jones0de4fc32022-04-21 17:18:35 +000033import fetch
Dave Borowitzb42b4742012-10-31 12:27:27 -070034from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070035from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
Ningning Xiac2fbc782016-08-22 14:24:39 -070036 ID_RE, RefSpec
LaMont Jonesff6b1da2022-06-01 21:03:34 +000037import git_superproject
LaMont Jones55ee3042022-04-06 17:10:21 +000038from git_trace2_event_log import EventLog
Remy Bohmer16c13282020-09-10 10:38:04 +020039from error import GitError, UploadError, DownloadError
Ningning Xiac2fbc782016-08-22 14:24:39 -070040from error import CacheApplyError
Mike Frysingere6a202f2019-08-02 15:57:57 -040041from error import ManifestInvalidRevisionError, ManifestInvalidPathError
LaMont Jones409407a2022-04-05 21:21:56 +000042from error import NoManifestException, ManifestParseError
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070043import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040044import progress
Joanna Wanga6c52f52022-11-03 16:51:19 -040045from repo_trace import Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050047from 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 -070048
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070049
LaMont Jones1eddca82022-09-01 15:15:04 +000050class SyncNetworkHalfResult(NamedTuple):
51 """Sync_NetworkHalf return value."""
52 # True if successful.
53 success: bool
54 # Did we query the remote? False when optimized_fetch is True and we have the
55 # commit already present.
56 remote_fetched: bool
57
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +010058
George Engelbrecht9bc283e2020-04-02 12:36:09 -060059# Maximum sleep time allowed during retries.
60MAXIMUM_RETRY_SLEEP_SEC = 3600.0
61# +-10% random jitter is added to each Fetches retry sleep duration.
62RETRY_JITTER_PERCENT = 0.1
63
LaMont Jonesfa8d9392022-11-02 22:01:29 +000064# Whether to use alternates. Switching back and forth is *NOT* supported.
Mike Frysinger1d00a7e2021-12-21 00:40:31 -050065# TODO(vapier): Remove knob once behavior is verified.
66_ALTERNATES = os.environ.get('REPO_USE_ALTERNATES') == '1'
George Engelbrecht9bc283e2020-04-02 12:36:09 -060067
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +010068
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070069def _lwrite(path, content):
70 lock = '%s.lock' % path
71
Remy Bohmer169b0212020-11-21 10:57:52 +010072 # Maintain Unix line endings on all OS's to match git behavior.
73 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070074 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070075
76 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070077 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070078 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080079 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070080 raise
81
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070082
Shawn O. Pearce48244782009-04-16 08:25:57 -070083def _error(fmt, *args):
84 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070085 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070086
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070087
David Pursehousef33929d2015-08-24 14:39:14 +090088def _warn(fmt, *args):
89 msg = fmt % args
90 print('warn: %s' % msg, file=sys.stderr)
91
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070092
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070093def not_rev(r):
94 return '^' + r
95
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070096
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080097def sq(r):
98 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080099
David Pursehouse819827a2020-02-12 15:20:19 +0900100
Jonathan Nieder93719792015-03-17 11:29:58 -0700101_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700102
103
Jonathan Nieder93719792015-03-17 11:29:58 -0700104def _ProjectHooks():
105 """List the hooks present in the 'hooks' directory.
106
107 These hooks are project hooks and are copied to the '.git/hooks' directory
108 of all subprojects.
109
110 This function caches the list of hooks (based on the contents of the
111 'repo/hooks' directory) on the first call.
112
113 Returns:
114 A list of absolute paths to all of the files in the hooks directory.
115 """
116 global _project_hook_list
117 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700118 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700119 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700120 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700121 return _project_hook_list
122
123
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700124class DownloadedChange(object):
125 _commit_cache = None
126
127 def __init__(self, project, base, change_id, ps_id, commit):
128 self.project = project
129 self.base = base
130 self.change_id = change_id
131 self.ps_id = ps_id
132 self.commit = commit
133
134 @property
135 def commits(self):
136 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700137 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
138 '--abbrev-commit',
139 '--pretty=oneline',
140 '--reverse',
141 '--date-order',
142 not_rev(self.base),
143 self.commit,
144 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700145 return self._commit_cache
146
147
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700148class ReviewableBranch(object):
149 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400150 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700151
152 def __init__(self, project, branch, base):
153 self.project = project
154 self.branch = branch
155 self.base = base
156
157 @property
158 def name(self):
159 return self.branch.name
160
161 @property
162 def commits(self):
163 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400164 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
165 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
166 try:
167 self._commit_cache = self.project.bare_git.rev_list(*args)
168 except GitError:
169 # We weren't able to probe the commits for this branch. Was it tracking
170 # a branch that no longer exists? If so, return no commits. Otherwise,
171 # rethrow the error as we don't know what's going on.
172 if self.base_exists:
173 raise
174
175 self._commit_cache = []
176
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 return self._commit_cache
178
179 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800180 def unabbrev_commits(self):
181 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700182 for commit in self.project.bare_git.rev_list(not_rev(self.base),
183 R_HEADS + self.name,
184 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800185 r[commit[0:8]] = commit
186 return r
187
188 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700189 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700190 return self.project.bare_git.log('--pretty=format:%cd',
191 '-n', '1',
192 R_HEADS + self.name,
193 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700194
Mike Frysinger6da17752019-09-11 18:43:17 -0400195 @property
196 def base_exists(self):
197 """Whether the branch we're tracking exists.
198
199 Normally it should, but sometimes branches we track can get deleted.
200 """
201 if self._base_exists is None:
202 try:
203 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
204 # If we're still here, the base branch exists.
205 self._base_exists = True
206 except GitError:
207 # If we failed to verify, the base branch doesn't exist.
208 self._base_exists = False
209
210 return self._base_exists
211
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700212 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500213 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700214 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500215 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500216 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200217 private=False,
Vadim Bendebury75bcd242018-10-31 13:48:01 -0700218 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200219 wip=False,
William Escandeac76fd32022-08-02 16:05:37 -0700220 ready=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200221 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800222 validate_certs=True,
223 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500224 self.project.UploadForReview(branch=self.name,
225 people=people,
226 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700227 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500228 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500229 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200230 private=private,
Vadim Bendebury75bcd242018-10-31 13:48:01 -0700231 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200232 wip=wip,
William Escandeac76fd32022-08-02 16:05:37 -0700233 ready=ready,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200234 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800235 validate_certs=validate_certs,
236 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700238 def GetPublishedRefs(self):
239 refs = {}
240 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700241 self.branch.remote.SshReviewUrl(self.project.UserEmail),
242 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700243 for line in output.split('\n'):
244 try:
245 (sha, ref) = line.split()
246 refs[sha] = ref
247 except ValueError:
248 pass
249
250 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700252
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700254
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500256 super().__init__(config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100257 self.project = self.printer('header', attr='bold')
258 self.branch = self.printer('header', attr='bold')
259 self.nobranch = self.printer('nobranch', fg='red')
260 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700261
Anthony King7bdac712014-07-16 12:56:40 +0100262 self.added = self.printer('added', fg='green')
263 self.changed = self.printer('changed', fg='red')
264 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700265
266
267class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700268
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700269 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500270 super().__init__(config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100271 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400272 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700273
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700274
Jack Neus6ea0cae2021-07-20 20:52:33 +0000275class Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700276
James W. Mills24c13082012-04-12 15:04:13 -0500277 def __init__(self, name, value, keep):
278 self.name = name
279 self.value = value
280 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700281
Jack Neus6ea0cae2021-07-20 20:52:33 +0000282 def __eq__(self, other):
283 if not isinstance(other, Annotation):
284 return False
285 return self.__dict__ == other.__dict__
286
287 def __lt__(self, other):
288 # This exists just so that lists of Annotation objects can be sorted, for
289 # use in comparisons.
290 if not isinstance(other, Annotation):
291 raise ValueError('comparison is not between two Annotation objects')
292 if self.name == other.name:
293 if self.value == other.value:
294 return self.keep < other.keep
295 return self.value < other.value
296 return self.name < other.name
297
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700298
Mike Frysingere6a202f2019-08-02 15:57:57 -0400299def _SafeExpandPath(base, subpath, skipfinal=False):
300 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700301
Mike Frysingere6a202f2019-08-02 15:57:57 -0400302 We make sure no intermediate symlinks are traversed, and that the final path
303 is not a special file (e.g. not a socket or fifo).
304
305 NB: We rely on a number of paths already being filtered out while parsing the
306 manifest. See the validation logic in manifest_xml.py for more details.
307 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500308 # Split up the path by its components. We can't use os.path.sep exclusively
309 # as some platforms (like Windows) will convert / to \ and that bypasses all
310 # our constructed logic here. Especially since manifest authors only use
311 # / in their paths.
312 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
313 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400314 if skipfinal:
315 # Whether the caller handles the final component itself.
316 finalpart = components.pop()
317
318 path = base
319 for part in components:
320 if part in {'.', '..'}:
321 raise ManifestInvalidPathError(
322 '%s: "%s" not allowed in paths' % (subpath, part))
323
324 path = os.path.join(path, part)
325 if platform_utils.islink(path):
326 raise ManifestInvalidPathError(
327 '%s: traversing symlinks not allow' % (path,))
328
329 if os.path.exists(path):
330 if not os.path.isfile(path) and not platform_utils.isdir(path):
331 raise ManifestInvalidPathError(
332 '%s: only regular files & directories allowed' % (path,))
333
334 if skipfinal:
335 path = os.path.join(path, finalpart)
336
337 return path
338
339
340class _CopyFile(object):
341 """Container for <copyfile> manifest element."""
342
343 def __init__(self, git_worktree, src, topdir, dest):
344 """Register a <copyfile> request.
345
346 Args:
347 git_worktree: Absolute path to the git project checkout.
348 src: Relative path under |git_worktree| of file to read.
349 topdir: Absolute path to the top of the repo client checkout.
350 dest: Relative path under |topdir| of file to write.
351 """
352 self.git_worktree = git_worktree
353 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700354 self.src = src
355 self.dest = dest
356
357 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400358 src = _SafeExpandPath(self.git_worktree, self.src)
359 dest = _SafeExpandPath(self.topdir, self.dest)
360
361 if platform_utils.isdir(src):
362 raise ManifestInvalidPathError(
363 '%s: copying from directory not supported' % (self.src,))
364 if platform_utils.isdir(dest):
365 raise ManifestInvalidPathError(
366 '%s: copying to directory not allowed' % (self.dest,))
367
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700368 # copy file if it does not exist or is out of date
369 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
370 try:
371 # remove existing file first, since it might be read-only
372 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800373 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400374 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200375 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700376 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200377 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700378 shutil.copy(src, dest)
379 # make the file read-only
380 mode = os.stat(dest)[stat.ST_MODE]
381 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
382 os.chmod(dest, mode)
383 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700384 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700385
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700386
Anthony King7bdac712014-07-16 12:56:40 +0100387class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400388 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700389
Mike Frysingere6a202f2019-08-02 15:57:57 -0400390 def __init__(self, git_worktree, src, topdir, dest):
391 """Register a <linkfile> request.
392
393 Args:
394 git_worktree: Absolute path to the git project checkout.
395 src: Target of symlink relative to path under |git_worktree|.
396 topdir: Absolute path to the top of the repo client checkout.
397 dest: Relative path under |topdir| of symlink to create.
398 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700399 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400400 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500401 self.src = src
402 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500403
Wink Saville4c426ef2015-06-03 08:05:17 -0700404 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500405 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700406 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500407 try:
408 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800409 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800410 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500411 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700412 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700413 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500414 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700415 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500416 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700417 _error('Cannot link file %s to %s', relSrc, absDest)
418
419 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400420 """Link the self.src & self.dest paths.
421
422 Handles wild cards on the src linking all of the files in the source in to
423 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700424 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500425 # Some people use src="." to create stable links to projects. Lets allow
426 # that but reject all other uses of "." to keep things simple.
427 if self.src == '.':
428 src = self.git_worktree
429 else:
430 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400431
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300432 if not glob.has_magic(src):
433 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400434 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
435 # dest & src are absolute paths at this point. Make sure the target of
436 # the symlink is relative in the context of the repo client checkout.
437 relpath = os.path.relpath(src, os.path.dirname(dest))
438 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700439 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400440 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300441 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400442 if os.path.exists(dest) and not platform_utils.isdir(dest):
443 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700444 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400445 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700446 # Create a releative path from source dir to destination dir
447 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400448 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700449
450 # Get the source file name
451 srcFile = os.path.basename(absSrcFile)
452
453 # Now form the final full paths to srcFile. They will be
454 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400455 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700456 relSrc = os.path.join(relSrcDir, srcFile)
457 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500458
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700459
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700460class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700461
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700462 def __init__(self,
463 name,
Anthony King7bdac712014-07-16 12:56:40 +0100464 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700465 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100466 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700467 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700468 orig_name=None,
469 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700470 self.name = name
471 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700472 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700473 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100474 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700475 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700476 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700477
Ian Kasprzak0286e312021-02-05 10:06:18 -0800478
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700479class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600480 # These objects can be shared between several working trees.
LaMont Jones68d69632022-06-07 18:24:20 +0000481 @property
482 def shareable_dirs(self):
483 """Return the shareable directories"""
484 if self.UseAlternates:
485 return ['hooks', 'rr-cache']
486 else:
487 return ['hooks', 'objects', 'rr-cache']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700488
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700489 def __init__(self,
490 manifest,
491 name,
492 remote,
493 gitdir,
David James8d201162013-10-11 17:03:19 -0700494 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700495 worktree,
496 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700497 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800498 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100499 rebase=True,
500 groups=None,
501 sync_c=False,
502 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900503 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100504 clone_depth=None,
505 upstream=None,
506 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500507 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100508 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900509 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700510 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600511 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700512 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800513 """Init a Project object.
514
515 Args:
516 manifest: The XmlManifest object.
517 name: The `name` attribute of manifest.xml's project element.
518 remote: RemoteSpec object specifying its remote's properties.
519 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700520 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800521 worktree: Absolute path of git working tree.
522 relpath: Relative path of git working tree to repo's top directory.
523 revisionExpr: The `revision` attribute of manifest.xml's project element.
524 revisionId: git commit id for checking out.
525 rebase: The `rebase` attribute of manifest.xml's project element.
526 groups: The `groups` attribute of manifest.xml's project element.
527 sync_c: The `sync-c` attribute of manifest.xml's project element.
528 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900529 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800530 upstream: The `upstream` attribute of manifest.xml's project element.
531 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500532 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800533 is_derived: False if the project was explicitly defined in the manifest;
534 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400535 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900536 optimized_fetch: If True, when a project is set to a sha1 revision, only
537 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600538 retry_fetches: Retry remote fetches n times upon receiving transient error
539 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700540 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800541 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400542 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700543 self.name = name
544 self.remote = remote
Michael Kelly37c21c22020-06-13 02:10:40 -0700545 self.UpdatePaths(relpath, worktree, gitdir, objdir)
Michael Kelly2f3c3312020-07-21 19:40:38 -0700546 self.SetRevision(revisionExpr, revisionId=revisionId)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700547
Mike Pontillod3153822012-02-28 11:53:24 -0800548 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700549 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700550 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800551 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900552 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900553 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700554 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800555 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500556 # NB: Do not use this setting in __init__ to change behavior so that the
557 # manifest.git checkout can inspect & change it after instantiating. See
558 # the XmlManifest init code for more info.
559 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800560 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900561 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600562 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800563 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800564
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700565 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700566 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500567 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500568 self.annotations = []
Bryan Jacobsf609f912013-05-06 13:36:24 -0400569 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700570 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700571
Doug Anderson37282b42011-03-04 11:54:18 -0800572 # This will be filled in if a project is later identified to be the
573 # project containing repo hooks.
574 self.enabled_repo_hooks = []
575
LaMont Jonescc879a92021-11-18 22:40:18 +0000576 def RelPath(self, local=True):
577 """Return the path for the project relative to a manifest.
578
579 Args:
580 local: a boolean, if True, the path is relative to the local
581 (sub)manifest. If false, the path is relative to the
582 outermost manifest.
583 """
584 if local:
585 return self.relpath
586 return os.path.join(self.manifest.path_prefix, self.relpath)
587
Michael Kelly2f3c3312020-07-21 19:40:38 -0700588 def SetRevision(self, revisionExpr, revisionId=None):
589 """Set revisionId based on revision expression and id"""
590 self.revisionExpr = revisionExpr
591 if revisionId is None and revisionExpr and IsId(revisionExpr):
592 self.revisionId = self.revisionExpr
593 else:
594 self.revisionId = revisionId
595
Michael Kelly37c21c22020-06-13 02:10:40 -0700596 def UpdatePaths(self, relpath, worktree, gitdir, objdir):
597 """Update paths used by this project"""
598 self.gitdir = gitdir.replace('\\', '/')
599 self.objdir = objdir.replace('\\', '/')
600 if worktree:
601 self.worktree = os.path.normpath(worktree).replace('\\', '/')
602 else:
603 self.worktree = None
604 self.relpath = relpath
605
606 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
607 defaults=self.manifest.globalConfig)
608
609 if self.worktree:
610 self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
611 else:
612 self.work_git = None
613 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
614 self.bare_ref = GitRefs(self.gitdir)
615 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
616
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700617 @property
LaMont Jones68d69632022-06-07 18:24:20 +0000618 def UseAlternates(self):
619 """Whether git alternates are in use.
620
621 This will be removed once migration to alternates is complete.
622 """
623 return _ALTERNATES or self.manifest.is_multimanifest
624
625 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800626 def Derived(self):
627 return self.is_derived
628
629 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700630 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700631 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700632
633 @property
634 def CurrentBranch(self):
635 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400636
637 The branch name omits the 'refs/heads/' prefix.
638 None is returned if the project is on a detached HEAD, or if the work_git is
639 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700640 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400641 try:
642 b = self.work_git.GetHead()
643 except NoManifestException:
644 # If the local checkout is in a bad state, don't barf. Let the callers
645 # process this like the head is unreadable.
646 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700647 if b.startswith(R_HEADS):
648 return b[len(R_HEADS):]
649 return None
650
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700651 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500652 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
653 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
654 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200655
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700656 def IsDirty(self, consider_untracked=True):
657 """Is the working directory modified in some way?
658 """
659 self.work_git.update_index('-q',
660 '--unmerged',
661 '--ignore-missing',
662 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900663 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700664 return True
665 if self.work_git.DiffZ('diff-files'):
666 return True
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200667 if consider_untracked and self.UntrackedFiles():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700668 return True
669 return False
670
671 _userident_name = None
672 _userident_email = None
673
674 @property
675 def UserName(self):
676 """Obtain the user's personal name.
677 """
678 if self._userident_name is None:
679 self._LoadUserIdentity()
680 return self._userident_name
681
682 @property
683 def UserEmail(self):
684 """Obtain the user's email address. This is very likely
685 to be their Gerrit login.
686 """
687 if self._userident_email is None:
688 self._LoadUserIdentity()
689 return self._userident_email
690
691 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900692 u = self.bare_git.var('GIT_COMMITTER_IDENT')
693 m = re.compile("^(.*) <([^>]*)> ").match(u)
694 if m:
695 self._userident_name = m.group(1)
696 self._userident_email = m.group(2)
697 else:
698 self._userident_name = ''
699 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700700
Mike Frysingerdede5642022-07-10 04:56:04 -0400701 def GetRemote(self, name=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702 """Get the configuration for a single remote.
Mike Frysingerdede5642022-07-10 04:56:04 -0400703
704 Defaults to the current project's remote.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700705 """
Mike Frysingerdede5642022-07-10 04:56:04 -0400706 if name is None:
707 name = self.remote.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708 return self.config.GetRemote(name)
709
710 def GetBranch(self, name):
711 """Get the configuration for a single branch.
712 """
713 return self.config.GetBranch(name)
714
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700715 def GetBranches(self):
716 """Get all existing local branches.
717 """
718 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900719 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700720 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700721
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530722 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700723 if name.startswith(R_HEADS):
724 name = name[len(R_HEADS):]
725 b = self.GetBranch(name)
726 b.current = name == current
727 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900728 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700729 heads[name] = b
730
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530731 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700732 if name.startswith(R_PUB):
733 name = name[len(R_PUB):]
734 b = heads.get(name)
735 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900736 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700737
738 return heads
739
Colin Cross5acde752012-03-28 20:15:45 -0700740 def MatchesGroups(self, manifest_groups):
741 """Returns true if the manifest groups specified at init should cause
742 this project to be synced.
743 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700744 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700745
746 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700747 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700748 manifest_groups: "-group1,group2"
749 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500750
751 The special manifest group "default" will match any project that
752 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700753 """
LaMont Jones501733c2022-04-20 16:42:32 +0000754 default_groups = self.manifest.default_groups or ['default']
755 expanded_manifest_groups = manifest_groups or default_groups
Conley Owensbb1b5f52012-08-13 13:11:18 -0700756 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700757 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500758 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700759
Conley Owens971de8e2012-04-16 10:36:08 -0700760 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700761 for group in expanded_manifest_groups:
762 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700763 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700764 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700765 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700766
Conley Owens971de8e2012-04-16 10:36:08 -0700767 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700768
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700769# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700770 def UncommitedFiles(self, get_all=True):
771 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700772
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700773 Args:
774 get_all: a boolean, if True - get information about all different
775 uncommitted files. If False - return as soon as any kind of
776 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500777 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700778 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500779 self.work_git.update_index('-q',
780 '--unmerged',
781 '--ignore-missing',
782 '--refresh')
783 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700784 details.append("rebase in progress")
785 if not get_all:
786 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500787
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700788 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
789 if changes:
790 details.extend(changes)
791 if not get_all:
792 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500793
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700794 changes = self.work_git.DiffZ('diff-files').keys()
795 if changes:
796 details.extend(changes)
797 if not get_all:
798 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500799
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200800 changes = self.UntrackedFiles()
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700801 if changes:
802 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500803
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700804 return details
805
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200806 def UntrackedFiles(self):
807 """Returns a list of strings, untracked files in the git tree."""
808 return self.work_git.LsOthers()
809
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700810 def HasChanges(self):
811 """Returns true if there are uncommitted changes.
812 """
Martin Geisler8db78c72022-07-08 11:05:24 +0200813 return bool(self.UncommitedFiles(get_all=False))
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500814
LaMont Jones8501d462022-06-22 19:21:15 +0000815 def PrintWorkTreeStatus(self, output_redir=None, quiet=False, local=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200817
818 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200819 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600820 quiet: If True then only print the project name. Do not print
821 the modified files, branch name, etc.
LaMont Jones8501d462022-06-22 19:21:15 +0000822 local: a boolean, if True, the path is relative to the local
823 (sub)manifest. If false, the path is relative to the
824 outermost manifest.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700825 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700826 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700827 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200828 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700829 print(file=output_redir)
LaMont Jones8501d462022-06-22 19:21:15 +0000830 print('project %s/' % self.RelPath(local), file=output_redir)
Sarah Owenscecd1d82012-11-01 22:59:27 -0700831 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 return
833
834 self.work_git.update_index('-q',
835 '--unmerged',
836 '--ignore-missing',
837 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700838 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
840 df = self.work_git.DiffZ('diff-files')
841 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100842 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700843 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844
845 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700846 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200847 out.redirect(output_redir)
LaMont Jones8501d462022-06-22 19:21:15 +0000848 out.project('project %-40s', self.RelPath(local) + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600850 if quiet:
851 out.nl()
852 return 'DIRTY'
853
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854 branch = self.CurrentBranch
855 if branch is None:
856 out.nobranch('(*** NO BRANCH ***)')
857 else:
858 out.branch('branch %s', branch)
859 out.nl()
860
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700861 if rb:
862 out.important('prior sync failed; rebase still in progress')
863 out.nl()
864
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865 paths = list()
866 paths.extend(di.keys())
867 paths.extend(df.keys())
868 paths.extend(do)
869
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530870 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900871 try:
872 i = di[p]
873 except KeyError:
874 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700875
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900876 try:
877 f = df[p]
878 except KeyError:
879 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200880
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900881 if i:
882 i_status = i.status.upper()
883 else:
884 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700885
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900886 if f:
887 f_status = f.status.lower()
888 else:
889 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890
891 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800892 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700893 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894 else:
895 line = ' %s%s\t%s' % (i_status, f_status, p)
896
897 if i and not f:
898 out.added('%s', line)
899 elif (i and f) or (not i and f):
900 out.changed('%s', line)
901 elif not i and not f:
902 out.untracked('%s', line)
903 else:
904 out.write('%s', line)
905 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200906
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700907 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700908
LaMont Jones8501d462022-06-22 19:21:15 +0000909 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None,
910 local=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911 """Prints the status of the repository to stdout.
912 """
913 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500914 if output_redir:
915 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916 cmd = ['diff']
917 if out.is_on:
918 cmd.append('--color')
919 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300920 if absolute_paths:
LaMont Jones8501d462022-06-22 19:21:15 +0000921 cmd.append('--src-prefix=a/%s/' % self.RelPath(local))
922 cmd.append('--dst-prefix=b/%s/' % self.RelPath(local))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400924 try:
925 p = GitCommand(self,
926 cmd,
927 capture_stdout=True,
928 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500929 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400930 except GitError as e:
931 out.nl()
LaMont Jones8501d462022-06-22 19:21:15 +0000932 out.project('project %s/' % self.RelPath(local))
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400933 out.nl()
934 out.fail('%s', str(e))
935 out.nl()
936 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500937 if p.stdout:
938 out.nl()
LaMont Jones8501d462022-06-22 19:21:15 +0000939 out.project('project %s/' % self.RelPath(local))
Mike Frysinger84230002021-02-16 17:08:35 -0500940 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500941 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400942 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700944# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900945 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946 """Was the branch published (uploaded) for code review?
947 If so, returns the SHA-1 hash of the last published
948 state for the branch.
949 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700950 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900951 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700952 try:
953 return self.bare_git.rev_parse(key)
954 except GitError:
955 return None
956 else:
957 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900958 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700959 except KeyError:
960 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700961
David Pursehouse8a68ff92012-09-24 12:15:13 +0900962 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963 """Prunes any stale published refs.
964 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900965 if all_refs is None:
966 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967 heads = set()
968 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530969 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970 if name.startswith(R_HEADS):
971 heads.add(name)
972 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900973 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700974
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530975 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700976 n = name[len(R_PUB):]
977 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900978 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700980 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 """List any branches which can be uploaded for review.
982 """
983 heads = {}
984 pubed = {}
985
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530986 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900988 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900990 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991
992 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530993 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900994 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700995 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700996 if selected_branch and branch != selected_branch:
997 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800999 rb = self.GetUploadableBranch(branch)
1000 if rb:
1001 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002 return ready
1003
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001004 def GetUploadableBranch(self, branch_name):
1005 """Get a single uploadable branch, or None.
1006 """
1007 branch = self.GetBranch(branch_name)
1008 base = branch.LocalMerge
1009 if branch.LocalMerge:
1010 rb = ReviewableBranch(self, branch, base)
1011 if rb.commits:
1012 return rb
1013 return None
1014
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001015 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001016 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -05001017 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001018 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001019 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001020 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +02001021 private=False,
Vadim Bendebury75bcd242018-10-31 13:48:01 -07001022 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001023 wip=False,
William Escandeac76fd32022-08-02 16:05:37 -07001024 ready=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001025 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001026 validate_certs=True,
1027 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028 """Uploads the named branch for code review.
1029 """
1030 if branch is None:
1031 branch = self.CurrentBranch
1032 if branch is None:
1033 raise GitError('not currently on a branch')
1034
1035 branch = self.GetBranch(branch)
1036 if not branch.LocalMerge:
1037 raise GitError('branch %s does not track a remote' % branch.name)
1038 if not branch.remote.review:
1039 raise GitError('remote %s has no review url' % branch.remote.name)
1040
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001041 # Basic validity check on label syntax.
1042 for label in labels:
1043 if not re.match(r'^.+[+-][0-9]+$', label):
1044 raise UploadError(
1045 f'invalid label syntax "{label}": labels use forms like '
1046 'CodeReview+1 or Verified-1')
1047
Bryan Jacobsf609f912013-05-06 13:36:24 -04001048 if dest_branch is None:
1049 dest_branch = self.dest_branch
1050 if dest_branch is None:
1051 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001052 if not dest_branch.startswith(R_HEADS):
1053 dest_branch = R_HEADS + dest_branch
1054
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001055 if not branch.remote.projectname:
1056 branch.remote.projectname = self.name
1057 branch.remote.Save()
1058
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001059 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001060 if url is None:
1061 raise UploadError('review not configured')
1062 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001063 if dryrun:
1064 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001065
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001066 if url.startswith('ssh://'):
Jonathan Nieder81df7e12018-11-05 13:21:52 -08001067 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001068
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001069 for push_option in (push_options or []):
1070 cmd.append('-o')
1071 cmd.append(push_option)
1072
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001073 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001074
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001075 if dest_branch.startswith(R_HEADS):
1076 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001077
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001078 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001079 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001080 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001081 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001082 opts += ['t=%s' % p for p in hashtags]
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001083 # NB: No need to encode labels as they've been validated above.
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001084 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001085
David Pursehousef25a3702018-11-14 19:01:22 -08001086 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder81df7e12018-11-05 13:21:52 -08001087 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendebury75bcd242018-10-31 13:48:01 -07001088 if notify:
1089 opts += ['notify=' + notify]
Jonathan Nieder81df7e12018-11-05 13:21:52 -08001090 if private:
1091 opts += ['private']
1092 if wip:
1093 opts += ['wip']
William Escandeac76fd32022-08-02 16:05:37 -07001094 if ready:
1095 opts += ['ready']
Jonathan Nieder81df7e12018-11-05 13:21:52 -08001096 if opts:
1097 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001098 cmd.append(ref_spec)
1099
Anthony King7bdac712014-07-16 12:56:40 +01001100 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001101 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102
Mike Frysingerd7f86832020-11-19 19:18:46 -05001103 if not dryrun:
1104 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1105 self.bare_git.UpdateRef(R_PUB + branch.name,
1106 R_HEADS + branch.name,
1107 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001108
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001109# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001110 def _ExtractArchive(self, tarpath, path=None):
1111 """Extract the given tar on its current location
1112
1113 Args:
1114 - tarpath: The path to the actual tar file
1115
1116 """
1117 try:
1118 with tarfile.open(tarpath, 'r') as tar:
1119 tar.extractall(path=path)
1120 return True
1121 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001122 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001123 return False
1124
Ningning Xiac2fbc782016-08-22 14:24:39 -07001125 def CachePopulate(self, cache_dir, url):
1126 """Populate cache in the cache_dir.
1127
1128 Args:
1129 cache_dir: Directory to cache git files from Google Storage.
1130 url: Git url of current repository.
1131
1132 Raises:
1133 CacheApplyError if it fails to populate the git cache.
1134 """
1135 cmd = ['cache', 'populate', '--ignore_locks', '-v',
1136 '--cache-dir', cache_dir, url]
1137
1138 if GitCommand(self, cmd, cwd=cache_dir).Wait() != 0:
1139 raise CacheApplyError('Failed to populate cache. cache_dir: %s '
1140 'url: %s' % (cache_dir, url))
1141
1142 def CacheExists(self, cache_dir, url):
1143 """Check the existence of the cache files.
1144
1145 Args:
1146 cache_dir: Directory to cache git files.
1147 url: Git url of current repository.
1148
1149 Raises:
1150 CacheApplyError if the cache files do not exist.
1151 """
1152 cmd = ['cache', 'exists', '--quiet', '--cache-dir', cache_dir, url]
1153
1154 exist = GitCommand(self, cmd, cwd=self.gitdir, capture_stdout=True)
1155 if exist.Wait() != 0:
1156 raise CacheApplyError('Failed to execute git cache exists cmd. '
1157 'cache_dir: %s url: %s' % (cache_dir, url))
1158
1159 if not exist.stdout or not exist.stdout.strip():
1160 raise CacheApplyError('Failed to find cache. cache_dir: %s '
1161 'url: %s' % (cache_dir, url))
1162 return exist.stdout.strip()
1163
1164 def CacheApply(self, cache_dir):
1165 """Apply git cache files populated from Google Storage buckets.
1166
1167 Args:
1168 cache_dir: Directory to cache git files.
1169
1170 Raises:
1171 CacheApplyError if it fails to apply git caches.
1172 """
1173 remote = self.GetRemote(self.remote.name)
1174
1175 self.CachePopulate(cache_dir, remote.url)
1176
1177 mirror_dir = self.CacheExists(cache_dir, remote.url)
1178
1179 refspec = RefSpec(True, 'refs/heads/*',
1180 'refs/remotes/%s/*' % remote.name)
1181
1182 fetch_cache_cmd = ['fetch', mirror_dir, str(refspec)]
1183 if GitCommand(self, fetch_cache_cmd, self.gitdir).Wait() != 0:
1184 raise CacheApplyError('Failed to fetch refs %s from %s' %
1185 (mirror_dir, str(refspec)))
1186
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001187 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001188 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001189 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001190 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001191 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001192 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001193 force_sync=False,
1194 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001195 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001196 archive=False,
1197 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001198 retry_fetches=0,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001199 prune=False,
Mike Frysingerde72f6a2018-12-13 03:20:31 -05001200 submodules=False,
Mike Frysingerea6d8692021-04-19 12:52:52 -04001201 cache_dir=None,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001202 ssh_proxy=None,
Mike Frysinger39ba6312019-07-27 12:45:51 -04001203 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001204 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001205 """Perform only the network IO portion of the sync process.
1206 Local working directory/branch state is not affected.
1207 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001208 if archive and not isinstance(self, MetaProject):
1209 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001210 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
LaMont Jones1eddca82022-09-01 15:15:04 +00001211 return SyncNetworkHalfResult(False, False)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001212
1213 name = self.relpath.replace('\\', '/')
1214 name = name.replace('/', '_')
1215 tarpath = '%s.tar' % name
1216 topdir = self.manifest.topdir
1217
1218 try:
1219 self._FetchArchive(tarpath, cwd=topdir)
1220 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001221 _error('%s', e)
LaMont Jones1eddca82022-09-01 15:15:04 +00001222 return SyncNetworkHalfResult(False, False)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001223
1224 # From now on, we only need absolute tarpath
1225 tarpath = os.path.join(topdir, tarpath)
1226
1227 if not self._ExtractArchive(tarpath, path=topdir):
LaMont Jones1eddca82022-09-01 15:15:04 +00001228 return SyncNetworkHalfResult(False, True)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001229 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001230 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001231 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001232 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001233 self._CopyAndLinkFiles()
LaMont Jones1eddca82022-09-01 15:15:04 +00001234 return SyncNetworkHalfResult(True, True)
Mike Frysinger76844ba2021-02-28 17:08:55 -05001235
1236 # If the shared object dir already exists, don't try to rebootstrap with a
1237 # clone bundle download. We should have the majority of objects already.
1238 if clone_bundle and os.path.exists(self.objdir):
1239 clone_bundle = False
1240
Raman Tennetif32f2432021-04-12 20:57:25 -07001241 if self.name in partial_clone_exclude:
1242 clone_bundle = True
1243 clone_filter = None
1244
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001245 if is_new is None:
1246 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001247 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001248 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001249 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001250 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001251 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001252
LaMont Jones68d69632022-06-07 18:24:20 +00001253 if self.UseAlternates:
Mike Frysinger1d00a7e2021-12-21 00:40:31 -05001254 # If gitdir/objects is a symlink, migrate it from the old layout.
1255 gitdir_objects = os.path.join(self.gitdir, 'objects')
1256 if platform_utils.islink(gitdir_objects):
1257 platform_utils.remove(gitdir_objects, missing_ok=True)
1258 gitdir_alt = os.path.join(self.gitdir, 'objects/info/alternates')
1259 if not os.path.exists(gitdir_alt):
1260 os.makedirs(os.path.dirname(gitdir_alt), exist_ok=True)
1261 _lwrite(gitdir_alt, os.path.join(
1262 os.path.relpath(self.objdir, gitdir_objects), 'objects') + '\n')
1263
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001264 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001265 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001266 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001267 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001268 # This works for both absolute and relative alternate directories.
1269 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001270 except IOError:
1271 alt_dir = None
1272 else:
1273 alt_dir = None
1274
Ningning Xiac2fbc782016-08-22 14:24:39 -07001275 applied_cache = False
1276 # If cache_dir is provided, and it's a new repository without
1277 # alternative_dir, bootstrap this project repo with the git
1278 # cache files.
1279 if cache_dir is not None and is_new and alt_dir is None:
1280 try:
1281 self.CacheApply(cache_dir)
1282 applied_cache = True
1283 is_new = False
1284 except CacheApplyError as e:
1285 _error('Could not apply git cache: %s', e)
1286 _error('Please check if you have the right GS credentials.')
1287 _error('Please check if the cache files exist in GS.')
1288
Mike Frysingere50b6a72020-02-19 01:45:48 -05001289 if (clone_bundle
Mike Frysingerd70c6072020-02-25 10:22:02 -05001290 and not applied_cache
Mike Frysingere50b6a72020-02-19 01:45:48 -05001291 and alt_dir is None
1292 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001293 is_new = False
1294
Mike Frysinger73561142021-05-03 01:10:09 -04001295 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001296 if self.sync_c:
1297 current_branch_only = True
1298 elif not self.manifest._loaded:
1299 # Manifest cannot check defaults until it syncs.
1300 current_branch_only = False
1301 elif self.manifest.default.sync_c:
1302 current_branch_only = True
1303
Mike Frysingerd68ed632021-05-03 01:21:35 -04001304 if tags is None:
1305 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001306
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001307 if self.clone_depth:
1308 depth = self.clone_depth
1309 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001310 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001311
Mike Frysinger521d01b2020-02-17 01:51:49 -05001312 # See if we can skip the network fetch entirely.
LaMont Jones1eddca82022-09-01 15:15:04 +00001313 remote_fetched = False
Mike Frysinger521d01b2020-02-17 01:51:49 -05001314 if not (optimized_fetch and
1315 (ID_RE.match(self.revisionExpr) and
1316 self._CheckForImmutableRevision())):
LaMont Jones1eddca82022-09-01 15:15:04 +00001317 remote_fetched = True
Mike Frysinger521d01b2020-02-17 01:51:49 -05001318 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001319 initial=is_new,
1320 quiet=quiet, verbose=verbose, output_redir=output_redir,
1321 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001322 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001323 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001324 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001325 clone_filter=clone_filter, retry_fetches=retry_fetches):
LaMont Jones1eddca82022-09-01 15:15:04 +00001326 return SyncNetworkHalfResult(False, remote_fetched)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001327
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001328 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001329 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001330 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001331 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001332 if os.path.exists(alternates_file):
1333 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001334 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1335 merge_output=bool(output_redir))
1336 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001337 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001338 if p.Wait() != 0:
LaMont Jones1eddca82022-09-01 15:15:04 +00001339 return SyncNetworkHalfResult(False, remote_fetched)
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001340 platform_utils.remove(alternates_file)
1341
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001342 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001343 self._InitMRef()
1344 else:
1345 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001346 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1347 missing_ok=True)
LaMont Jones1eddca82022-09-01 15:15:04 +00001348 return SyncNetworkHalfResult(True, remote_fetched)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001349
1350 def PostRepoUpgrade(self):
1351 self._InitHooks()
1352
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001353 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001354 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001355 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001356 for copyfile in self.copyfiles:
1357 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001358 for linkfile in self.linkfiles:
1359 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001360
Julien Camperguedd654222014-01-09 16:21:37 +01001361 def GetCommitRevisionId(self):
1362 """Get revisionId of a commit.
1363
1364 Use this method instead of GetRevisionId to get the id of the commit rather
1365 than the id of the current git object (for example, a tag)
1366
1367 """
1368 if not self.revisionExpr.startswith(R_TAGS):
1369 return self.GetRevisionId(self._allrefs)
1370
1371 try:
1372 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1373 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001374 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1375 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001376
David Pursehouse8a68ff92012-09-24 12:15:13 +09001377 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001378 if self.revisionId:
1379 return self.revisionId
1380
Mike Frysingerdede5642022-07-10 04:56:04 -04001381 rem = self.GetRemote()
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001382 rev = rem.ToLocal(self.revisionExpr)
1383
David Pursehouse8a68ff92012-09-24 12:15:13 +09001384 if all_refs is not None and rev in all_refs:
1385 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001386
1387 try:
1388 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1389 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001390 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1391 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001392
Raman Tenneti6a872c92021-01-14 19:17:50 -08001393 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001394 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001395 self.upstream = self.revisionExpr
1396
Raman Tenneti6a872c92021-01-14 19:17:50 -08001397 self.revisionId = revisionId
1398
Martin Kellye4e94d22017-03-21 16:05:12 -07001399 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001400 """Perform only the local IO portion of the sync process.
1401 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001402 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001403 if not os.path.exists(self.gitdir):
1404 syncbuf.fail(self,
1405 'Cannot checkout %s due to missing network sync; Run '
1406 '`repo sync -n %s` first.' %
1407 (self.name, self.name))
1408 return
1409
Martin Kellye4e94d22017-03-21 16:05:12 -07001410 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001411 all_refs = self.bare_ref.all
1412 self.CleanPublishedCache(all_refs)
1413 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001414
Mike Frysinger0458faa2021-03-10 23:35:44 -05001415 # Special case the root of the repo client checkout. Make sure it doesn't
1416 # contain files being checked out to dirs we don't allow.
1417 if self.relpath == '.':
1418 PROTECTED_PATHS = {'.repo'}
1419 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1420 bad_paths = paths & PROTECTED_PATHS
1421 if bad_paths:
1422 syncbuf.fail(self,
1423 'Refusing to checkout project that writes to protected '
1424 'paths: %s' % (', '.join(bad_paths),))
1425 return
1426
David Pursehouse1d947b32012-10-25 12:23:11 +09001427 def _doff():
1428 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001429 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001430
Martin Kellye4e94d22017-03-21 16:05:12 -07001431 def _dosubmodules():
1432 self._SyncSubmodules(quiet=True)
1433
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001434 head = self.work_git.GetHead()
1435 if head.startswith(R_HEADS):
1436 branch = head[len(R_HEADS):]
1437 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001438 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001439 except KeyError:
1440 head = None
1441 else:
1442 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001443
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001444 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001445 # Currently on a detached HEAD. The user is assumed to
1446 # not have any local modifications worth worrying about.
1447 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001448 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001449 syncbuf.fail(self, _PriorSyncFailedError())
1450 return
1451
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001452 if head == revid:
1453 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001454 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001455 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001456 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001457 # The copy/linkfile config may have changed.
1458 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001459 return
1460 else:
1461 lost = self._revlist(not_rev(revid), HEAD)
1462 if lost:
1463 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001464
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001465 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001466 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001467 if submodules:
1468 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001469 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001470 syncbuf.fail(self, e)
1471 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001472 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001473 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001474
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001475 if head == revid:
1476 # No changes; don't do anything further.
1477 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001478 # The copy/linkfile config may have changed.
1479 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001480 return
1481
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001482 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001483
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001484 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001485 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001486 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001487 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001488 syncbuf.info(self,
1489 "leaving %s; does not track upstream",
1490 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001492 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001493 if submodules:
1494 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001495 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001496 syncbuf.fail(self, e)
1497 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001498 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001499 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001500
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001501 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001502
1503 # See if we can perform a fast forward merge. This can happen if our
1504 # branch isn't in the exact same state as we last published.
1505 try:
1506 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1507 # Skip the published logic.
1508 pub = False
1509 except GitError:
1510 pub = self.WasPublished(branch.name, all_refs)
1511
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001512 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001513 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001514 if not_merged:
1515 if upstream_gain:
1516 # The user has published this branch and some of those
1517 # commits are not yet merged upstream. We do not want
1518 # to rewrite the published commits so we punt.
1519 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001520 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001521 "branch %s is published (but not merged) and is now "
1522 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001523 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001524 elif pub == head:
1525 # All published commits are merged, and thus we are a
1526 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001527 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001528 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001529 if submodules:
1530 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001531 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001532
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001533 # Examine the local commits not in the remote. Find the
1534 # last one attributed to this user, if any.
1535 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001536 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001537 last_mine = None
1538 cnt_mine = 0
1539 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001540 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001541 if committer_email == self.UserEmail:
1542 last_mine = commit_id
1543 cnt_mine += 1
1544
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001545 if not upstream_gain and cnt_mine == len(local_changes):
Peter Kjellerstedta39af3d2022-09-01 19:24:36 +02001546 # The copy/linkfile config may have changed.
1547 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001548 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001549
1550 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001551 syncbuf.fail(self, _DirtyError())
1552 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001553
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001554 # If the upstream switched on us, warn the user.
1555 #
1556 if branch.merge != self.revisionExpr:
1557 if branch.merge and self.revisionExpr:
1558 syncbuf.info(self,
1559 'manifest switched %s...%s',
1560 branch.merge,
1561 self.revisionExpr)
1562 elif branch.merge:
1563 syncbuf.info(self,
1564 'manifest no longer tracks %s',
1565 branch.merge)
1566
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001567 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001568 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001569 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001570 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001571 syncbuf.info(self,
1572 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001573 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001574
Mike Frysingerdede5642022-07-10 04:56:04 -04001575 branch.remote = self.GetRemote()
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001576 if not ID_RE.match(self.revisionExpr):
1577 # in case of manifest sync the revisionExpr might be a SHA1
1578 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001579 if not branch.merge.startswith('refs/'):
1580 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001581 branch.Save()
1582
Mike Pontillod3153822012-02-28 11:53:24 -08001583 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001584 def _docopyandlink():
1585 self._CopyAndLinkFiles()
1586
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001587 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001588 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001589 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001590 if submodules:
1591 syncbuf.later2(self, _dosubmodules)
1592 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001593 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001594 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001595 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001596 if submodules:
1597 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001598 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001599 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001600 syncbuf.fail(self, e)
1601 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001602 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001603 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001604 if submodules:
1605 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001606
Mike Frysingere6a202f2019-08-02 15:57:57 -04001607 def AddCopyFile(self, src, dest, topdir):
1608 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001609
Mike Frysingere6a202f2019-08-02 15:57:57 -04001610 No filesystem changes occur here. Actual copying happens later on.
1611
1612 Paths should have basic validation run on them before being queued.
1613 Further checking will be handled when the actual copy happens.
1614 """
1615 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1616
1617 def AddLinkFile(self, src, dest, topdir):
1618 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1619
1620 No filesystem changes occur here. Actual linking happens later on.
1621
1622 Paths should have basic validation run on them before being queued.
1623 Further checking will be handled when the actual link happens.
1624 """
1625 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001626
James W. Mills24c13082012-04-12 15:04:13 -05001627 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001628 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001629
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001630 def DownloadPatchSet(self, change_id, patch_id):
1631 """Download a single patch set of a single change to FETCH_HEAD.
1632 """
Mike Frysingerdede5642022-07-10 04:56:04 -04001633 remote = self.GetRemote()
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001634
1635 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001636 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001637 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001638 if GitCommand(self, cmd, bare=True).Wait() != 0:
1639 return None
1640 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001641 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001642 change_id,
1643 patch_id,
1644 self.bare_git.rev_parse('FETCH_HEAD'))
1645
Mike Frysingerc0d18662020-02-19 19:19:18 -05001646 def DeleteWorktree(self, quiet=False, force=False):
1647 """Delete the source checkout and any other housekeeping tasks.
Mike Frysingerf914edc2020-02-09 03:01:56 -05001648
Mike Frysingerc0d18662020-02-19 19:19:18 -05001649 This currently leaves behind the internal .repo/ cache state. This helps
1650 when switching branches or manifest changes get reverted as we don't have
1651 to redownload all the git objects. But we should do some GC at some point.
1652
1653 Args:
1654 quiet: Whether to hide normal messages.
1655 force: Always delete tree even if dirty.
1656
1657 Returns:
1658 True if the worktree was completely cleaned out.
1659 """
1660 if self.IsDirty():
1661 if force:
1662 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
LaMont Jones8501d462022-06-22 19:21:15 +00001663 (self.RelPath(local=False),), file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001664 else:
1665 print('error: %s: Cannot remove project: uncommitted changes are '
LaMont Jones8501d462022-06-22 19:21:15 +00001666 'present.\n' % (self.RelPath(local=False),), file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001667 return False
1668
1669 if not quiet:
LaMont Jones8501d462022-06-22 19:21:15 +00001670 print('%s: Deleting obsolete checkout.' % (self.RelPath(local=False),))
Mike Frysingerc0d18662020-02-19 19:19:18 -05001671
1672 # Unlock and delink from the main worktree. We don't use git's worktree
1673 # remove because it will recursively delete projects -- we handle that
1674 # ourselves below. https://crbug.com/git/48
1675 if self.use_git_worktrees:
1676 needle = platform_utils.realpath(self.gitdir)
1677 # Find the git worktree commondir under .repo/worktrees/.
1678 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1679 assert output.startswith('worktree '), output
1680 commondir = output[9:]
1681 # Walk each of the git worktrees to see where they point.
1682 configs = os.path.join(commondir, 'worktrees')
1683 for name in os.listdir(configs):
1684 gitdir = os.path.join(configs, name, 'gitdir')
1685 with open(gitdir) as fp:
1686 relpath = fp.read().strip()
1687 # Resolve the checkout path and see if it matches this project.
1688 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1689 if fullpath == needle:
1690 platform_utils.rmtree(os.path.join(configs, name))
1691
1692 # Delete the .git directory first, so we're less likely to have a partially
1693 # working git repository around. There shouldn't be any git projects here,
1694 # so rmtree works.
1695
1696 # Try to remove plain files first in case of git worktrees. If this fails
1697 # for any reason, we'll fall back to rmtree, and that'll display errors if
1698 # it can't remove things either.
1699 try:
1700 platform_utils.remove(self.gitdir)
1701 except OSError:
1702 pass
1703 try:
1704 platform_utils.rmtree(self.gitdir)
1705 except OSError as e:
1706 if e.errno != errno.ENOENT:
1707 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1708 print('error: %s: Failed to delete obsolete checkout; remove manually, '
LaMont Jones8501d462022-06-22 19:21:15 +00001709 'then run `repo sync -l`.' % (self.RelPath(local=False),),
1710 file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001711 return False
1712
1713 # Delete everything under the worktree, except for directories that contain
1714 # another git project.
1715 dirs_to_remove = []
1716 failed = False
1717 for root, dirs, files in platform_utils.walk(self.worktree):
1718 for f in files:
1719 path = os.path.join(root, f)
1720 try:
1721 platform_utils.remove(path)
1722 except OSError as e:
1723 if e.errno != errno.ENOENT:
1724 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1725 failed = True
1726 dirs[:] = [d for d in dirs
1727 if not os.path.lexists(os.path.join(root, d, '.git'))]
1728 dirs_to_remove += [os.path.join(root, d) for d in dirs
1729 if os.path.join(root, d) not in dirs_to_remove]
1730 for d in reversed(dirs_to_remove):
1731 if platform_utils.islink(d):
1732 try:
1733 platform_utils.remove(d)
1734 except OSError as e:
1735 if e.errno != errno.ENOENT:
1736 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1737 failed = True
1738 elif not platform_utils.listdir(d):
1739 try:
1740 platform_utils.rmdir(d)
1741 except OSError as e:
1742 if e.errno != errno.ENOENT:
1743 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1744 failed = True
1745 if failed:
LaMont Jones8501d462022-06-22 19:21:15 +00001746 print('error: %s: Failed to delete obsolete checkout.' % (self.RelPath(local=False),),
Mike Frysingerc0d18662020-02-19 19:19:18 -05001747 file=sys.stderr)
1748 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1749 return False
1750
1751 # Try deleting parent dirs if they are empty.
1752 path = self.worktree
1753 while path != self.manifest.topdir:
1754 try:
1755 platform_utils.rmdir(path)
1756 except OSError as e:
1757 if e.errno != errno.ENOENT:
1758 break
1759 path = os.path.dirname(path)
1760
1761 return True
1762
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001763# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001764 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001765 """Create a new branch off the manifest's revision.
1766 """
Simran Basib9a1b732015-08-20 12:19:28 -07001767 if not branch_merge:
1768 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001769 head = self.work_git.GetHead()
1770 if head == (R_HEADS + name):
1771 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001772
David Pursehouse8a68ff92012-09-24 12:15:13 +09001773 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001774 if R_HEADS + name in all_refs:
Mike Frysingerad1b7bd2022-08-18 09:17:09 -04001775 return GitCommand(self, ['checkout', '-q', name, '--']).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001776
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001777 branch = self.GetBranch(name)
Mike Frysingerdede5642022-07-10 04:56:04 -04001778 branch.remote = self.GetRemote()
Simran Basib9a1b732015-08-20 12:19:28 -07001779 branch.merge = branch_merge
1780 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1781 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001782
1783 if revision is None:
1784 revid = self.GetRevisionId(all_refs)
1785 else:
1786 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001787
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001788 if head.startswith(R_HEADS):
1789 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001790 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001791 except KeyError:
1792 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001793 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001794 ref = R_HEADS + name
1795 self.work_git.update_ref(ref, revid)
1796 self.work_git.symbolic_ref(HEAD, ref)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001797 branch.Save()
1798 return True
1799
Mike Frysingerad1b7bd2022-08-18 09:17:09 -04001800 if GitCommand(self, ['checkout', '-q', '-b', branch.name, revid]).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001801 branch.Save()
1802 return True
1803 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001804
Wink Saville02d79452009-04-10 13:01:24 -07001805 def CheckoutBranch(self, name):
1806 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001807
1808 Args:
1809 name: The name of the branch to checkout.
1810
1811 Returns:
1812 True if the checkout succeeded; False if it didn't; None if the branch
1813 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001814 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001815 rev = R_HEADS + name
1816 head = self.work_git.GetHead()
1817 if head == rev:
1818 # Already on the branch
1819 #
1820 return True
Wink Saville02d79452009-04-10 13:01:24 -07001821
David Pursehouse8a68ff92012-09-24 12:15:13 +09001822 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001823 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001824 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001825 except KeyError:
1826 # Branch does not exist in this project
1827 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001828 return None
Wink Saville02d79452009-04-10 13:01:24 -07001829
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001830 if head.startswith(R_HEADS):
1831 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001832 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001833 except KeyError:
1834 head = None
1835
1836 if head == revid:
1837 # Same revision; just update HEAD to point to the new
1838 # target branch, but otherwise take no other action.
1839 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001840 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1841 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001842 return True
1843
1844 return GitCommand(self,
1845 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001846 capture_stdout=True,
1847 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001848
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001849 def AbandonBranch(self, name):
1850 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001851
1852 Args:
1853 name: The name of the branch to abandon.
1854
1855 Returns:
1856 True if the abandon succeeded; False if it didn't; None if the branch
1857 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001858 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001859 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001860 all_refs = self.bare_ref.all
1861 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001862 # Doesn't exist
1863 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001864
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001865 head = self.work_git.GetHead()
1866 if head == rev:
1867 # We can't destroy the branch while we are sitting
1868 # on it. Switch to a detached HEAD.
1869 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001870 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001871
David Pursehouse8a68ff92012-09-24 12:15:13 +09001872 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001873 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001874 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001875 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001876 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001877
1878 return GitCommand(self,
1879 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001880 capture_stdout=True,
1881 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001882
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001883 def PruneHeads(self):
1884 """Prune any topic branches already merged into upstream.
1885 """
1886 cb = self.CurrentBranch
1887 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001888 left = self._allrefs
1889 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001890 if name.startswith(R_HEADS):
1891 name = name[len(R_HEADS):]
1892 if cb is None or name != cb:
1893 kill.append(name)
1894
Mike Frysingera3794e92021-03-11 23:24:01 -05001895 # Minor optimization: If there's nothing to prune, then don't try to read
1896 # any project state.
1897 if not kill and not cb:
1898 return []
1899
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001900 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001901 if cb is not None \
1902 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001903 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001904 self.work_git.DetachHead(HEAD)
1905 kill.append(cb)
1906
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001907 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001908 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001909
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001910 try:
1911 self.bare_git.DetachHead(rev)
1912
1913 b = ['branch', '-d']
1914 b.extend(kill)
1915 b = GitCommand(self, b, bare=True,
1916 capture_stdout=True,
1917 capture_stderr=True)
1918 b.Wait()
1919 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001920 if ID_RE.match(old):
1921 self.bare_git.DetachHead(old)
1922 else:
1923 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001924 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001925
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001926 for branch in kill:
1927 if (R_HEADS + branch) not in left:
1928 self.CleanPublishedCache()
1929 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001930
1931 if cb and cb not in kill:
1932 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001933 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001934
1935 kept = []
1936 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001937 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001938 branch = self.GetBranch(branch)
1939 base = branch.LocalMerge
1940 if not base:
1941 base = rev
1942 kept.append(ReviewableBranch(self, branch, base))
1943 return kept
1944
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001945# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001946 def GetRegisteredSubprojects(self):
1947 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001948
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001949 def rec(subprojects):
1950 if not subprojects:
1951 return
1952 result.extend(subprojects)
1953 for p in subprojects:
1954 rec(p.subprojects)
1955 rec(self.subprojects)
1956 return result
1957
1958 def _GetSubmodules(self):
1959 # Unfortunately we cannot call `git submodule status --recursive` here
1960 # because the working tree might not exist yet, and it cannot be used
1961 # without a working tree in its current implementation.
1962
1963 def get_submodules(gitdir, rev):
1964 # Parse .gitmodules for submodule sub_paths and sub_urls
1965 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1966 if not sub_paths:
1967 return []
1968 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1969 # revision of submodule repository
1970 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1971 submodules = []
1972 for sub_path, sub_url in zip(sub_paths, sub_urls):
1973 try:
1974 sub_rev = sub_revs[sub_path]
1975 except KeyError:
1976 # Ignore non-exist submodules
1977 continue
1978 submodules.append((sub_rev, sub_path, sub_url))
1979 return submodules
1980
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001981 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1982 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001983
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001984 def parse_gitmodules(gitdir, rev):
1985 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1986 try:
Anthony King7bdac712014-07-16 12:56:40 +01001987 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1988 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001989 except GitError:
1990 return [], []
1991 if p.Wait() != 0:
1992 return [], []
1993
1994 gitmodules_lines = []
1995 fd, temp_gitmodules_path = tempfile.mkstemp()
1996 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001997 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001998 os.close(fd)
1999 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002000 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2001 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002002 if p.Wait() != 0:
2003 return [], []
2004 gitmodules_lines = p.stdout.split('\n')
2005 except GitError:
2006 return [], []
2007 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002008 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002009
2010 names = set()
2011 paths = {}
2012 urls = {}
2013 for line in gitmodules_lines:
2014 if not line:
2015 continue
2016 m = re_path.match(line)
2017 if m:
2018 names.add(m.group(1))
2019 paths[m.group(1)] = m.group(2)
2020 continue
2021 m = re_url.match(line)
2022 if m:
2023 names.add(m.group(1))
2024 urls[m.group(1)] = m.group(2)
2025 continue
2026 names = sorted(names)
2027 return ([paths.get(name, '') for name in names],
2028 [urls.get(name, '') for name in names])
2029
2030 def git_ls_tree(gitdir, rev, paths):
2031 cmd = ['ls-tree', rev, '--']
2032 cmd.extend(paths)
2033 try:
Anthony King7bdac712014-07-16 12:56:40 +01002034 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2035 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002036 except GitError:
2037 return []
2038 if p.Wait() != 0:
2039 return []
2040 objects = {}
2041 for line in p.stdout.split('\n'):
2042 if not line.strip():
2043 continue
2044 object_rev, object_path = line.split()[2:4]
2045 objects[object_path] = object_rev
2046 return objects
2047
2048 try:
2049 rev = self.GetRevisionId()
2050 except GitError:
2051 return []
2052 return get_submodules(self.gitdir, rev)
2053
2054 def GetDerivedSubprojects(self):
2055 result = []
2056 if not self.Exists:
2057 # If git repo does not exist yet, querying its submodules will
2058 # mess up its states; so return here.
2059 return result
2060 for rev, path, url in self._GetSubmodules():
2061 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002062 relpath, worktree, gitdir, objdir = \
2063 self.manifest.GetSubprojectPaths(self, name, path)
2064 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002065 if project:
2066 result.extend(project.GetDerivedSubprojects())
2067 continue
David James8d201162013-10-11 17:03:19 -07002068
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002069 if url.startswith('..'):
2070 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002071 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002072 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002073 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002074 review=self.remote.review,
2075 revision=self.remote.revision)
2076 subproject = Project(manifest=self.manifest,
2077 name=name,
2078 remote=remote,
2079 gitdir=gitdir,
2080 objdir=objdir,
2081 worktree=worktree,
2082 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002083 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002084 revisionId=rev,
2085 rebase=self.rebase,
2086 groups=self.groups,
2087 sync_c=self.sync_c,
2088 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002089 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002090 parent=self,
2091 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002092 result.append(subproject)
2093 result.extend(subproject.GetDerivedSubprojects())
2094 return result
2095
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002096# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002097 def EnableRepositoryExtension(self, key, value='true', version=1):
2098 """Enable git repository extension |key| with |value|.
2099
2100 Args:
2101 key: The extension to enabled. Omit the "extensions." prefix.
2102 value: The value to use for the extension.
2103 version: The minimum git repository version needed.
2104 """
2105 # Make sure the git repo version is new enough already.
2106 found_version = self.config.GetInt('core.repositoryFormatVersion')
2107 if found_version is None:
2108 found_version = 0
2109 if found_version < version:
2110 self.config.SetString('core.repositoryFormatVersion', str(version))
2111
2112 # Enable the extension!
2113 self.config.SetString('extensions.%s' % (key,), value)
2114
Mike Frysinger50a81de2020-09-06 15:51:21 -04002115 def ResolveRemoteHead(self, name=None):
2116 """Find out what the default branch (HEAD) points to.
2117
2118 Normally this points to refs/heads/master, but projects are moving to main.
2119 Support whatever the server uses rather than hardcoding "master" ourselves.
2120 """
2121 if name is None:
2122 name = self.remote.name
2123
2124 # The output will look like (NB: tabs are separators):
2125 # ref: refs/heads/master HEAD
2126 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2127 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2128
2129 for line in output.splitlines():
2130 lhs, rhs = line.split('\t', 1)
2131 if rhs == 'HEAD' and lhs.startswith('ref:'):
2132 return lhs[4:].strip()
2133
2134 return None
2135
Zac Livingstone4332262017-06-16 08:56:09 -06002136 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002137 try:
2138 # if revision (sha or tag) is not present then following function
2139 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002140 self.bare_git.rev_list('-1', '--missing=allow-any',
2141 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00002142 if self.upstream:
Mike Frysingerdede5642022-07-10 04:56:04 -04002143 rev = self.GetRemote().ToLocal(self.upstream)
Xin Li0e776a52021-06-29 21:42:34 +00002144 self.bare_git.rev_list('-1', '--missing=allow-any',
2145 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002146 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002147 return True
2148 except GitError:
2149 # There is no such persistent revision. We have to fetch it.
2150 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002151
Julien Campergue335f5ef2013-10-16 11:02:35 +02002152 def _FetchArchive(self, tarpath, cwd=None):
2153 cmd = ['archive', '-v', '-o', tarpath]
2154 cmd.append('--remote=%s' % self.remote.url)
LaMont Jones8501d462022-06-22 19:21:15 +00002155 cmd.append('--prefix=%s/' % self.RelPath(local=False))
Julien Campergue335f5ef2013-10-16 11:02:35 +02002156 cmd.append(self.revisionExpr)
2157
2158 command = GitCommand(self, cmd, cwd=cwd,
2159 capture_stdout=True,
2160 capture_stderr=True)
2161
2162 if command.Wait() != 0:
2163 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2164
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002165 def _RemoteFetch(self, name=None,
2166 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002167 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002168 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002169 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002170 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002171 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002172 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002173 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002174 depth=None,
Mike Frysinger9d0e84c2019-03-18 21:27:54 -04002175 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002176 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002177 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002178 clone_filter=None,
2179 retry_fetches=2,
2180 retry_sleep_initial_sec=4.0,
2181 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002182 is_sha1 = False
2183 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002184 # The depth should not be used when fetching to a mirror because
2185 # it will result in a shallow repository that cannot be cloned or
2186 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002187 # The repo project should also never be synced with partial depth.
2188 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2189 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002190
Shawn Pearce69e04d82014-01-29 12:48:54 -08002191 if depth:
2192 current_branch_only = True
2193
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002194 if ID_RE.match(self.revisionExpr) is not None:
2195 is_sha1 = True
2196
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002197 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002198 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002199 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002200 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002201 elif self.upstream and self.upstream.startswith(R_TAGS):
2202 # This is a tag and its commit id should never change.
2203 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002204
2205 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002206 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002207 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002208 print('Skipped fetching project %s (already have persistent ref)'
2209 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002210 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002211 if is_sha1 and not depth:
2212 # When syncing a specific commit and --depth is not set:
2213 # * if upstream is explicitly specified and is not a sha1, fetch only
2214 # upstream as users expect only upstream to be fetch.
2215 # Note: The commit might not be in upstream in which case the sync
2216 # will fail.
2217 # * otherwise, fetch all branches to make sure we end up with the
2218 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002219 if self.upstream:
2220 current_branch_only = not ID_RE.match(self.upstream)
2221 else:
2222 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002223
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002224 if not name:
2225 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002226
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002227 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002228 if not remote.PreConnectFetch(ssh_proxy):
2229 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002230
Shawn O. Pearce88443382010-10-08 10:02:09 +02002231 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002232 if alt_dir and 'objects' == os.path.basename(alt_dir):
2233 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002234 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002235
David Pursehouse8a68ff92012-09-24 12:15:13 +09002236 all_refs = self.bare_ref.all
2237 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002238 tmp = set()
2239
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302240 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002241 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002242 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002243 all_refs[r] = ref_id
2244 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002245 continue
2246
David Pursehouse8a68ff92012-09-24 12:15:13 +09002247 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002248 continue
2249
David Pursehouse8a68ff92012-09-24 12:15:13 +09002250 r = 'refs/_alt/%s' % ref_id
2251 all_refs[r] = ref_id
2252 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002253 tmp.add(r)
2254
heping3d7bbc92017-04-12 19:51:47 +08002255 tmp_packed_lines = []
2256 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002257
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302258 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002259 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002260 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002261 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002262 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002263
heping3d7bbc92017-04-12 19:51:47 +08002264 tmp_packed = ''.join(tmp_packed_lines)
2265 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002266 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002267 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002268 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002269
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002270 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002271
Xin Li745be2e2019-06-03 11:24:30 -07002272 if clone_filter:
2273 git_require((2, 19, 0), fail=True, msg='partial clones')
2274 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002275 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002276
Conley Owensf97e8382015-01-21 11:12:46 -08002277 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002278 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002279 else:
2280 # If this repo has shallow objects, then we don't know which refs have
2281 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2282 # do this with projects that don't have shallow objects, since it is less
2283 # efficient.
2284 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2285 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002286
Mike Frysinger4847e052020-02-22 00:07:35 -05002287 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002288 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002289 if not quiet and sys.stdout.isatty():
2290 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002291 if not self.worktree:
2292 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002293 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002294
Mike Frysinger9d0e84c2019-03-18 21:27:54 -04002295 if force_sync:
2296 cmd.append('--force')
2297
David Pursehouse74cfd272015-10-14 10:50:15 +09002298 if prune:
2299 cmd.append('--prune')
2300
LaMont Jonesff6b1da2022-06-01 21:03:34 +00002301 # Always pass something for --recurse-submodules, git with GIT_DIR behaves
2302 # incorrectly when not given `--recurse-submodules=no`. (b/218891912)
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002303 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002304
Kuang-che Wu6856f982019-11-25 12:37:55 +08002305 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002306 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002307 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002308 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002309 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002310 spec.append('tag')
2311 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002312
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302313 if self.manifest.IsMirror and not current_branch_only:
2314 branch = None
2315 else:
2316 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002317 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002318 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002319 # Shallow checkout of a specific commit, fetch from that commit and not
2320 # the heads only as the commit might be deeper in the history.
2321 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002322 if self.upstream:
2323 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002324 else:
2325 if is_sha1:
2326 branch = self.upstream
2327 if branch is not None and branch.strip():
2328 if not branch.startswith('refs/'):
2329 branch = R_HEADS + branch
2330 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2331
2332 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2333 # whole repo.
2334 if self.manifest.IsMirror and not spec:
2335 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2336
2337 # If using depth then we should not get all the tags since they may
2338 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002339 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002340 cmd.append('--no-tags')
2341 else:
2342 cmd.append('--tags')
2343 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2344
Conley Owens80b87fe2014-05-09 17:13:44 -07002345 cmd.extend(spec)
2346
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002347 # At least one retry minimum due to git remote prune.
2348 retry_fetches = max(retry_fetches, 2)
2349 retry_cur_sleep = retry_sleep_initial_sec
2350 ok = prune_tried = False
2351 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002352 gitcmd = GitCommand(
2353 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2354 ssh_proxy=ssh_proxy,
2355 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002356 if gitcmd.stdout and not quiet and output_redir:
2357 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002358 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002359 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002360 ok = True
2361 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002362
2363 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002364 elif (gitcmd.stdout and
2365 'error:' in gitcmd.stdout and
2366 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002367 # Fallthru to sleep+retry logic at the bottom.
2368 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002369
Mike Frysinger6823bc22021-04-15 02:06:28 -04002370 # Try to prune remote branches once in case there are conflicts.
2371 # For example, if the remote had refs/heads/upstream, but deleted that and
2372 # now has refs/heads/upstream/foo.
2373 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002374 'error:' in gitcmd.stdout and
2375 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002376 not prune_tried):
2377 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002378 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002379 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002380 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002381 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002382 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002383 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002384 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002385 continue
Brian Harring14a66742012-09-28 20:21:57 -07002386 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002387 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2388 # in sha1 mode, we just tried sync'ing from the upstream field; it
2389 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002390 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002391 elif ret < 0:
2392 # Git died with a signal, exit immediately
2393 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002394
2395 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002396 if not verbose and gitcmd.stdout:
2397 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002398 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002399 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2400 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002401 time.sleep(retry_cur_sleep)
2402 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2403 MAXIMUM_RETRY_SLEEP_SEC)
2404 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2405 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002406
2407 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002408 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002409 if old_packed != '':
2410 _lwrite(packed_refs, old_packed)
2411 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002412 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002413 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002414
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002415 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002416 # We just synced the upstream given branch; verify we
2417 # got what we wanted, else trigger a second run of all
2418 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002419 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002420 # Sync the current branch only with depth set to None.
2421 # We always pass depth=None down to avoid infinite recursion.
2422 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002423 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002424 current_branch_only=current_branch_only and depth,
2425 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002426 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002427
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002428 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002429
Mike Frysingere50b6a72020-02-19 01:45:48 -05002430 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002431 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002432 return False
2433
Mike Frysingerdede5642022-07-10 04:56:04 -04002434 remote = self.GetRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002435 bundle_url = remote.url + '/clone.bundle'
2436 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002437 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2438 'persistent-http',
2439 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002440 return False
2441
2442 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2443 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2444
2445 exist_dst = os.path.exists(bundle_dst)
2446 exist_tmp = os.path.exists(bundle_tmp)
2447
2448 if not initial and not exist_dst and not exist_tmp:
2449 return False
2450
2451 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002452 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2453 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002454 if not exist_dst:
2455 return False
2456
2457 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002458 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002459 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002460 if not quiet and sys.stdout.isatty():
2461 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002462 if not self.worktree:
2463 cmd.append('--update-head-ok')
2464 cmd.append(bundle_dst)
2465 for f in remote.fetch:
2466 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002467 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002468
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002469 ok = GitCommand(
2470 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002471 platform_utils.remove(bundle_dst, missing_ok=True)
2472 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002473 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002474
Mike Frysingere50b6a72020-02-19 01:45:48 -05002475 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002476 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002477
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002478 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002479 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002480 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002481 if os.path.exists(tmpPath):
2482 size = os.stat(tmpPath).st_size
2483 if size >= 1024:
2484 cmd += ['--continue-at', '%d' % (size,)]
2485 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002486 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002487 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002488 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002489 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002490 if proxy:
2491 cmd += ['--proxy', proxy]
2492 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2493 cmd += ['--proxy', os.environ['http_proxy']]
2494 if srcUrl.startswith('persistent-https'):
2495 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2496 elif srcUrl.startswith('persistent-http'):
2497 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002498 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002499
Joanna Wanga6c52f52022-11-03 16:51:19 -04002500 proc = None
2501 with Trace('Fetching bundle: %s', ' '.join(cmd)):
2502 if verbose:
2503 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2504 stdout = None if verbose else subprocess.PIPE
2505 stderr = None if verbose else subprocess.STDOUT
2506 try:
2507 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
2508 except OSError:
2509 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002510
Mike Frysingere50b6a72020-02-19 01:45:48 -05002511 (output, _) = proc.communicate()
2512 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002513
Dave Borowitz137d0132015-01-02 11:12:54 -08002514 if curlret == 22:
2515 # From curl man page:
2516 # 22: HTTP page not retrieved. The requested url was not found or
2517 # returned another error with the HTTP error code being 400 or above.
2518 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002519 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002520 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2521 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002522 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002523 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002524 elif curlret and not verbose and output:
2525 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002526
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002527 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002528 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002529 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002530 return True
2531 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002532 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002533 return False
2534 else:
2535 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002536
Kris Giesingc8d882a2014-12-23 13:02:32 -08002537 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002538 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002539 with open(path, 'rb') as f:
2540 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002541 return True
2542 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002543 if not quiet:
2544 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002545 return False
2546 except OSError:
2547 return False
2548
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002549 def _Checkout(self, rev, quiet=False):
2550 cmd = ['checkout']
2551 if quiet:
2552 cmd.append('-q')
2553 cmd.append(rev)
2554 cmd.append('--')
2555 if GitCommand(self, cmd).Wait() != 0:
2556 if self._allrefs:
2557 raise GitError('%s checkout %s ' % (self.name, rev))
2558
Mike Frysinger915fda12020-03-22 12:15:20 -04002559 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002560 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002561 if ffonly:
2562 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002563 if record_origin:
2564 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002565 cmd.append(rev)
2566 cmd.append('--')
2567 if GitCommand(self, cmd).Wait() != 0:
2568 if self._allrefs:
2569 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2570
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302571 def _LsRemote(self, refs):
2572 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302573 p = GitCommand(self, cmd, capture_stdout=True)
2574 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002575 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302576 return None
2577
Anthony King7bdac712014-07-16 12:56:40 +01002578 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002579 cmd = ['revert']
2580 cmd.append('--no-edit')
2581 cmd.append(rev)
2582 cmd.append('--')
2583 if GitCommand(self, cmd).Wait() != 0:
2584 if self._allrefs:
2585 raise GitError('%s revert %s ' % (self.name, rev))
2586
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002587 def _ResetHard(self, rev, quiet=True):
2588 cmd = ['reset', '--hard']
2589 if quiet:
2590 cmd.append('-q')
2591 cmd.append(rev)
2592 if GitCommand(self, cmd).Wait() != 0:
2593 raise GitError('%s reset --hard %s ' % (self.name, rev))
2594
Martin Kellye4e94d22017-03-21 16:05:12 -07002595 def _SyncSubmodules(self, quiet=True):
2596 cmd = ['submodule', 'update', '--init', '--recursive']
2597 if quiet:
2598 cmd.append('-q')
2599 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002600 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002601
Anthony King7bdac712014-07-16 12:56:40 +01002602 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002603 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002604 if onto is not None:
2605 cmd.extend(['--onto', onto])
2606 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002607 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608 raise GitError('%s rebase %s ' % (self.name, upstream))
2609
Pierre Tardy3d125942012-05-04 12:18:12 +02002610 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002611 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002612 if ffonly:
2613 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002614 if GitCommand(self, cmd).Wait() != 0:
2615 raise GitError('%s merge %s ' % (self.name, head))
2616
David Pursehousee8ace262020-02-13 12:41:15 +09002617 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002618 init_git_dir = not os.path.exists(self.gitdir)
2619 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002620 try:
2621 # Initialize the bare repository, which contains all of the objects.
2622 if init_obj_dir:
2623 os.makedirs(self.objdir)
2624 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002625
Mike Frysinger333c0a42021-11-15 12:39:00 -05002626 self._UpdateHooks(quiet=quiet)
2627
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002628 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002629 # Enable per-worktree config file support if possible. This is more a
2630 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002631 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002632 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002633
Kevin Degib1a07b82015-07-27 13:33:43 -06002634 # If we have a separate directory to hold refs, initialize it as well.
2635 if self.objdir != self.gitdir:
2636 if init_git_dir:
2637 os.makedirs(self.gitdir)
2638
2639 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002640 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002641 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002642 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002643 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002644 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002645 print("Retrying clone after deleting %s" %
2646 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002647 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002648 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2649 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002650 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002651 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002652 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2653 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002654 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002655 raise e
2656 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002657
Kevin Degi384b3c52014-10-16 16:02:58 -06002658 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002659 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002660 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002661
LaMont Jonescc879a92021-11-18 22:40:18 +00002662 def _expanded_ref_dirs():
2663 """Iterate through the possible git reference directory paths."""
2664 name = self.name + '.git'
2665 yield mirror_git or os.path.join(ref_dir, name)
2666 for prefix in '', self.remote.name:
2667 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2668 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002669
LaMont Jonescc879a92021-11-18 22:40:18 +00002670 if ref_dir or mirror_git:
2671 found_ref_dir = None
2672 for path in _expanded_ref_dirs():
2673 if os.path.exists(path):
2674 found_ref_dir = path
2675 break
2676 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002677
Kevin Degib1a07b82015-07-27 13:33:43 -06002678 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002679 if not os.path.isabs(ref_dir):
2680 # The alternate directory is relative to the object database.
2681 ref_dir = os.path.relpath(ref_dir,
2682 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002683 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002684 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002685
Kevin Degib1a07b82015-07-27 13:33:43 -06002686 m = self.manifest.manifestProject.config
2687 for key in ['user.name', 'user.email']:
2688 if m.Has(key, include_defaults=False):
2689 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002690 if not self.manifest.EnableGitLfs:
2691 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2692 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002693 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002694 except Exception:
2695 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002696 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002697 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002698 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002699 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002700
David Pursehousee8ace262020-02-13 12:41:15 +09002701 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002702 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002703 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002704
David Pursehousee8ace262020-02-13 12:41:15 +09002705 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002706 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002707 if not os.path.exists(hooks):
2708 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002709
2710 # Delete sample hooks. They're noise.
2711 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002712 try:
2713 platform_utils.remove(hook, missing_ok=True)
2714 except PermissionError:
2715 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002716
Jonathan Nieder93719792015-03-17 11:29:58 -07002717 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002718 name = os.path.basename(stock_hook)
2719
Victor Boivie65e0f352011-04-18 11:23:29 +02002720 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002721 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002722 # Don't install a Gerrit Code Review hook if this
2723 # project does not appear to use it for reviews.
2724 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002725 # Since the manifest project is one of those, but also
2726 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002727 continue
2728
2729 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002730 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002731 continue
2732 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002733 # If the files are the same, we'll leave it alone. We create symlinks
2734 # below by default but fallback to hardlinks if the OS blocks them.
2735 # So if we're here, it's probably because we made a hardlink below.
2736 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002737 if not quiet:
2738 _warn("%s: Not replacing locally modified %s hook",
LaMont Jones8501d462022-06-22 19:21:15 +00002739 self.RelPath(local=False), name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002740 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002741 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002742 platform_utils.symlink(
2743 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002744 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002745 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002746 try:
2747 os.link(stock_hook, dst)
2748 except OSError:
2749 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002750 else:
2751 raise
2752
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002753 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002754 if self.remote.url:
Mike Frysingerdede5642022-07-10 04:56:04 -04002755 remote = self.GetRemote()
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002756 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002757 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002758 remote.review = self.remote.review
2759 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002760
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002761 if self.worktree:
2762 remote.ResetFetch(mirror=False)
2763 else:
2764 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002765 remote.Save()
2766
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002767 def _InitMRef(self):
Mike Frysinger790f4ce2020-12-07 22:04:55 -05002768 """Initialize the pseudo m/<manifest branch> ref."""
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002769 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002770 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002771 # Set up the m/ space to point to the worktree-specific ref space.
2772 # We'll update the worktree-specific ref space on each checkout.
2773 ref = R_M + self.manifest.branch
2774 if not self.bare_ref.symref(ref):
2775 self.bare_git.symbolic_ref(
2776 '-m', 'redirecting to worktree scope',
2777 ref, R_WORKTREE_M + self.manifest.branch)
2778
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002779 # We can't update this ref with git worktrees until it exists.
2780 # We'll wait until the initial checkout to set it.
2781 if not os.path.exists(self.worktree):
2782 return
2783
2784 base = R_WORKTREE_M
2785 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002786
2787 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002788 else:
2789 base = R_M
2790 active_git = self.bare_git
2791
2792 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002793
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002794 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002795 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002796
Remy Böhmer1469c282020-12-15 18:49:02 +01002797 def _InitAnyMRef(self, ref, active_git, detach=False):
Mike Frysinger790f4ce2020-12-07 22:04:55 -05002798 """Initialize |ref| in |active_git| to the value in the manifest.
2799
2800 This points |ref| to the <project> setting in the manifest.
2801
2802 Args:
2803 ref: The branch to update.
2804 active_git: The git repository to make updates in.
2805 detach: Whether to update target of symbolic refs, or overwrite the ref
2806 directly (and thus make it non-symbolic).
2807 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002808 cur = self.bare_ref.symref(ref)
2809
2810 if self.revisionId:
2811 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2812 msg = 'manifest set to %s' % self.revisionId
2813 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002814 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002815 else:
Mike Frysingerdede5642022-07-10 04:56:04 -04002816 remote = self.GetRemote()
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002817 dst = remote.ToLocal(self.revisionExpr)
2818 if cur != dst:
2819 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002820 if detach:
2821 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2822 else:
2823 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002824
Mike Frysingerc72bd842021-11-14 03:58:00 -05002825 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002826 # Git worktrees don't use symlinks to share at all.
2827 if self.use_git_worktrees:
2828 return
2829
Mike Frysingerd33dce02021-12-20 18:16:33 -05002830 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002831 # Try to self-heal a bit in simple cases.
2832 dst_path = os.path.join(destdir, name)
2833 src_path = os.path.join(srcdir, name)
2834
Mike Frysingered4f2112020-02-11 23:06:29 -05002835 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002836 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002837 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002838 # Fail if the links are pointing to the wrong place
2839 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002840 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002841 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002842 'work tree. If you\'re comfortable with the '
2843 'possibility of losing the work tree\'s git metadata,'
2844 ' use `repo sync --force-sync {0}` to '
LaMont Jones8501d462022-06-22 19:21:15 +00002845 'proceed.'.format(self.RelPath(local=False)))
Kevin Degi384b3c52014-10-16 16:02:58 -06002846
Mike Frysingerc72bd842021-11-14 03:58:00 -05002847 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002848 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2849
2850 Args:
2851 gitdir: The bare git repository. Must already be initialized.
2852 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002853 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2854 This saves you the effort of initializing |dotgit| yourself.
2855 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002856 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002857 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002858
2859 to_copy = []
2860 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002861 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002862
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002863 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002864 for name in set(to_copy).union(to_symlink):
2865 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002866 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002867 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002868
Kevin Degi384b3c52014-10-16 16:02:58 -06002869 if os.path.lexists(dst):
2870 continue
David James8d201162013-10-11 17:03:19 -07002871
2872 # If the source dir doesn't exist, create an empty dir.
2873 if name in symlink_dirs and not os.path.lexists(src):
2874 os.makedirs(src)
2875
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002876 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002877 platform_utils.symlink(
2878 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002879 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002880 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002881 shutil.copytree(src, dst)
2882 elif os.path.isfile(src):
2883 shutil.copy(src, dst)
2884
David James8d201162013-10-11 17:03:19 -07002885 except OSError as e:
2886 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002887 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002888 else:
2889 raise
2890
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002891 def _InitGitWorktree(self):
2892 """Init the project using git worktrees."""
2893 self.bare_git.worktree('prune')
2894 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2895 self.worktree, self.GetRevisionId())
2896
2897 # Rewrite the internal state files to use relative paths between the
2898 # checkouts & worktrees.
2899 dotgit = os.path.join(self.worktree, '.git')
2900 with open(dotgit, 'r') as fp:
2901 # Figure out the checkout->worktree path.
2902 setting = fp.read()
2903 assert setting.startswith('gitdir:')
2904 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002905 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2906 # of file permissions. Delete it and recreate it from scratch to avoid.
2907 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002908 # Use relative path from checkout->worktree & maintain Unix line endings
2909 # on all OS's to match git behavior.
2910 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002911 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2912 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002913 # Use relative path from worktree->checkout & maintain Unix line endings
2914 # on all OS's to match git behavior.
2915 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002916 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2917
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002918 self._InitMRef()
2919
Martin Kellye4e94d22017-03-21 16:05:12 -07002920 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002921 """Setup the worktree .git path.
2922
2923 This is the user-visible path like src/foo/.git/.
2924
2925 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2926 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2927
2928 Older checkouts had .git/ directories. If we see that, migrate it.
2929
2930 This also handles changes in the manifest. Maybe this project was backed
2931 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2932 the path we point to under .repo/projects/ to match.
2933 """
2934 dotgit = os.path.join(self.worktree, '.git')
2935
2936 # If using an old layout style (a directory), migrate it.
2937 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2938 self._MigrateOldWorkTreeGitDir(dotgit)
2939
2940 init_dotgit = not os.path.exists(dotgit)
2941 if self.use_git_worktrees:
2942 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002943 self._InitGitWorktree()
2944 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002945 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002946 if not init_dotgit:
2947 # See if the project has changed.
2948 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2949 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002950
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002951 if init_dotgit or not os.path.exists(dotgit):
2952 os.makedirs(self.worktree, exist_ok=True)
2953 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002954
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002955 if init_dotgit:
2956 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002957
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002958 # Finish checking out the worktree.
2959 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2960 if GitCommand(self, cmd).Wait() != 0:
2961 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002962
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002963 if submodules:
2964 self._SyncSubmodules(quiet=True)
2965 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002966
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002967 @classmethod
2968 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2969 """Migrate the old worktree .git/ dir style to a symlink.
2970
2971 This logic specifically only uses state from |dotgit| to figure out where to
2972 move content and not |self|. This way if the backing project also changed
2973 places, we only do the .git/ dir to .git symlink migration here. The path
2974 updates will happen independently.
2975 """
2976 # Figure out where in .repo/projects/ it's pointing to.
2977 if not os.path.islink(os.path.join(dotgit, 'refs')):
2978 raise GitError(f'{dotgit}: unsupported checkout state')
2979 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2980
2981 # Remove known symlink paths that exist in .repo/projects/.
2982 KNOWN_LINKS = {
2983 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2984 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2985 }
2986 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2987 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002988 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2989 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002990 }
2991
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002992 # First see if we'd succeed before starting the migration.
2993 unknown_paths = []
2994 for name in platform_utils.listdir(dotgit):
2995 # Ignore all temporary/backup names. These are common with vim & emacs.
2996 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2997 continue
2998
2999 dotgit_path = os.path.join(dotgit, name)
3000 if name in KNOWN_LINKS:
3001 if not platform_utils.islink(dotgit_path):
3002 unknown_paths.append(f'{dotgit_path}: should be a symlink')
3003 else:
3004 gitdir_path = os.path.join(gitdir, name)
3005 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
3006 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
3007 if unknown_paths:
3008 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
3009
Mike Frysinger2a089cf2021-11-13 23:29:42 -05003010 # Now walk the paths and sync the .git/ to .repo/projects/.
3011 for name in platform_utils.listdir(dotgit):
3012 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05003013
3014 # Ignore all temporary/backup names. These are common with vim & emacs.
3015 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
3016 platform_utils.remove(dotgit_path)
3017 elif name in KNOWN_LINKS:
3018 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05003019 else:
3020 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05003021 platform_utils.remove(gitdir_path, missing_ok=True)
3022 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05003023
3024 # Now that the dir should be empty, clear it out, and symlink it over.
3025 platform_utils.rmdir(dotgit)
3026 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003027
Renaud Paquay788e9622017-01-27 11:41:12 -08003028 def _get_symlink_error_message(self):
3029 if platform_utils.isWindows():
3030 return ('Unable to create symbolic link. Please re-run the command as '
3031 'Administrator, or see '
3032 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
3033 'for other options.')
3034 return 'filesystem must support symlinks'
3035
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003036 def _revlist(self, *args, **kw):
3037 a = []
3038 a.extend(args)
3039 a.append('--')
3040 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003041
3042 @property
3043 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07003044 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003045
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003046 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003047 """Get logs between two revisions of this project."""
3048 comp = '..'
3049 if rev1:
3050 revs = [rev1]
3051 if rev2:
3052 revs.extend([comp, rev2])
3053 cmd = ['log', ''.join(revs)]
3054 out = DiffColoring(self.config)
3055 if out.is_on and color:
3056 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003057 if pretty_format is not None:
3058 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003059 if oneline:
3060 cmd.append('--oneline')
3061
3062 try:
3063 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
3064 if log.Wait() == 0:
3065 return log.stdout
3066 except GitError:
3067 # worktree may not exist if groups changed for example. In that case,
3068 # try in gitdir instead.
3069 if not os.path.exists(self.worktree):
3070 return self.bare_git.log(*cmd[1:])
3071 else:
3072 raise
3073 return None
3074
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003075 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3076 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003077 """Get the list of logs from this revision to given revisionId"""
3078 logs = {}
3079 selfId = self.GetRevisionId(self._allrefs)
3080 toId = toProject.GetRevisionId(toProject._allrefs)
3081
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003082 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3083 pretty_format=pretty_format)
3084 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3085 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003086 return logs
3087
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003088 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003089
David James8d201162013-10-11 17:03:19 -07003090 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003091 self._project = project
3092 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003093 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003094
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09003095 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
3096 def __getstate__(self):
3097 return (self._project, self._bare, self._gitdir)
3098
3099 def __setstate__(self, state):
3100 self._project, self._bare, self._gitdir = state
3101
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003102 def LsOthers(self):
3103 p = GitCommand(self._project,
3104 ['ls-files',
3105 '-z',
3106 '--others',
3107 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003108 bare=False,
David James8d201162013-10-11 17:03:19 -07003109 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003110 capture_stdout=True,
3111 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003112 if p.Wait() == 0:
3113 out = p.stdout
3114 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003115 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003116 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003117 return []
3118
3119 def DiffZ(self, name, *args):
3120 cmd = [name]
3121 cmd.append('-z')
Eli Ribble7b4f0192019-05-02 18:21:42 -07003122 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003123 cmd.extend(args)
3124 p = GitCommand(self._project,
3125 cmd,
David James8d201162013-10-11 17:03:19 -07003126 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003127 bare=False,
3128 capture_stdout=True,
3129 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05003130 p.Wait()
3131 r = {}
3132 out = p.stdout
3133 if out:
3134 out = iter(out[:-1].split('\0'))
3135 while out:
3136 try:
3137 info = next(out)
3138 path = next(out)
3139 except StopIteration:
3140 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003141
Mike Frysinger84230002021-02-16 17:08:35 -05003142 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003143
Mike Frysinger84230002021-02-16 17:08:35 -05003144 def __init__(self, path, omode, nmode, oid, nid, state):
3145 self.path = path
3146 self.src_path = None
3147 self.old_mode = omode
3148 self.new_mode = nmode
3149 self.old_id = oid
3150 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003151
Mike Frysinger84230002021-02-16 17:08:35 -05003152 if len(state) == 1:
3153 self.status = state
3154 self.level = None
3155 else:
3156 self.status = state[:1]
3157 self.level = state[1:]
3158 while self.level.startswith('0'):
3159 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003160
Mike Frysinger84230002021-02-16 17:08:35 -05003161 info = info[1:].split(' ')
3162 info = _Info(path, *info)
3163 if info.status in ('R', 'C'):
3164 info.src_path = info.path
3165 info.path = next(out)
3166 r[info.path] = info
3167 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003168
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003169 def GetDotgitPath(self, subpath=None):
3170 """Return the full path to the .git dir.
3171
3172 As a convenience, append |subpath| if provided.
3173 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003174 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003175 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003176 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003177 dotgit = os.path.join(self._project.worktree, '.git')
3178 if os.path.isfile(dotgit):
3179 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3180 with open(dotgit) as fp:
3181 setting = fp.read()
3182 assert setting.startswith('gitdir:')
3183 gitdir = setting.split(':', 1)[1].strip()
3184 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3185
3186 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3187
3188 def GetHead(self):
3189 """Return the ref that HEAD points to."""
3190 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003191 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003192 with open(path) as fd:
3193 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003194 except IOError as e:
3195 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003196 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303197 line = line.decode()
3198 except AttributeError:
3199 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003200 if line.startswith('ref: '):
3201 return line[5:-1]
3202 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003203
3204 def SetHead(self, ref, message=None):
3205 cmdv = []
3206 if message is not None:
3207 cmdv.extend(['-m', message])
3208 cmdv.append(HEAD)
3209 cmdv.append(ref)
3210 self.symbolic_ref(*cmdv)
3211
3212 def DetachHead(self, new, message=None):
3213 cmdv = ['--no-deref']
3214 if message is not None:
3215 cmdv.extend(['-m', message])
3216 cmdv.append(HEAD)
3217 cmdv.append(new)
3218 self.update_ref(*cmdv)
3219
3220 def UpdateRef(self, name, new, old=None,
3221 message=None,
3222 detach=False):
3223 cmdv = []
3224 if message is not None:
3225 cmdv.extend(['-m', message])
3226 if detach:
3227 cmdv.append('--no-deref')
3228 cmdv.append(name)
3229 cmdv.append(new)
3230 if old is not None:
3231 cmdv.append(old)
3232 self.update_ref(*cmdv)
3233
3234 def DeleteRef(self, name, old=None):
3235 if not old:
3236 old = self.rev_parse(name)
3237 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003238 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003239
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003240 def rev_list(self, *args, **kw):
3241 if 'format' in kw:
3242 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3243 else:
3244 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003245 cmdv.extend(args)
3246 p = GitCommand(self._project,
3247 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003248 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003249 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003250 capture_stdout=True,
3251 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003252 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003253 raise GitError('%s rev-list %s: %s' %
3254 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003255 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003256
3257 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003258 """Allow arbitrary git commands using pythonic syntax.
3259
3260 This allows you to do things like:
3261 git_obj.rev_parse('HEAD')
3262
3263 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3264 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003265 Any other positional arguments will be passed to the git command, and the
3266 following keyword arguments are supported:
3267 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003268
3269 Args:
3270 name: The name of the git command to call. Any '_' characters will
3271 be replaced with '-'.
3272
3273 Returns:
3274 A callable object that will try to call git with the named command.
3275 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003276 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003277
Dave Borowitz091f8932012-10-23 17:01:04 -07003278 def runner(*args, **kwargs):
3279 cmdv = []
3280 config = kwargs.pop('config', None)
3281 for k in kwargs:
3282 raise TypeError('%s() got an unexpected keyword argument %r'
3283 % (name, k))
3284 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303285 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003286 cmdv.append('-c')
3287 cmdv.append('%s=%s' % (k, v))
3288 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003289 cmdv.extend(args)
3290 p = GitCommand(self._project,
3291 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003292 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003293 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003294 capture_stdout=True,
3295 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003296 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003297 raise GitError('%s %s: %s' %
3298 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003299 r = p.stdout
3300 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3301 return r[:-1]
3302 return r
3303 return runner
3304
3305
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003306class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003307
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003308 def __str__(self):
3309 return 'prior sync failed; rebase still in progress'
3310
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003311
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003312class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003313
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003314 def __str__(self):
3315 return 'contains uncommitted changes'
3316
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003317
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003318class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003319
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003320 def __init__(self, project, text):
3321 self.project = project
3322 self.text = text
3323
3324 def Print(self, syncbuf):
LaMont Jones8501d462022-06-22 19:21:15 +00003325 syncbuf.out.info('%s/: %s', self.project.RelPath(local=False), self.text)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003326 syncbuf.out.nl()
3327
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003328
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003329class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003330
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003331 def __init__(self, project, why):
3332 self.project = project
3333 self.why = why
3334
3335 def Print(self, syncbuf):
3336 syncbuf.out.fail('error: %s/: %s',
LaMont Jones8501d462022-06-22 19:21:15 +00003337 self.project.RelPath(local=False),
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003338 str(self.why))
3339 syncbuf.out.nl()
3340
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003341
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003342class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003343
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003344 def __init__(self, project, action):
3345 self.project = project
3346 self.action = action
3347
3348 def Run(self, syncbuf):
3349 out = syncbuf.out
LaMont Jones8501d462022-06-22 19:21:15 +00003350 out.project('project %s/', self.project.RelPath(local=False))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003351 out.nl()
3352 try:
3353 self.action()
3354 out.nl()
3355 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003356 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003357 out.nl()
3358 return False
3359
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003360
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003361class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003362
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003363 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003364 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003365 self.project = self.printer('header', attr='bold')
3366 self.info = self.printer('info')
3367 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003368
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003369
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003370class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003371
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003372 def __init__(self, config, detach_head=False):
3373 self._messages = []
3374 self._failures = []
3375 self._later_queue1 = []
3376 self._later_queue2 = []
3377
3378 self.out = _SyncColoring(config)
3379 self.out.redirect(sys.stderr)
3380
3381 self.detach_head = detach_head
3382 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003383 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003384
3385 def info(self, project, fmt, *args):
3386 self._messages.append(_InfoMessage(project, fmt % args))
3387
3388 def fail(self, project, err=None):
3389 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003390 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003391
3392 def later1(self, project, what):
3393 self._later_queue1.append(_Later(project, what))
3394
3395 def later2(self, project, what):
3396 self._later_queue2.append(_Later(project, what))
3397
3398 def Finish(self):
3399 self._PrintMessages()
3400 self._RunLater()
3401 self._PrintMessages()
3402 return self.clean
3403
David Rileye0684ad2017-04-05 00:02:59 -07003404 def Recently(self):
3405 recent_clean = self.recent_clean
3406 self.recent_clean = True
3407 return recent_clean
3408
3409 def _MarkUnclean(self):
3410 self.clean = False
3411 self.recent_clean = False
3412
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003413 def _RunLater(self):
3414 for q in ['_later_queue1', '_later_queue2']:
3415 if not self._RunQueue(q):
3416 return
3417
3418 def _RunQueue(self, queue):
3419 for m in getattr(self, queue):
3420 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003421 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003422 return False
3423 setattr(self, queue, [])
3424 return True
3425
3426 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003427 if self._messages or self._failures:
3428 if os.isatty(2):
3429 self.out.write(progress.CSI_ERASE_LINE)
3430 self.out.write('\r')
3431
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003432 for m in self._messages:
3433 m.Print(self)
3434 for m in self._failures:
3435 m.Print(self)
3436
3437 self._messages = []
3438 self._failures = []
3439
3440
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003441class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003442 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003443
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003444 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003445 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003446 manifest=manifest,
3447 name=name,
3448 gitdir=gitdir,
3449 objdir=gitdir,
3450 worktree=worktree,
3451 remote=RemoteSpec('origin'),
3452 relpath='.repo/%s' % name,
3453 revisionExpr='refs/heads/master',
3454 revisionId=None,
3455 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003456
3457 def PreSync(self):
3458 if self.Exists:
3459 cb = self.CurrentBranch
3460 if cb:
3461 base = self.GetBranch(cb).merge
3462 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003463 self.revisionExpr = base
3464 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003465
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003466 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003467 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003468 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003469 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003470 return False
3471
David Pursehouse8a68ff92012-09-24 12:15:13 +09003472 all_refs = self.bare_ref.all
3473 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003474 head = self.work_git.GetHead()
3475 if head.startswith(R_HEADS):
3476 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003477 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003478 except KeyError:
3479 head = None
3480
3481 if revid == head:
3482 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003483 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003484 return True
3485 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003486
3487
3488class RepoProject(MetaProject):
3489 """The MetaProject for repo itself."""
3490
3491 @property
3492 def LastFetch(self):
3493 try:
3494 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3495 return os.path.getmtime(fh)
3496 except OSError:
3497 return 0
3498
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +01003499
LaMont Jones9b72cf22022-03-29 21:54:22 +00003500class ManifestProject(MetaProject):
3501 """The MetaProject for manifests."""
3502
3503 def MetaBranchSwitch(self, submodules=False):
3504 """Prepare for manifest branch switch."""
3505
3506 # detach and delete manifest branch, allowing a new
3507 # branch to take over
3508 syncbuf = SyncBuffer(self.config, detach_head=True)
3509 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3510 syncbuf.Finish()
3511
3512 return GitCommand(self,
3513 ['update-ref', '-d', 'refs/heads/default'],
3514 capture_stdout=True,
3515 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003516
3517 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003518 def standalone_manifest_url(self):
3519 """The URL of the standalone manifest, or None."""
LaMont Jones55ee3042022-04-06 17:10:21 +00003520 return self.config.GetString('manifest.standalone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003521
3522 @property
3523 def manifest_groups(self):
3524 """The manifest groups string."""
3525 return self.config.GetString('manifest.groups')
3526
3527 @property
3528 def reference(self):
3529 """The --reference for this manifest."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003530 return self.config.GetString('repo.reference')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003531
3532 @property
3533 def dissociate(self):
3534 """Whether to dissociate."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003535 return self.config.GetBoolean('repo.dissociate')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003536
3537 @property
3538 def archive(self):
3539 """Whether we use archive."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003540 return self.config.GetBoolean('repo.archive')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003541
3542 @property
3543 def mirror(self):
3544 """Whether we use mirror."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003545 return self.config.GetBoolean('repo.mirror')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003546
3547 @property
3548 def use_worktree(self):
3549 """Whether we use worktree."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003550 return self.config.GetBoolean('repo.worktree')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003551
3552 @property
3553 def clone_bundle(self):
3554 """Whether we use clone_bundle."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003555 return self.config.GetBoolean('repo.clonebundle')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003556
3557 @property
3558 def submodules(self):
3559 """Whether we use submodules."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003560 return self.config.GetBoolean('repo.submodules')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003561
3562 @property
3563 def git_lfs(self):
3564 """Whether we use git_lfs."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003565 return self.config.GetBoolean('repo.git-lfs')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003566
3567 @property
3568 def use_superproject(self):
3569 """Whether we use superproject."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003570 return self.config.GetBoolean('repo.superproject')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003571
3572 @property
3573 def partial_clone(self):
3574 """Whether this is a partial clone."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003575 return self.config.GetBoolean('repo.partialclone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003576
3577 @property
3578 def depth(self):
3579 """Partial clone depth."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003580 return self.config.GetString('repo.depth')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003581
3582 @property
3583 def clone_filter(self):
3584 """The clone filter."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003585 return self.config.GetString('repo.clonefilter')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003586
3587 @property
3588 def partial_clone_exclude(self):
3589 """Partial clone exclude string"""
Joanna Wangea5239d2022-12-02 09:47:29 -05003590 return self.config.GetString('repo.partialcloneexclude')
LaMont Jones4ada0432022-04-14 15:10:43 +00003591
3592 @property
3593 def manifest_platform(self):
3594 """The --platform argument from `repo init`."""
3595 return self.config.GetString('manifest.platform')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003596
3597 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003598 def _platform_name(self):
3599 """Return the name of the platform."""
3600 return platform.system().lower()
3601
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003602 def SyncWithPossibleInit(self, submanifest, verbose=False,
3603 current_branch_only=False, tags='', git_event_log=None):
3604 """Sync a manifestProject, possibly for the first time.
3605
3606 Call Sync() with arguments from the most recent `repo init`. If this is a
3607 new sub manifest, then inherit options from the parent's manifestProject.
3608
3609 This is used by subcmds.Sync() to do an initial download of new sub
3610 manifests.
3611
3612 Args:
3613 submanifest: an XmlSubmanifest, the submanifest to re-sync.
3614 verbose: a boolean, whether to show all output, rather than only errors.
3615 current_branch_only: a boolean, whether to only fetch the current manifest
3616 branch from the server.
3617 tags: a boolean, whether to fetch tags.
3618 git_event_log: an EventLog, for git tracing.
3619 """
3620 # TODO(lamontjones): when refactoring sync (and init?) consider how to
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003621 # better get the init options that we should use for new submanifests that
3622 # are added when syncing an existing workspace.
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003623 git_event_log = git_event_log or EventLog()
3624 spec = submanifest.ToSubmanifestSpec()
3625 # Use the init options from the existing manifestProject, or the parent if
3626 # it doesn't exist.
3627 #
3628 # Today, we only support changing manifest_groups on the sub-manifest, with
3629 # no supported-for-the-user way to change the other arguments from those
3630 # specified by the outermost manifest.
3631 #
3632 # TODO(lamontjones): determine which of these should come from the outermost
3633 # manifest and which should come from the parent manifest.
3634 mp = self if self.Exists else submanifest.parent.manifestProject
3635 return self.Sync(
3636 manifest_url=spec.manifestUrl,
3637 manifest_branch=spec.revision,
3638 standalone_manifest=mp.standalone_manifest_url,
3639 groups=mp.manifest_groups,
3640 platform=mp.manifest_platform,
3641 mirror=mp.mirror,
3642 dissociate=mp.dissociate,
3643 reference=mp.reference,
3644 worktree=mp.use_worktree,
3645 submodules=mp.submodules,
3646 archive=mp.archive,
3647 partial_clone=mp.partial_clone,
3648 clone_filter=mp.clone_filter,
3649 partial_clone_exclude=mp.partial_clone_exclude,
3650 clone_bundle=mp.clone_bundle,
3651 git_lfs=mp.git_lfs,
3652 use_superproject=mp.use_superproject,
3653 verbose=verbose,
3654 current_branch_only=current_branch_only,
3655 tags=tags,
3656 depth=mp.depth,
3657 git_event_log=git_event_log,
3658 manifest_name=spec.manifestName,
3659 this_manifest_only=True,
3660 outer_manifest=False,
3661 )
3662
LaMont Jones9b03f152022-03-29 23:01:18 +00003663 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003664 standalone_manifest=False, groups='', mirror=False, reference='',
3665 dissociate=False, worktree=False, submodules=False, archive=False,
3666 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003667 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3668 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jones55ee3042022-04-06 17:10:21 +00003669 git_event_log=None, platform='', manifest_name='default.xml',
3670 tags='', this_manifest_only=False, outer_manifest=True):
LaMont Jones9b03f152022-03-29 23:01:18 +00003671 """Sync the manifest and all submanifests.
3672
3673 Args:
3674 manifest_url: a string, the URL of the manifest project.
3675 manifest_branch: a string, the manifest branch to use.
3676 standalone_manifest: a boolean, whether to store the manifest as a static
3677 file.
3678 groups: a string, restricts the checkout to projects with the specified
3679 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003680 mirror: a boolean, whether to create a mirror of the remote repository.
3681 reference: a string, location of a repo instance to use as a reference.
3682 dissociate: a boolean, whether to dissociate from reference mirrors after
3683 clone.
3684 worktree: a boolean, whether to use git-worktree to manage projects.
3685 submodules: a boolean, whether sync submodules associated with the
3686 manifest project.
3687 archive: a boolean, whether to checkout each project as an archive. See
3688 git-archive.
3689 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003690 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003691 clone_filter: a string, filter to use with partial_clone.
3692 partial_clone_exclude : a string, comma-delimeted list of project namess
3693 to exclude from partial clone.
3694 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3695 git_lfs: a boolean, whether to enable git LFS support.
3696 use_superproject: a boolean, whether to use the manifest superproject to
3697 sync projects.
3698 verbose: a boolean, whether to show all output, rather than only errors.
3699 current_branch_only: a boolean, whether to only fetch the current manifest
3700 branch from the server.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003701 platform: a string, restrict the checkout to projects with the specified
3702 platform group.
LaMont Jones55ee3042022-04-06 17:10:21 +00003703 git_event_log: an EventLog, for git tracing.
LaMont Jones4ada0432022-04-14 15:10:43 +00003704 tags: a boolean, whether to fetch tags.
LaMont Jones409407a2022-04-05 21:21:56 +00003705 manifest_name: a string, the name of the manifest file to use.
3706 this_manifest_only: a boolean, whether to only operate on the current sub
3707 manifest.
3708 outer_manifest: a boolean, whether to start at the outermost manifest.
LaMont Jones9b03f152022-03-29 23:01:18 +00003709
3710 Returns:
3711 a boolean, whether the sync was successful.
3712 """
3713 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3714
LaMont Jones501733c2022-04-20 16:42:32 +00003715 groups = groups or self.manifest.GetDefaultGroupsStr(with_platform=False)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003716 platform = platform or 'auto'
LaMont Jones55ee3042022-04-06 17:10:21 +00003717 git_event_log = git_event_log or EventLog()
LaMont Jones409407a2022-04-05 21:21:56 +00003718 if outer_manifest and self.manifest.is_submanifest:
3719 # In a multi-manifest checkout, use the outer manifest unless we are told
3720 # not to.
3721 return self.client.outer_manifest.manifestProject.Sync(
3722 manifest_url=manifest_url,
3723 manifest_branch=manifest_branch,
3724 standalone_manifest=standalone_manifest,
3725 groups=groups,
3726 platform=platform,
3727 mirror=mirror,
3728 dissociate=dissociate,
3729 reference=reference,
3730 worktree=worktree,
3731 submodules=submodules,
3732 archive=archive,
3733 partial_clone=partial_clone,
3734 clone_filter=clone_filter,
3735 partial_clone_exclude=partial_clone_exclude,
3736 clone_bundle=clone_bundle,
3737 git_lfs=git_lfs,
3738 use_superproject=use_superproject,
3739 verbose=verbose,
3740 current_branch_only=current_branch_only,
3741 tags=tags,
3742 depth=depth,
LaMont Jones55ee3042022-04-06 17:10:21 +00003743 git_event_log=git_event_log,
LaMont Jones409407a2022-04-05 21:21:56 +00003744 manifest_name=manifest_name,
3745 this_manifest_only=this_manifest_only,
3746 outer_manifest=False)
3747
LaMont Jones9b03f152022-03-29 23:01:18 +00003748 # If repo has already been initialized, we take -u with the absence of
3749 # --standalone-manifest to mean "transition to a standard repo set up",
3750 # which necessitates starting fresh.
3751 # If --standalone-manifest is set, we always tear everything down and start
3752 # anew.
3753 if self.Exists:
3754 was_standalone_manifest = self.config.GetString('manifest.standalone')
3755 if was_standalone_manifest and not manifest_url:
3756 print('fatal: repo was initialized with a standlone manifest, '
3757 'cannot be re-initialized without --manifest-url/-u')
3758 return False
3759
3760 if standalone_manifest or (was_standalone_manifest and manifest_url):
3761 self.config.ClearCache()
3762 if self.gitdir and os.path.exists(self.gitdir):
3763 platform_utils.rmtree(self.gitdir)
3764 if self.worktree and os.path.exists(self.worktree):
3765 platform_utils.rmtree(self.worktree)
3766
3767 is_new = not self.Exists
3768 if is_new:
3769 if not manifest_url:
3770 print('fatal: manifest url is required.', file=sys.stderr)
3771 return False
3772
LaMont Jones409407a2022-04-05 21:21:56 +00003773 if verbose:
LaMont Jones9b03f152022-03-29 23:01:18 +00003774 print('Downloading manifest from %s' %
3775 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3776 file=sys.stderr)
3777
3778 # The manifest project object doesn't keep track of the path on the
3779 # server where this git is located, so let's save that here.
3780 mirrored_manifest_git = None
3781 if reference:
3782 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3783 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3784 if not mirrored_manifest_git.endswith(".git"):
3785 mirrored_manifest_git += ".git"
3786 if not os.path.exists(mirrored_manifest_git):
3787 mirrored_manifest_git = os.path.join(reference,
3788 '.repo/manifests.git')
3789
3790 self._InitGitDir(mirror_git=mirrored_manifest_git)
3791
3792 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3793 # still do much of the manifests.git set up, but will avoid actual syncs to
3794 # a remote.
3795 if standalone_manifest:
3796 self.config.SetString('manifest.standalone', manifest_url)
3797 elif not manifest_url and not manifest_branch:
3798 # If -u is set and --standalone-manifest is not, then we're not in
3799 # standalone mode. Otherwise, use config to infer what we were in the last
3800 # init.
3801 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3802 if not standalone_manifest:
3803 self.config.SetString('manifest.standalone', None)
3804
3805 self._ConfigureDepth(depth)
3806
3807 # Set the remote URL before the remote branch as we might need it below.
3808 if manifest_url:
Mike Frysingerdede5642022-07-10 04:56:04 -04003809 r = self.GetRemote()
LaMont Jones9b03f152022-03-29 23:01:18 +00003810 r.url = manifest_url
3811 r.ResetFetch()
3812 r.Save()
3813
3814 if not standalone_manifest:
3815 if manifest_branch:
3816 if manifest_branch == 'HEAD':
3817 manifest_branch = self.ResolveRemoteHead()
3818 if manifest_branch is None:
3819 print('fatal: unable to resolve HEAD', file=sys.stderr)
3820 return False
3821 self.revisionExpr = manifest_branch
3822 else:
3823 if is_new:
3824 default_branch = self.ResolveRemoteHead()
3825 if default_branch is None:
3826 # If the remote doesn't have HEAD configured, default to master.
3827 default_branch = 'refs/heads/master'
3828 self.revisionExpr = default_branch
3829 else:
3830 self.PreSync()
3831
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003832 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003833 all_platforms = ['linux', 'darwin', 'windows']
3834 platformize = lambda x: 'platform-' + x
3835 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003836 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003837 groups.append(platformize(self._platform_name))
3838 elif platform == 'all':
3839 groups.extend(map(platformize, all_platforms))
3840 elif platform in all_platforms:
3841 groups.append(platformize(platform))
3842 elif platform != 'none':
3843 print('fatal: invalid platform flag', file=sys.stderr)
3844 return False
LaMont Jones4ada0432022-04-14 15:10:43 +00003845 self.config.SetString('manifest.platform', platform)
LaMont Jones9b03f152022-03-29 23:01:18 +00003846
3847 groups = [x for x in groups if x]
3848 groupstr = ','.join(groups)
3849 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3850 groupstr = None
3851 self.config.SetString('manifest.groups', groupstr)
3852
3853 if reference:
3854 self.config.SetString('repo.reference', reference)
3855
3856 if dissociate:
3857 self.config.SetBoolean('repo.dissociate', dissociate)
3858
3859 if worktree:
3860 if mirror:
3861 print('fatal: --mirror and --worktree are incompatible',
3862 file=sys.stderr)
3863 return False
3864 if submodules:
3865 print('fatal: --submodules and --worktree are incompatible',
3866 file=sys.stderr)
3867 return False
3868 self.config.SetBoolean('repo.worktree', worktree)
3869 if is_new:
3870 self.use_git_worktrees = True
3871 print('warning: --worktree is experimental!', file=sys.stderr)
3872
3873 if archive:
3874 if is_new:
3875 self.config.SetBoolean('repo.archive', archive)
3876 else:
3877 print('fatal: --archive is only supported when initializing a new '
3878 'workspace.', file=sys.stderr)
3879 print('Either delete the .repo folder in this workspace, or initialize '
3880 'in another location.', file=sys.stderr)
3881 return False
3882
3883 if mirror:
3884 if is_new:
3885 self.config.SetBoolean('repo.mirror', mirror)
3886 else:
3887 print('fatal: --mirror is only supported when initializing a new '
3888 'workspace.', file=sys.stderr)
3889 print('Either delete the .repo folder in this workspace, or initialize '
3890 'in another location.', file=sys.stderr)
3891 return False
3892
3893 if partial_clone is not None:
3894 if mirror:
3895 print('fatal: --mirror and --partial-clone are mutually exclusive',
3896 file=sys.stderr)
3897 return False
3898 self.config.SetBoolean('repo.partialclone', partial_clone)
3899 if clone_filter:
3900 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jones55ee3042022-04-06 17:10:21 +00003901 elif self.partial_clone:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003902 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003903 else:
3904 clone_filter = None
3905
3906 if partial_clone_exclude is not None:
3907 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3908
3909 if clone_bundle is None:
3910 clone_bundle = False if partial_clone else True
3911 else:
3912 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3913
3914 if submodules:
3915 self.config.SetBoolean('repo.submodules', submodules)
3916
3917 if git_lfs is not None:
3918 if git_lfs:
3919 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3920
3921 self.config.SetBoolean('repo.git-lfs', git_lfs)
3922 if not is_new:
3923 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3924 ' Existing projects will require manual updates.\n', file=sys.stderr)
3925
3926 if use_superproject is not None:
3927 self.config.SetBoolean('repo.superproject', use_superproject)
3928
LaMont Jones0165e202022-04-27 17:34:42 +00003929 if not standalone_manifest:
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +01003930 success = self.Sync_NetworkHalf(
LaMont Jones0165e202022-04-27 17:34:42 +00003931 is_new=is_new, quiet=not verbose, verbose=verbose,
3932 clone_bundle=clone_bundle, current_branch_only=current_branch_only,
3933 tags=tags, submodules=submodules, clone_filter=clone_filter,
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +01003934 partial_clone_exclude=self.manifest.PartialCloneExclude).success
3935 if not success:
Mike Frysingerdede5642022-07-10 04:56:04 -04003936 r = self.GetRemote()
LaMont Jones0165e202022-04-27 17:34:42 +00003937 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
LaMont Jonesaf8fb132022-08-31 20:21:25 +00003938
3939 # Better delete the manifest git dir if we created it; otherwise next
3940 # time (when user fixes problems) we won't go through the "is_new" logic.
3941 if is_new:
3942 platform_utils.rmtree(self.gitdir)
LaMont Jones9b03f152022-03-29 23:01:18 +00003943 return False
3944
LaMont Jones0165e202022-04-27 17:34:42 +00003945 if manifest_branch:
3946 self.MetaBranchSwitch(submodules=submodules)
3947
3948 syncbuf = SyncBuffer(self.config)
3949 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3950 syncbuf.Finish()
3951
3952 if is_new or self.CurrentBranch is None:
3953 if not self.StartBranch('default'):
3954 print('fatal: cannot create default in manifest', file=sys.stderr)
3955 return False
3956
3957 if not manifest_name:
3958 print('fatal: manifest name (-m) is required.', file=sys.stderr)
3959 return False
3960
3961 elif is_new:
3962 # This is a new standalone manifest.
3963 manifest_name = 'default.xml'
3964 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3965 dest = os.path.join(self.worktree, manifest_name)
3966 os.makedirs(os.path.dirname(dest), exist_ok=True)
3967 with open(dest, 'wb') as f:
3968 f.write(manifest_data)
LaMont Jones409407a2022-04-05 21:21:56 +00003969
3970 try:
3971 self.manifest.Link(manifest_name)
3972 except ManifestParseError as e:
3973 print("fatal: manifest '%s' not available" % manifest_name,
3974 file=sys.stderr)
3975 print('fatal: %s' % str(e), file=sys.stderr)
3976 return False
3977
LaMont Jones55ee3042022-04-06 17:10:21 +00003978 if not this_manifest_only:
3979 for submanifest in self.manifest.submanifests.values():
LaMont Jonesb90a4222022-04-14 15:00:09 +00003980 spec = submanifest.ToSubmanifestSpec()
LaMont Jones55ee3042022-04-06 17:10:21 +00003981 submanifest.repo_client.manifestProject.Sync(
3982 manifest_url=spec.manifestUrl,
3983 manifest_branch=spec.revision,
3984 standalone_manifest=standalone_manifest,
3985 groups=self.manifest_groups,
3986 platform=platform,
3987 mirror=mirror,
3988 dissociate=dissociate,
3989 reference=reference,
3990 worktree=worktree,
3991 submodules=submodules,
3992 archive=archive,
3993 partial_clone=partial_clone,
3994 clone_filter=clone_filter,
3995 partial_clone_exclude=partial_clone_exclude,
3996 clone_bundle=clone_bundle,
3997 git_lfs=git_lfs,
3998 use_superproject=use_superproject,
3999 verbose=verbose,
4000 current_branch_only=current_branch_only,
4001 tags=tags,
4002 depth=depth,
4003 git_event_log=git_event_log,
4004 manifest_name=spec.manifestName,
4005 this_manifest_only=False,
4006 outer_manifest=False,
4007 )
LaMont Jones409407a2022-04-05 21:21:56 +00004008
LaMont Jones0ddb6772022-05-20 09:11:54 +00004009 # Lastly, if the manifest has a <superproject> then have the superproject
LaMont Jonesff6b1da2022-06-01 21:03:34 +00004010 # sync it (if it will be used).
4011 if git_superproject.UseSuperproject(use_superproject, self.manifest):
LaMont Jones0ddb6772022-05-20 09:11:54 +00004012 sync_result = self.manifest.superproject.Sync(git_event_log)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00004013 if not sync_result.success:
LaMont Jones7f44d362023-01-31 22:13:24 +00004014 submanifest = ''
4015 if self.manifest.path_prefix:
4016 submanifest = f'for {self.manifest.path_prefix} '
4017 print(f'warning: git update of superproject {submanifest}failed, repo '
4018 'sync will not use superproject to fetch source; while this '
4019 'error is not fatal, and you can continue to run repo sync, '
4020 'please run repo init with the --no-use-superproject option to '
4021 'stop seeing this warning', file=sys.stderr)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00004022 if sync_result.fatal and use_superproject is not None:
4023 return False
LaMont Jones409407a2022-04-05 21:21:56 +00004024
LaMont Jones9b03f152022-03-29 23:01:18 +00004025 return True
4026
4027 def _ConfigureDepth(self, depth):
4028 """Configure the depth we'll sync down.
4029
4030 Args:
4031 depth: an int, how deep of a partial clone to create.
4032 """
4033 # Opt.depth will be non-None if user actually passed --depth to repo init.
4034 if depth is not None:
4035 if depth > 0:
4036 # Positive values will set the depth.
4037 depth = str(depth)
4038 else:
4039 # Negative numbers will clear the depth; passing None to SetString
4040 # will do that.
4041 depth = None
4042
4043 # We store the depth in the main manifest project.
4044 self.config.SetString('repo.depth', depth)