blob: 996fc02eef2a3d554f2fb52cad4105faa319a1ef [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, \
36 ID_RE
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
Mike Frysingere6a202f2019-08-02 15:57:57 -040040from error import ManifestInvalidRevisionError, ManifestInvalidPathError
LaMont Jones409407a2022-04-05 21:21:56 +000041from error import NoManifestException, ManifestParseError
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070042import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040043import progress
Joanna Wanga6c52f52022-11-03 16:51:19 -040044from repo_trace import Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070045
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050046from 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 -070047
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070048
LaMont Jones1eddca82022-09-01 15:15:04 +000049class SyncNetworkHalfResult(NamedTuple):
50 """Sync_NetworkHalf return value."""
51 # True if successful.
52 success: bool
53 # Did we query the remote? False when optimized_fetch is True and we have the
54 # commit already present.
55 remote_fetched: bool
56
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +010057
George Engelbrecht9bc283e2020-04-02 12:36:09 -060058# Maximum sleep time allowed during retries.
59MAXIMUM_RETRY_SLEEP_SEC = 3600.0
60# +-10% random jitter is added to each Fetches retry sleep duration.
61RETRY_JITTER_PERCENT = 0.1
62
LaMont Jonesfa8d9392022-11-02 22:01:29 +000063# Whether to use alternates. Switching back and forth is *NOT* supported.
Mike Frysinger1d00a7e2021-12-21 00:40:31 -050064# TODO(vapier): Remove knob once behavior is verified.
65_ALTERNATES = os.environ.get('REPO_USE_ALTERNATES') == '1'
George Engelbrecht9bc283e2020-04-02 12:36:09 -060066
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +010067
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068def _lwrite(path, content):
69 lock = '%s.lock' % path
70
Remy Bohmer169b0212020-11-21 10:57:52 +010071 # Maintain Unix line endings on all OS's to match git behavior.
72 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070073 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070074
75 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070076 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070077 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080078 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070079 raise
80
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070081
Shawn O. Pearce48244782009-04-16 08:25:57 -070082def _error(fmt, *args):
83 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070084 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070085
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070086
David Pursehousef33929d2015-08-24 14:39:14 +090087def _warn(fmt, *args):
88 msg = fmt % args
89 print('warn: %s' % msg, file=sys.stderr)
90
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070091
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070092def not_rev(r):
93 return '^' + r
94
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070095
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080096def sq(r):
97 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080098
David Pursehouse819827a2020-02-12 15:20:19 +090099
Jonathan Nieder93719792015-03-17 11:29:58 -0700100_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700101
102
Jonathan Nieder93719792015-03-17 11:29:58 -0700103def _ProjectHooks():
104 """List the hooks present in the 'hooks' directory.
105
106 These hooks are project hooks and are copied to the '.git/hooks' directory
107 of all subprojects.
108
109 This function caches the list of hooks (based on the contents of the
110 'repo/hooks' directory) on the first call.
111
112 Returns:
113 A list of absolute paths to all of the files in the hooks directory.
114 """
115 global _project_hook_list
116 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700117 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700118 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700119 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700120 return _project_hook_list
121
122
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700123class DownloadedChange(object):
124 _commit_cache = None
125
126 def __init__(self, project, base, change_id, ps_id, commit):
127 self.project = project
128 self.base = base
129 self.change_id = change_id
130 self.ps_id = ps_id
131 self.commit = commit
132
133 @property
134 def commits(self):
135 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700136 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
137 '--abbrev-commit',
138 '--pretty=oneline',
139 '--reverse',
140 '--date-order',
141 not_rev(self.base),
142 self.commit,
143 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700144 return self._commit_cache
145
146
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700147class ReviewableBranch(object):
148 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400149 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700150
151 def __init__(self, project, branch, base):
152 self.project = project
153 self.branch = branch
154 self.base = base
155
156 @property
157 def name(self):
158 return self.branch.name
159
160 @property
161 def commits(self):
162 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400163 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
164 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
165 try:
166 self._commit_cache = self.project.bare_git.rev_list(*args)
167 except GitError:
168 # We weren't able to probe the commits for this branch. Was it tracking
169 # a branch that no longer exists? If so, return no commits. Otherwise,
170 # rethrow the error as we don't know what's going on.
171 if self.base_exists:
172 raise
173
174 self._commit_cache = []
175
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176 return self._commit_cache
177
178 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800179 def unabbrev_commits(self):
180 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700181 for commit in self.project.bare_git.rev_list(not_rev(self.base),
182 R_HEADS + self.name,
183 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800184 r[commit[0:8]] = commit
185 return r
186
187 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700188 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700189 return self.project.bare_git.log('--pretty=format:%cd',
190 '-n', '1',
191 R_HEADS + self.name,
192 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700193
Mike Frysinger6da17752019-09-11 18:43:17 -0400194 @property
195 def base_exists(self):
196 """Whether the branch we're tracking exists.
197
198 Normally it should, but sometimes branches we track can get deleted.
199 """
200 if self._base_exists is None:
201 try:
202 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
203 # If we're still here, the base branch exists.
204 self._base_exists = True
205 except GitError:
206 # If we failed to verify, the base branch doesn't exist.
207 self._base_exists = False
208
209 return self._base_exists
210
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700211 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500212 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700213 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500214 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500215 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200216 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700217 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200218 wip=False,
William Escandeac76fd32022-08-02 16:05:37 -0700219 ready=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200220 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800221 validate_certs=True,
222 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500223 self.project.UploadForReview(branch=self.name,
224 people=people,
225 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700226 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500227 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500228 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200229 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700230 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200231 wip=wip,
William Escandeac76fd32022-08-02 16:05:37 -0700232 ready=ready,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200233 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800234 validate_certs=validate_certs,
235 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700236
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700237 def GetPublishedRefs(self):
238 refs = {}
239 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700240 self.branch.remote.SshReviewUrl(self.project.UserEmail),
241 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700242 for line in output.split('\n'):
243 try:
244 (sha, ref) = line.split()
245 refs[sha] = ref
246 except ValueError:
247 pass
248
249 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700250
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700251
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700253
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700254 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500255 super().__init__(config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100256 self.project = self.printer('header', attr='bold')
257 self.branch = self.printer('header', attr='bold')
258 self.nobranch = self.printer('nobranch', fg='red')
259 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700260
Anthony King7bdac712014-07-16 12:56:40 +0100261 self.added = self.printer('added', fg='green')
262 self.changed = self.printer('changed', fg='red')
263 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700264
265
266class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700267
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700268 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500269 super().__init__(config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100270 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400271 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700272
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700273
Jack Neus6ea0cae2021-07-20 20:52:33 +0000274class Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700275
James W. Mills24c13082012-04-12 15:04:13 -0500276 def __init__(self, name, value, keep):
277 self.name = name
278 self.value = value
279 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700280
Jack Neus6ea0cae2021-07-20 20:52:33 +0000281 def __eq__(self, other):
282 if not isinstance(other, Annotation):
283 return False
284 return self.__dict__ == other.__dict__
285
286 def __lt__(self, other):
287 # This exists just so that lists of Annotation objects can be sorted, for
288 # use in comparisons.
289 if not isinstance(other, Annotation):
290 raise ValueError('comparison is not between two Annotation objects')
291 if self.name == other.name:
292 if self.value == other.value:
293 return self.keep < other.keep
294 return self.value < other.value
295 return self.name < other.name
296
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700297
Mike Frysingere6a202f2019-08-02 15:57:57 -0400298def _SafeExpandPath(base, subpath, skipfinal=False):
299 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700300
Mike Frysingere6a202f2019-08-02 15:57:57 -0400301 We make sure no intermediate symlinks are traversed, and that the final path
302 is not a special file (e.g. not a socket or fifo).
303
304 NB: We rely on a number of paths already being filtered out while parsing the
305 manifest. See the validation logic in manifest_xml.py for more details.
306 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500307 # Split up the path by its components. We can't use os.path.sep exclusively
308 # as some platforms (like Windows) will convert / to \ and that bypasses all
309 # our constructed logic here. Especially since manifest authors only use
310 # / in their paths.
311 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
312 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400313 if skipfinal:
314 # Whether the caller handles the final component itself.
315 finalpart = components.pop()
316
317 path = base
318 for part in components:
319 if part in {'.', '..'}:
320 raise ManifestInvalidPathError(
321 '%s: "%s" not allowed in paths' % (subpath, part))
322
323 path = os.path.join(path, part)
324 if platform_utils.islink(path):
325 raise ManifestInvalidPathError(
326 '%s: traversing symlinks not allow' % (path,))
327
328 if os.path.exists(path):
329 if not os.path.isfile(path) and not platform_utils.isdir(path):
330 raise ManifestInvalidPathError(
331 '%s: only regular files & directories allowed' % (path,))
332
333 if skipfinal:
334 path = os.path.join(path, finalpart)
335
336 return path
337
338
339class _CopyFile(object):
340 """Container for <copyfile> manifest element."""
341
342 def __init__(self, git_worktree, src, topdir, dest):
343 """Register a <copyfile> request.
344
345 Args:
346 git_worktree: Absolute path to the git project checkout.
347 src: Relative path under |git_worktree| of file to read.
348 topdir: Absolute path to the top of the repo client checkout.
349 dest: Relative path under |topdir| of file to write.
350 """
351 self.git_worktree = git_worktree
352 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700353 self.src = src
354 self.dest = dest
355
356 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400357 src = _SafeExpandPath(self.git_worktree, self.src)
358 dest = _SafeExpandPath(self.topdir, self.dest)
359
360 if platform_utils.isdir(src):
361 raise ManifestInvalidPathError(
362 '%s: copying from directory not supported' % (self.src,))
363 if platform_utils.isdir(dest):
364 raise ManifestInvalidPathError(
365 '%s: copying to directory not allowed' % (self.dest,))
366
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700367 # copy file if it does not exist or is out of date
368 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
369 try:
370 # remove existing file first, since it might be read-only
371 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800372 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400373 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200374 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700375 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200376 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700377 shutil.copy(src, dest)
378 # make the file read-only
379 mode = os.stat(dest)[stat.ST_MODE]
380 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
381 os.chmod(dest, mode)
382 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700383 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700384
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700385
Anthony King7bdac712014-07-16 12:56:40 +0100386class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400387 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700388
Mike Frysingere6a202f2019-08-02 15:57:57 -0400389 def __init__(self, git_worktree, src, topdir, dest):
390 """Register a <linkfile> request.
391
392 Args:
393 git_worktree: Absolute path to the git project checkout.
394 src: Target of symlink relative to path under |git_worktree|.
395 topdir: Absolute path to the top of the repo client checkout.
396 dest: Relative path under |topdir| of symlink to create.
397 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700398 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400399 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500400 self.src = src
401 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500402
Wink Saville4c426ef2015-06-03 08:05:17 -0700403 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500404 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700405 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500406 try:
407 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800408 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800409 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500410 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700411 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700412 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500413 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700414 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500415 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700416 _error('Cannot link file %s to %s', relSrc, absDest)
417
418 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400419 """Link the self.src & self.dest paths.
420
421 Handles wild cards on the src linking all of the files in the source in to
422 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700423 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500424 # Some people use src="." to create stable links to projects. Lets allow
425 # that but reject all other uses of "." to keep things simple.
426 if self.src == '.':
427 src = self.git_worktree
428 else:
429 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400430
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300431 if not glob.has_magic(src):
432 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400433 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
434 # dest & src are absolute paths at this point. Make sure the target of
435 # the symlink is relative in the context of the repo client checkout.
436 relpath = os.path.relpath(src, os.path.dirname(dest))
437 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700438 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400439 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300440 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400441 if os.path.exists(dest) and not platform_utils.isdir(dest):
442 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700443 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400444 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700445 # Create a releative path from source dir to destination dir
446 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400447 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700448
449 # Get the source file name
450 srcFile = os.path.basename(absSrcFile)
451
452 # Now form the final full paths to srcFile. They will be
453 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400454 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700455 relSrc = os.path.join(relSrcDir, srcFile)
456 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500457
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700458
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700459class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700460
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700461 def __init__(self,
462 name,
Anthony King7bdac712014-07-16 12:56:40 +0100463 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700464 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100465 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700466 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700467 orig_name=None,
468 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700469 self.name = name
470 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700471 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700472 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100473 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700474 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700475 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700476
Ian Kasprzak0286e312021-02-05 10:06:18 -0800477
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700478class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600479 # These objects can be shared between several working trees.
LaMont Jones68d69632022-06-07 18:24:20 +0000480 @property
481 def shareable_dirs(self):
482 """Return the shareable directories"""
483 if self.UseAlternates:
484 return ['hooks', 'rr-cache']
485 else:
486 return ['hooks', 'objects', 'rr-cache']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700487
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700488 def __init__(self,
489 manifest,
490 name,
491 remote,
492 gitdir,
David James8d201162013-10-11 17:03:19 -0700493 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700494 worktree,
495 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700496 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800497 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100498 rebase=True,
499 groups=None,
500 sync_c=False,
501 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900502 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100503 clone_depth=None,
504 upstream=None,
505 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500506 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100507 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900508 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700509 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600510 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700511 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800512 """Init a Project object.
513
514 Args:
515 manifest: The XmlManifest object.
516 name: The `name` attribute of manifest.xml's project element.
517 remote: RemoteSpec object specifying its remote's properties.
518 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700519 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800520 worktree: Absolute path of git working tree.
521 relpath: Relative path of git working tree to repo's top directory.
522 revisionExpr: The `revision` attribute of manifest.xml's project element.
523 revisionId: git commit id for checking out.
524 rebase: The `rebase` attribute of manifest.xml's project element.
525 groups: The `groups` attribute of manifest.xml's project element.
526 sync_c: The `sync-c` attribute of manifest.xml's project element.
527 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900528 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800529 upstream: The `upstream` attribute of manifest.xml's project element.
530 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500531 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800532 is_derived: False if the project was explicitly defined in the manifest;
533 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400534 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900535 optimized_fetch: If True, when a project is set to a sha1 revision, only
536 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600537 retry_fetches: Retry remote fetches n times upon receiving transient error
538 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700539 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800540 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400541 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700542 self.name = name
543 self.remote = remote
Michael Kelly37c21c22020-06-13 02:10:40 -0700544 self.UpdatePaths(relpath, worktree, gitdir, objdir)
Michael Kelly2f3c3312020-07-21 19:40:38 -0700545 self.SetRevision(revisionExpr, revisionId=revisionId)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700546
Mike Pontillod3153822012-02-28 11:53:24 -0800547 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700548 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700549 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800550 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900551 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900552 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700553 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800554 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500555 # NB: Do not use this setting in __init__ to change behavior so that the
556 # manifest.git checkout can inspect & change it after instantiating. See
557 # the XmlManifest init code for more info.
558 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800559 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900560 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600561 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800562 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800563
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700564 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700565 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500566 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500567 self.annotations = []
Bryan Jacobsf609f912013-05-06 13:36:24 -0400568 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700569 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700570
Doug Anderson37282b42011-03-04 11:54:18 -0800571 # This will be filled in if a project is later identified to be the
572 # project containing repo hooks.
573 self.enabled_repo_hooks = []
574
LaMont Jonescc879a92021-11-18 22:40:18 +0000575 def RelPath(self, local=True):
576 """Return the path for the project relative to a manifest.
577
578 Args:
579 local: a boolean, if True, the path is relative to the local
580 (sub)manifest. If false, the path is relative to the
581 outermost manifest.
582 """
583 if local:
584 return self.relpath
585 return os.path.join(self.manifest.path_prefix, self.relpath)
586
Michael Kelly2f3c3312020-07-21 19:40:38 -0700587 def SetRevision(self, revisionExpr, revisionId=None):
588 """Set revisionId based on revision expression and id"""
589 self.revisionExpr = revisionExpr
590 if revisionId is None and revisionExpr and IsId(revisionExpr):
591 self.revisionId = self.revisionExpr
592 else:
593 self.revisionId = revisionId
594
Michael Kelly37c21c22020-06-13 02:10:40 -0700595 def UpdatePaths(self, relpath, worktree, gitdir, objdir):
596 """Update paths used by this project"""
597 self.gitdir = gitdir.replace('\\', '/')
598 self.objdir = objdir.replace('\\', '/')
599 if worktree:
600 self.worktree = os.path.normpath(worktree).replace('\\', '/')
601 else:
602 self.worktree = None
603 self.relpath = relpath
604
605 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
606 defaults=self.manifest.globalConfig)
607
608 if self.worktree:
609 self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
610 else:
611 self.work_git = None
612 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
613 self.bare_ref = GitRefs(self.gitdir)
614 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
615
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700616 @property
LaMont Jones68d69632022-06-07 18:24:20 +0000617 def UseAlternates(self):
618 """Whether git alternates are in use.
619
620 This will be removed once migration to alternates is complete.
621 """
622 return _ALTERNATES or self.manifest.is_multimanifest
623
624 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800625 def Derived(self):
626 return self.is_derived
627
628 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700629 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700630 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700631
632 @property
633 def CurrentBranch(self):
634 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400635
636 The branch name omits the 'refs/heads/' prefix.
637 None is returned if the project is on a detached HEAD, or if the work_git is
638 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700639 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400640 try:
641 b = self.work_git.GetHead()
642 except NoManifestException:
643 # If the local checkout is in a bad state, don't barf. Let the callers
644 # process this like the head is unreadable.
645 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700646 if b.startswith(R_HEADS):
647 return b[len(R_HEADS):]
648 return None
649
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700650 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500651 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
652 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
653 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200654
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700655 def IsDirty(self, consider_untracked=True):
656 """Is the working directory modified in some way?
657 """
658 self.work_git.update_index('-q',
659 '--unmerged',
660 '--ignore-missing',
661 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900662 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700663 return True
664 if self.work_git.DiffZ('diff-files'):
665 return True
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200666 if consider_untracked and self.UntrackedFiles():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700667 return True
668 return False
669
670 _userident_name = None
671 _userident_email = None
672
673 @property
674 def UserName(self):
675 """Obtain the user's personal name.
676 """
677 if self._userident_name is None:
678 self._LoadUserIdentity()
679 return self._userident_name
680
681 @property
682 def UserEmail(self):
683 """Obtain the user's email address. This is very likely
684 to be their Gerrit login.
685 """
686 if self._userident_email is None:
687 self._LoadUserIdentity()
688 return self._userident_email
689
690 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900691 u = self.bare_git.var('GIT_COMMITTER_IDENT')
692 m = re.compile("^(.*) <([^>]*)> ").match(u)
693 if m:
694 self._userident_name = m.group(1)
695 self._userident_email = m.group(2)
696 else:
697 self._userident_name = ''
698 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700699
Mike Frysingerdede5642022-07-10 04:56:04 -0400700 def GetRemote(self, name=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700701 """Get the configuration for a single remote.
Mike Frysingerdede5642022-07-10 04:56:04 -0400702
703 Defaults to the current project's remote.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700704 """
Mike Frysingerdede5642022-07-10 04:56:04 -0400705 if name is None:
706 name = self.remote.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700707 return self.config.GetRemote(name)
708
709 def GetBranch(self, name):
710 """Get the configuration for a single branch.
711 """
712 return self.config.GetBranch(name)
713
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700714 def GetBranches(self):
715 """Get all existing local branches.
716 """
717 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900718 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700719 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700720
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530721 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700722 if name.startswith(R_HEADS):
723 name = name[len(R_HEADS):]
724 b = self.GetBranch(name)
725 b.current = name == current
726 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900727 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700728 heads[name] = b
729
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530730 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700731 if name.startswith(R_PUB):
732 name = name[len(R_PUB):]
733 b = heads.get(name)
734 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900735 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700736
737 return heads
738
Colin Cross5acde752012-03-28 20:15:45 -0700739 def MatchesGroups(self, manifest_groups):
740 """Returns true if the manifest groups specified at init should cause
741 this project to be synced.
742 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700743 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700744
745 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700746 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700747 manifest_groups: "-group1,group2"
748 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500749
750 The special manifest group "default" will match any project that
751 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700752 """
LaMont Jones501733c2022-04-20 16:42:32 +0000753 default_groups = self.manifest.default_groups or ['default']
754 expanded_manifest_groups = manifest_groups or default_groups
Conley Owensbb1b5f52012-08-13 13:11:18 -0700755 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700756 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500757 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700758
Conley Owens971de8e2012-04-16 10:36:08 -0700759 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700760 for group in expanded_manifest_groups:
761 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700762 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700763 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700764 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700765
Conley Owens971de8e2012-04-16 10:36:08 -0700766 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700767
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700768# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700769 def UncommitedFiles(self, get_all=True):
770 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700771
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700772 Args:
773 get_all: a boolean, if True - get information about all different
774 uncommitted files. If False - return as soon as any kind of
775 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500776 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700777 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500778 self.work_git.update_index('-q',
779 '--unmerged',
780 '--ignore-missing',
781 '--refresh')
782 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700783 details.append("rebase in progress")
784 if not get_all:
785 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500786
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700787 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
788 if changes:
789 details.extend(changes)
790 if not get_all:
791 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500792
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700793 changes = self.work_git.DiffZ('diff-files').keys()
794 if changes:
795 details.extend(changes)
796 if not get_all:
797 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500798
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200799 changes = self.UntrackedFiles()
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700800 if changes:
801 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500802
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700803 return details
804
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200805 def UntrackedFiles(self):
806 """Returns a list of strings, untracked files in the git tree."""
807 return self.work_git.LsOthers()
808
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700809 def HasChanges(self):
810 """Returns true if there are uncommitted changes.
811 """
Martin Geisler8db78c72022-07-08 11:05:24 +0200812 return bool(self.UncommitedFiles(get_all=False))
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500813
LaMont Jones8501d462022-06-22 19:21:15 +0000814 def PrintWorkTreeStatus(self, output_redir=None, quiet=False, local=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700815 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200816
817 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200818 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600819 quiet: If True then only print the project name. Do not print
820 the modified files, branch name, etc.
LaMont Jones8501d462022-06-22 19:21:15 +0000821 local: a boolean, if True, the path is relative to the local
822 (sub)manifest. If false, the path is relative to the
823 outermost manifest.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700825 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700826 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200827 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700828 print(file=output_redir)
LaMont Jones8501d462022-06-22 19:21:15 +0000829 print('project %s/' % self.RelPath(local), file=output_redir)
Sarah Owenscecd1d82012-11-01 22:59:27 -0700830 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700831 return
832
833 self.work_git.update_index('-q',
834 '--unmerged',
835 '--ignore-missing',
836 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700837 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
839 df = self.work_git.DiffZ('diff-files')
840 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100841 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700842 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843
844 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700845 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200846 out.redirect(output_redir)
LaMont Jones8501d462022-06-22 19:21:15 +0000847 out.project('project %-40s', self.RelPath(local) + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600849 if quiet:
850 out.nl()
851 return 'DIRTY'
852
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853 branch = self.CurrentBranch
854 if branch is None:
855 out.nobranch('(*** NO BRANCH ***)')
856 else:
857 out.branch('branch %s', branch)
858 out.nl()
859
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700860 if rb:
861 out.important('prior sync failed; rebase still in progress')
862 out.nl()
863
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700864 paths = list()
865 paths.extend(di.keys())
866 paths.extend(df.keys())
867 paths.extend(do)
868
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530869 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900870 try:
871 i = di[p]
872 except KeyError:
873 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700874
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900875 try:
876 f = df[p]
877 except KeyError:
878 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200879
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900880 if i:
881 i_status = i.status.upper()
882 else:
883 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700884
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900885 if f:
886 f_status = f.status.lower()
887 else:
888 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889
890 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800891 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700892 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893 else:
894 line = ' %s%s\t%s' % (i_status, f_status, p)
895
896 if i and not f:
897 out.added('%s', line)
898 elif (i and f) or (not i and f):
899 out.changed('%s', line)
900 elif not i and not f:
901 out.untracked('%s', line)
902 else:
903 out.write('%s', line)
904 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200905
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700906 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700907
LaMont Jones8501d462022-06-22 19:21:15 +0000908 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None,
909 local=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700910 """Prints the status of the repository to stdout.
911 """
912 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500913 if output_redir:
914 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700915 cmd = ['diff']
916 if out.is_on:
917 cmd.append('--color')
918 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300919 if absolute_paths:
LaMont Jones8501d462022-06-22 19:21:15 +0000920 cmd.append('--src-prefix=a/%s/' % self.RelPath(local))
921 cmd.append('--dst-prefix=b/%s/' % self.RelPath(local))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400923 try:
924 p = GitCommand(self,
925 cmd,
926 capture_stdout=True,
927 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500928 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400929 except GitError as e:
930 out.nl()
LaMont Jones8501d462022-06-22 19:21:15 +0000931 out.project('project %s/' % self.RelPath(local))
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400932 out.nl()
933 out.fail('%s', str(e))
934 out.nl()
935 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500936 if p.stdout:
937 out.nl()
LaMont Jones8501d462022-06-22 19:21:15 +0000938 out.project('project %s/' % self.RelPath(local))
Mike Frysinger84230002021-02-16 17:08:35 -0500939 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500940 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400941 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700943# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900944 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945 """Was the branch published (uploaded) for code review?
946 If so, returns the SHA-1 hash of the last published
947 state for the branch.
948 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700949 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900950 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700951 try:
952 return self.bare_git.rev_parse(key)
953 except GitError:
954 return None
955 else:
956 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900957 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700958 except KeyError:
959 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700960
David Pursehouse8a68ff92012-09-24 12:15:13 +0900961 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 """Prunes any stale published refs.
963 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900964 if all_refs is None:
965 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700966 heads = set()
967 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530968 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 if name.startswith(R_HEADS):
970 heads.add(name)
971 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900972 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530974 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700975 n = name[len(R_PUB):]
976 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900977 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700979 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980 """List any branches which can be uploaded for review.
981 """
982 heads = {}
983 pubed = {}
984
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530985 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900987 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700988 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900989 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700990
991 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530992 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900993 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700994 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700995 if selected_branch and branch != selected_branch:
996 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800998 rb = self.GetUploadableBranch(branch)
999 if rb:
1000 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001 return ready
1002
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001003 def GetUploadableBranch(self, branch_name):
1004 """Get a single uploadable branch, or None.
1005 """
1006 branch = self.GetBranch(branch_name)
1007 base = branch.LocalMerge
1008 if branch.LocalMerge:
1009 rb = ReviewableBranch(self, branch, base)
1010 if rb.commits:
1011 return rb
1012 return None
1013
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001014 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001015 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -05001016 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001017 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001018 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001019 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +02001020 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001021 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001022 wip=False,
William Escandeac76fd32022-08-02 16:05:37 -07001023 ready=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001024 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001025 validate_certs=True,
1026 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001027 """Uploads the named branch for code review.
1028 """
1029 if branch is None:
1030 branch = self.CurrentBranch
1031 if branch is None:
1032 raise GitError('not currently on a branch')
1033
1034 branch = self.GetBranch(branch)
1035 if not branch.LocalMerge:
1036 raise GitError('branch %s does not track a remote' % branch.name)
1037 if not branch.remote.review:
1038 raise GitError('remote %s has no review url' % branch.remote.name)
1039
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001040 # Basic validity check on label syntax.
1041 for label in labels:
1042 if not re.match(r'^.+[+-][0-9]+$', label):
1043 raise UploadError(
1044 f'invalid label syntax "{label}": labels use forms like '
1045 'CodeReview+1 or Verified-1')
1046
Bryan Jacobsf609f912013-05-06 13:36:24 -04001047 if dest_branch is None:
1048 dest_branch = self.dest_branch
1049 if dest_branch is None:
1050 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 if not dest_branch.startswith(R_HEADS):
1052 dest_branch = R_HEADS + dest_branch
1053
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001054 if not branch.remote.projectname:
1055 branch.remote.projectname = self.name
1056 branch.remote.Save()
1057
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001058 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001059 if url is None:
1060 raise UploadError('review not configured')
1061 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001062 if dryrun:
1063 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001064
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001065 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001066 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001067
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001068 for push_option in (push_options or []):
1069 cmd.append('-o')
1070 cmd.append(push_option)
1071
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001072 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001073
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001074 if dest_branch.startswith(R_HEADS):
1075 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001076
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001077 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001078 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001079 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001080 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001081 opts += ['t=%s' % p for p in hashtags]
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001082 # NB: No need to encode labels as they've been validated above.
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001083 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001084
David Pursehousef25a3702018-11-14 19:01:22 -08001085 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001086 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001087 if notify:
1088 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001089 if private:
1090 opts += ['private']
1091 if wip:
1092 opts += ['wip']
William Escandeac76fd32022-08-02 16:05:37 -07001093 if ready:
1094 opts += ['ready']
Jonathan Nieder713c5872018-11-05 13:21:52 -08001095 if opts:
1096 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001097 cmd.append(ref_spec)
1098
Anthony King7bdac712014-07-16 12:56:40 +01001099 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001100 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101
Mike Frysingerd7f86832020-11-19 19:18:46 -05001102 if not dryrun:
1103 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1104 self.bare_git.UpdateRef(R_PUB + branch.name,
1105 R_HEADS + branch.name,
1106 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001107
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001108# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001109 def _ExtractArchive(self, tarpath, path=None):
1110 """Extract the given tar on its current location
1111
1112 Args:
1113 - tarpath: The path to the actual tar file
1114
1115 """
1116 try:
1117 with tarfile.open(tarpath, 'r') as tar:
1118 tar.extractall(path=path)
1119 return True
1120 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001121 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001122 return False
1123
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001124 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001125 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001126 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001127 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001128 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001129 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001130 force_sync=False,
1131 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001132 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001133 archive=False,
1134 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001135 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001136 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001137 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001138 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001139 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001140 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001141 """Perform only the network IO portion of the sync process.
1142 Local working directory/branch state is not affected.
1143 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001144 if archive and not isinstance(self, MetaProject):
1145 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001146 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
LaMont Jones1eddca82022-09-01 15:15:04 +00001147 return SyncNetworkHalfResult(False, False)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001148
1149 name = self.relpath.replace('\\', '/')
1150 name = name.replace('/', '_')
1151 tarpath = '%s.tar' % name
1152 topdir = self.manifest.topdir
1153
1154 try:
1155 self._FetchArchive(tarpath, cwd=topdir)
1156 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001157 _error('%s', e)
LaMont Jones1eddca82022-09-01 15:15:04 +00001158 return SyncNetworkHalfResult(False, False)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001159
1160 # From now on, we only need absolute tarpath
1161 tarpath = os.path.join(topdir, tarpath)
1162
1163 if not self._ExtractArchive(tarpath, path=topdir):
LaMont Jones1eddca82022-09-01 15:15:04 +00001164 return SyncNetworkHalfResult(False, True)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001165 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001166 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001167 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001168 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001169 self._CopyAndLinkFiles()
LaMont Jones1eddca82022-09-01 15:15:04 +00001170 return SyncNetworkHalfResult(True, True)
Mike Frysinger76844ba2021-02-28 17:08:55 -05001171
1172 # If the shared object dir already exists, don't try to rebootstrap with a
1173 # clone bundle download. We should have the majority of objects already.
1174 if clone_bundle and os.path.exists(self.objdir):
1175 clone_bundle = False
1176
Raman Tennetif32f2432021-04-12 20:57:25 -07001177 if self.name in partial_clone_exclude:
1178 clone_bundle = True
1179 clone_filter = None
1180
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001181 if is_new is None:
1182 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001183 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001184 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001185 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001186 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001188
LaMont Jones68d69632022-06-07 18:24:20 +00001189 if self.UseAlternates:
Mike Frysinger1d00a7e2021-12-21 00:40:31 -05001190 # If gitdir/objects is a symlink, migrate it from the old layout.
1191 gitdir_objects = os.path.join(self.gitdir, 'objects')
1192 if platform_utils.islink(gitdir_objects):
1193 platform_utils.remove(gitdir_objects, missing_ok=True)
1194 gitdir_alt = os.path.join(self.gitdir, 'objects/info/alternates')
1195 if not os.path.exists(gitdir_alt):
1196 os.makedirs(os.path.dirname(gitdir_alt), exist_ok=True)
1197 _lwrite(gitdir_alt, os.path.join(
1198 os.path.relpath(self.objdir, gitdir_objects), 'objects') + '\n')
1199
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001200 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001201 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001202 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001203 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001204 # This works for both absolute and relative alternate directories.
1205 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001206 except IOError:
1207 alt_dir = None
1208 else:
1209 alt_dir = None
1210
Mike Frysingere50b6a72020-02-19 01:45:48 -05001211 if (clone_bundle
1212 and alt_dir is None
1213 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001214 is_new = False
1215
Mike Frysinger73561142021-05-03 01:10:09 -04001216 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001217 if self.sync_c:
1218 current_branch_only = True
1219 elif not self.manifest._loaded:
1220 # Manifest cannot check defaults until it syncs.
1221 current_branch_only = False
1222 elif self.manifest.default.sync_c:
1223 current_branch_only = True
1224
Mike Frysingerd68ed632021-05-03 01:21:35 -04001225 if tags is None:
1226 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001227
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001228 if self.clone_depth:
1229 depth = self.clone_depth
1230 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001231 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001232
Mike Frysinger521d01b2020-02-17 01:51:49 -05001233 # See if we can skip the network fetch entirely.
LaMont Jones1eddca82022-09-01 15:15:04 +00001234 remote_fetched = False
Mike Frysinger521d01b2020-02-17 01:51:49 -05001235 if not (optimized_fetch and
1236 (ID_RE.match(self.revisionExpr) and
1237 self._CheckForImmutableRevision())):
LaMont Jones1eddca82022-09-01 15:15:04 +00001238 remote_fetched = True
Mike Frysinger521d01b2020-02-17 01:51:49 -05001239 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001240 initial=is_new,
1241 quiet=quiet, verbose=verbose, output_redir=output_redir,
1242 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001243 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001244 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001245 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001246 clone_filter=clone_filter, retry_fetches=retry_fetches):
LaMont Jones1eddca82022-09-01 15:15:04 +00001247 return SyncNetworkHalfResult(False, remote_fetched)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001248
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001249 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001250 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001251 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001252 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001253 if os.path.exists(alternates_file):
1254 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001255 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1256 merge_output=bool(output_redir))
1257 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001258 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001259 if p.Wait() != 0:
LaMont Jones1eddca82022-09-01 15:15:04 +00001260 return SyncNetworkHalfResult(False, remote_fetched)
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001261 platform_utils.remove(alternates_file)
1262
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001263 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001264 self._InitMRef()
1265 else:
1266 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001267 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1268 missing_ok=True)
LaMont Jones1eddca82022-09-01 15:15:04 +00001269 return SyncNetworkHalfResult(True, remote_fetched)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001270
1271 def PostRepoUpgrade(self):
1272 self._InitHooks()
1273
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001274 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001275 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001276 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001277 for copyfile in self.copyfiles:
1278 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001279 for linkfile in self.linkfiles:
1280 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001281
Julien Camperguedd654222014-01-09 16:21:37 +01001282 def GetCommitRevisionId(self):
1283 """Get revisionId of a commit.
1284
1285 Use this method instead of GetRevisionId to get the id of the commit rather
1286 than the id of the current git object (for example, a tag)
1287
1288 """
1289 if not self.revisionExpr.startswith(R_TAGS):
1290 return self.GetRevisionId(self._allrefs)
1291
1292 try:
1293 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1294 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001295 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1296 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001297
David Pursehouse8a68ff92012-09-24 12:15:13 +09001298 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001299 if self.revisionId:
1300 return self.revisionId
1301
Mike Frysingerdede5642022-07-10 04:56:04 -04001302 rem = self.GetRemote()
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001303 rev = rem.ToLocal(self.revisionExpr)
1304
David Pursehouse8a68ff92012-09-24 12:15:13 +09001305 if all_refs is not None and rev in all_refs:
1306 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001307
1308 try:
1309 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1310 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001311 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1312 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001313
Raman Tenneti6a872c92021-01-14 19:17:50 -08001314 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001315 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001316 self.upstream = self.revisionExpr
1317
Raman Tenneti6a872c92021-01-14 19:17:50 -08001318 self.revisionId = revisionId
1319
Martin Kellye4e94d22017-03-21 16:05:12 -07001320 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321 """Perform only the local IO portion of the sync process.
1322 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001324 if not os.path.exists(self.gitdir):
1325 syncbuf.fail(self,
1326 'Cannot checkout %s due to missing network sync; Run '
1327 '`repo sync -n %s` first.' %
1328 (self.name, self.name))
1329 return
1330
Martin Kellye4e94d22017-03-21 16:05:12 -07001331 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001332 all_refs = self.bare_ref.all
1333 self.CleanPublishedCache(all_refs)
1334 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001335
Mike Frysinger0458faa2021-03-10 23:35:44 -05001336 # Special case the root of the repo client checkout. Make sure it doesn't
1337 # contain files being checked out to dirs we don't allow.
1338 if self.relpath == '.':
1339 PROTECTED_PATHS = {'.repo'}
1340 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1341 bad_paths = paths & PROTECTED_PATHS
1342 if bad_paths:
1343 syncbuf.fail(self,
1344 'Refusing to checkout project that writes to protected '
1345 'paths: %s' % (', '.join(bad_paths),))
1346 return
1347
David Pursehouse1d947b32012-10-25 12:23:11 +09001348 def _doff():
1349 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001350 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001351
Martin Kellye4e94d22017-03-21 16:05:12 -07001352 def _dosubmodules():
1353 self._SyncSubmodules(quiet=True)
1354
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001355 head = self.work_git.GetHead()
1356 if head.startswith(R_HEADS):
1357 branch = head[len(R_HEADS):]
1358 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001359 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001360 except KeyError:
1361 head = None
1362 else:
1363 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001365 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001366 # Currently on a detached HEAD. The user is assumed to
1367 # not have any local modifications worth worrying about.
1368 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001369 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001370 syncbuf.fail(self, _PriorSyncFailedError())
1371 return
1372
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001373 if head == revid:
1374 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001375 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001376 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001377 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001378 # The copy/linkfile config may have changed.
1379 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001380 return
1381 else:
1382 lost = self._revlist(not_rev(revid), HEAD)
1383 if lost:
1384 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001385
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001387 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001388 if submodules:
1389 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001390 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001391 syncbuf.fail(self, e)
1392 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001393 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001394 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001395
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001396 if head == revid:
1397 # No changes; don't do anything further.
1398 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001399 # The copy/linkfile config may have changed.
1400 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001401 return
1402
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001404
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001405 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001406 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001407 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001408 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001409 syncbuf.info(self,
1410 "leaving %s; does not track upstream",
1411 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001412 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001413 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001414 if submodules:
1415 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001416 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001417 syncbuf.fail(self, e)
1418 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001419 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001420 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001421
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001422 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001423
1424 # See if we can perform a fast forward merge. This can happen if our
1425 # branch isn't in the exact same state as we last published.
1426 try:
1427 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1428 # Skip the published logic.
1429 pub = False
1430 except GitError:
1431 pub = self.WasPublished(branch.name, all_refs)
1432
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001433 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001434 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001435 if not_merged:
1436 if upstream_gain:
1437 # The user has published this branch and some of those
1438 # commits are not yet merged upstream. We do not want
1439 # to rewrite the published commits so we punt.
1440 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001441 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001442 "branch %s is published (but not merged) and is now "
1443 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001444 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001445 elif pub == head:
1446 # All published commits are merged, and thus we are a
1447 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001448 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001449 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001450 if submodules:
1451 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001452 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001453
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001454 # Examine the local commits not in the remote. Find the
1455 # last one attributed to this user, if any.
1456 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001457 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001458 last_mine = None
1459 cnt_mine = 0
1460 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001461 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001462 if committer_email == self.UserEmail:
1463 last_mine = commit_id
1464 cnt_mine += 1
1465
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001466 if not upstream_gain and cnt_mine == len(local_changes):
Peter Kjellerstedta39af3d2022-09-01 19:24:36 +02001467 # The copy/linkfile config may have changed.
1468 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001469 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001470
1471 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001472 syncbuf.fail(self, _DirtyError())
1473 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001474
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001475 # If the upstream switched on us, warn the user.
1476 #
1477 if branch.merge != self.revisionExpr:
1478 if branch.merge and self.revisionExpr:
1479 syncbuf.info(self,
1480 'manifest switched %s...%s',
1481 branch.merge,
1482 self.revisionExpr)
1483 elif branch.merge:
1484 syncbuf.info(self,
1485 'manifest no longer tracks %s',
1486 branch.merge)
1487
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001488 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001489 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001490 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001492 syncbuf.info(self,
1493 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001494 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001495
Mike Frysingerdede5642022-07-10 04:56:04 -04001496 branch.remote = self.GetRemote()
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001497 if not ID_RE.match(self.revisionExpr):
1498 # in case of manifest sync the revisionExpr might be a SHA1
1499 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001500 if not branch.merge.startswith('refs/'):
1501 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001502 branch.Save()
1503
Mike Pontillod3153822012-02-28 11:53:24 -08001504 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001505 def _docopyandlink():
1506 self._CopyAndLinkFiles()
1507
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001508 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001509 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001510 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001511 if submodules:
1512 syncbuf.later2(self, _dosubmodules)
1513 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001514 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001515 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001516 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001517 if submodules:
1518 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001519 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001520 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001521 syncbuf.fail(self, e)
1522 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001523 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001524 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001525 if submodules:
1526 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001527
Mike Frysingere6a202f2019-08-02 15:57:57 -04001528 def AddCopyFile(self, src, dest, topdir):
1529 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001530
Mike Frysingere6a202f2019-08-02 15:57:57 -04001531 No filesystem changes occur here. Actual copying happens later on.
1532
1533 Paths should have basic validation run on them before being queued.
1534 Further checking will be handled when the actual copy happens.
1535 """
1536 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1537
1538 def AddLinkFile(self, src, dest, topdir):
1539 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1540
1541 No filesystem changes occur here. Actual linking happens later on.
1542
1543 Paths should have basic validation run on them before being queued.
1544 Further checking will be handled when the actual link happens.
1545 """
1546 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001547
James W. Mills24c13082012-04-12 15:04:13 -05001548 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001549 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001550
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001551 def DownloadPatchSet(self, change_id, patch_id):
1552 """Download a single patch set of a single change to FETCH_HEAD.
1553 """
Mike Frysingerdede5642022-07-10 04:56:04 -04001554 remote = self.GetRemote()
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001555
1556 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001557 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001558 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001559 if GitCommand(self, cmd, bare=True).Wait() != 0:
1560 return None
1561 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001562 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001563 change_id,
1564 patch_id,
1565 self.bare_git.rev_parse('FETCH_HEAD'))
1566
Mike Frysingerc0d18662020-02-19 19:19:18 -05001567 def DeleteWorktree(self, quiet=False, force=False):
1568 """Delete the source checkout and any other housekeeping tasks.
1569
1570 This currently leaves behind the internal .repo/ cache state. This helps
1571 when switching branches or manifest changes get reverted as we don't have
1572 to redownload all the git objects. But we should do some GC at some point.
1573
1574 Args:
1575 quiet: Whether to hide normal messages.
1576 force: Always delete tree even if dirty.
1577
1578 Returns:
1579 True if the worktree was completely cleaned out.
1580 """
1581 if self.IsDirty():
1582 if force:
1583 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
LaMont Jones8501d462022-06-22 19:21:15 +00001584 (self.RelPath(local=False),), file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001585 else:
1586 print('error: %s: Cannot remove project: uncommitted changes are '
LaMont Jones8501d462022-06-22 19:21:15 +00001587 'present.\n' % (self.RelPath(local=False),), file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001588 return False
1589
1590 if not quiet:
LaMont Jones8501d462022-06-22 19:21:15 +00001591 print('%s: Deleting obsolete checkout.' % (self.RelPath(local=False),))
Mike Frysingerc0d18662020-02-19 19:19:18 -05001592
1593 # Unlock and delink from the main worktree. We don't use git's worktree
1594 # remove because it will recursively delete projects -- we handle that
1595 # ourselves below. https://crbug.com/git/48
1596 if self.use_git_worktrees:
1597 needle = platform_utils.realpath(self.gitdir)
1598 # Find the git worktree commondir under .repo/worktrees/.
1599 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1600 assert output.startswith('worktree '), output
1601 commondir = output[9:]
1602 # Walk each of the git worktrees to see where they point.
1603 configs = os.path.join(commondir, 'worktrees')
1604 for name in os.listdir(configs):
1605 gitdir = os.path.join(configs, name, 'gitdir')
1606 with open(gitdir) as fp:
1607 relpath = fp.read().strip()
1608 # Resolve the checkout path and see if it matches this project.
1609 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1610 if fullpath == needle:
1611 platform_utils.rmtree(os.path.join(configs, name))
1612
1613 # Delete the .git directory first, so we're less likely to have a partially
1614 # working git repository around. There shouldn't be any git projects here,
1615 # so rmtree works.
1616
1617 # Try to remove plain files first in case of git worktrees. If this fails
1618 # for any reason, we'll fall back to rmtree, and that'll display errors if
1619 # it can't remove things either.
1620 try:
1621 platform_utils.remove(self.gitdir)
1622 except OSError:
1623 pass
1624 try:
1625 platform_utils.rmtree(self.gitdir)
1626 except OSError as e:
1627 if e.errno != errno.ENOENT:
1628 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1629 print('error: %s: Failed to delete obsolete checkout; remove manually, '
LaMont Jones8501d462022-06-22 19:21:15 +00001630 'then run `repo sync -l`.' % (self.RelPath(local=False),),
1631 file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001632 return False
1633
1634 # Delete everything under the worktree, except for directories that contain
1635 # another git project.
1636 dirs_to_remove = []
1637 failed = False
1638 for root, dirs, files in platform_utils.walk(self.worktree):
1639 for f in files:
1640 path = os.path.join(root, f)
1641 try:
1642 platform_utils.remove(path)
1643 except OSError as e:
1644 if e.errno != errno.ENOENT:
1645 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1646 failed = True
1647 dirs[:] = [d for d in dirs
1648 if not os.path.lexists(os.path.join(root, d, '.git'))]
1649 dirs_to_remove += [os.path.join(root, d) for d in dirs
1650 if os.path.join(root, d) not in dirs_to_remove]
1651 for d in reversed(dirs_to_remove):
1652 if platform_utils.islink(d):
1653 try:
1654 platform_utils.remove(d)
1655 except OSError as e:
1656 if e.errno != errno.ENOENT:
1657 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1658 failed = True
1659 elif not platform_utils.listdir(d):
1660 try:
1661 platform_utils.rmdir(d)
1662 except OSError as e:
1663 if e.errno != errno.ENOENT:
1664 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1665 failed = True
1666 if failed:
LaMont Jones8501d462022-06-22 19:21:15 +00001667 print('error: %s: Failed to delete obsolete checkout.' % (self.RelPath(local=False),),
Mike Frysingerc0d18662020-02-19 19:19:18 -05001668 file=sys.stderr)
1669 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1670 return False
1671
1672 # Try deleting parent dirs if they are empty.
1673 path = self.worktree
1674 while path != self.manifest.topdir:
1675 try:
1676 platform_utils.rmdir(path)
1677 except OSError as e:
1678 if e.errno != errno.ENOENT:
1679 break
1680 path = os.path.dirname(path)
1681
1682 return True
1683
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001684# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001685 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001686 """Create a new branch off the manifest's revision.
1687 """
Simran Basib9a1b732015-08-20 12:19:28 -07001688 if not branch_merge:
1689 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001690 head = self.work_git.GetHead()
1691 if head == (R_HEADS + name):
1692 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001693
David Pursehouse8a68ff92012-09-24 12:15:13 +09001694 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001695 if R_HEADS + name in all_refs:
Mike Frysingerad1b7bd2022-08-18 09:17:09 -04001696 return GitCommand(self, ['checkout', '-q', name, '--']).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001697
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001698 branch = self.GetBranch(name)
Mike Frysingerdede5642022-07-10 04:56:04 -04001699 branch.remote = self.GetRemote()
Simran Basib9a1b732015-08-20 12:19:28 -07001700 branch.merge = branch_merge
1701 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1702 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001703
1704 if revision is None:
1705 revid = self.GetRevisionId(all_refs)
1706 else:
1707 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001708
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001709 if head.startswith(R_HEADS):
1710 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001711 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001712 except KeyError:
1713 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001714 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001715 ref = R_HEADS + name
1716 self.work_git.update_ref(ref, revid)
1717 self.work_git.symbolic_ref(HEAD, ref)
1718 branch.Save()
1719 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001720
Mike Frysingerad1b7bd2022-08-18 09:17:09 -04001721 if GitCommand(self, ['checkout', '-q', '-b', branch.name, revid]).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001722 branch.Save()
1723 return True
1724 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001725
Wink Saville02d79452009-04-10 13:01:24 -07001726 def CheckoutBranch(self, name):
1727 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001728
1729 Args:
1730 name: The name of the branch to checkout.
1731
1732 Returns:
1733 True if the checkout succeeded; False if it didn't; None if the branch
1734 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001735 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001736 rev = R_HEADS + name
1737 head = self.work_git.GetHead()
1738 if head == rev:
1739 # Already on the branch
1740 #
1741 return True
Wink Saville02d79452009-04-10 13:01:24 -07001742
David Pursehouse8a68ff92012-09-24 12:15:13 +09001743 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001744 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001745 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001746 except KeyError:
1747 # Branch does not exist in this project
1748 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001749 return None
Wink Saville02d79452009-04-10 13:01:24 -07001750
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001751 if head.startswith(R_HEADS):
1752 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001753 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001754 except KeyError:
1755 head = None
1756
1757 if head == revid:
1758 # Same revision; just update HEAD to point to the new
1759 # target branch, but otherwise take no other action.
1760 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001761 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1762 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001763 return True
1764
1765 return GitCommand(self,
1766 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001767 capture_stdout=True,
1768 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001769
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001770 def AbandonBranch(self, name):
1771 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001772
1773 Args:
1774 name: The name of the branch to abandon.
1775
1776 Returns:
1777 True if the abandon succeeded; False if it didn't; None if the branch
1778 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001779 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001780 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001781 all_refs = self.bare_ref.all
1782 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001783 # Doesn't exist
1784 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001785
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001786 head = self.work_git.GetHead()
1787 if head == rev:
1788 # We can't destroy the branch while we are sitting
1789 # on it. Switch to a detached HEAD.
1790 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001791 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001792
David Pursehouse8a68ff92012-09-24 12:15:13 +09001793 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001794 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001795 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001796 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001797 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001798
1799 return GitCommand(self,
1800 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001801 capture_stdout=True,
1802 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001803
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001804 def PruneHeads(self):
1805 """Prune any topic branches already merged into upstream.
1806 """
1807 cb = self.CurrentBranch
1808 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001809 left = self._allrefs
1810 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001811 if name.startswith(R_HEADS):
1812 name = name[len(R_HEADS):]
1813 if cb is None or name != cb:
1814 kill.append(name)
1815
Mike Frysingera3794e92021-03-11 23:24:01 -05001816 # Minor optimization: If there's nothing to prune, then don't try to read
1817 # any project state.
1818 if not kill and not cb:
1819 return []
1820
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001821 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001822 if cb is not None \
1823 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001824 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001825 self.work_git.DetachHead(HEAD)
1826 kill.append(cb)
1827
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001828 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001829 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001830
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001831 try:
1832 self.bare_git.DetachHead(rev)
1833
1834 b = ['branch', '-d']
1835 b.extend(kill)
1836 b = GitCommand(self, b, bare=True,
1837 capture_stdout=True,
1838 capture_stderr=True)
1839 b.Wait()
1840 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001841 if ID_RE.match(old):
1842 self.bare_git.DetachHead(old)
1843 else:
1844 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001845 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001846
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001847 for branch in kill:
1848 if (R_HEADS + branch) not in left:
1849 self.CleanPublishedCache()
1850 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001851
1852 if cb and cb not in kill:
1853 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001854 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001855
1856 kept = []
1857 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001858 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001859 branch = self.GetBranch(branch)
1860 base = branch.LocalMerge
1861 if not base:
1862 base = rev
1863 kept.append(ReviewableBranch(self, branch, base))
1864 return kept
1865
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001866# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001867 def GetRegisteredSubprojects(self):
1868 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001869
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001870 def rec(subprojects):
1871 if not subprojects:
1872 return
1873 result.extend(subprojects)
1874 for p in subprojects:
1875 rec(p.subprojects)
1876 rec(self.subprojects)
1877 return result
1878
1879 def _GetSubmodules(self):
1880 # Unfortunately we cannot call `git submodule status --recursive` here
1881 # because the working tree might not exist yet, and it cannot be used
1882 # without a working tree in its current implementation.
1883
1884 def get_submodules(gitdir, rev):
1885 # Parse .gitmodules for submodule sub_paths and sub_urls
1886 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1887 if not sub_paths:
1888 return []
1889 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1890 # revision of submodule repository
1891 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1892 submodules = []
1893 for sub_path, sub_url in zip(sub_paths, sub_urls):
1894 try:
1895 sub_rev = sub_revs[sub_path]
1896 except KeyError:
1897 # Ignore non-exist submodules
1898 continue
1899 submodules.append((sub_rev, sub_path, sub_url))
1900 return submodules
1901
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001902 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1903 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001904
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001905 def parse_gitmodules(gitdir, rev):
1906 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1907 try:
Anthony King7bdac712014-07-16 12:56:40 +01001908 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1909 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001910 except GitError:
1911 return [], []
1912 if p.Wait() != 0:
1913 return [], []
1914
1915 gitmodules_lines = []
1916 fd, temp_gitmodules_path = tempfile.mkstemp()
1917 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001918 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001919 os.close(fd)
1920 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001921 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1922 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001923 if p.Wait() != 0:
1924 return [], []
1925 gitmodules_lines = p.stdout.split('\n')
1926 except GitError:
1927 return [], []
1928 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001929 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001930
1931 names = set()
1932 paths = {}
1933 urls = {}
1934 for line in gitmodules_lines:
1935 if not line:
1936 continue
1937 m = re_path.match(line)
1938 if m:
1939 names.add(m.group(1))
1940 paths[m.group(1)] = m.group(2)
1941 continue
1942 m = re_url.match(line)
1943 if m:
1944 names.add(m.group(1))
1945 urls[m.group(1)] = m.group(2)
1946 continue
1947 names = sorted(names)
1948 return ([paths.get(name, '') for name in names],
1949 [urls.get(name, '') for name in names])
1950
1951 def git_ls_tree(gitdir, rev, paths):
1952 cmd = ['ls-tree', rev, '--']
1953 cmd.extend(paths)
1954 try:
Anthony King7bdac712014-07-16 12:56:40 +01001955 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1956 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001957 except GitError:
1958 return []
1959 if p.Wait() != 0:
1960 return []
1961 objects = {}
1962 for line in p.stdout.split('\n'):
1963 if not line.strip():
1964 continue
1965 object_rev, object_path = line.split()[2:4]
1966 objects[object_path] = object_rev
1967 return objects
1968
1969 try:
1970 rev = self.GetRevisionId()
1971 except GitError:
1972 return []
1973 return get_submodules(self.gitdir, rev)
1974
1975 def GetDerivedSubprojects(self):
1976 result = []
1977 if not self.Exists:
1978 # If git repo does not exist yet, querying its submodules will
1979 # mess up its states; so return here.
1980 return result
1981 for rev, path, url in self._GetSubmodules():
1982 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001983 relpath, worktree, gitdir, objdir = \
1984 self.manifest.GetSubprojectPaths(self, name, path)
1985 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001986 if project:
1987 result.extend(project.GetDerivedSubprojects())
1988 continue
David James8d201162013-10-11 17:03:19 -07001989
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001990 if url.startswith('..'):
1991 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001992 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001993 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001994 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001995 review=self.remote.review,
1996 revision=self.remote.revision)
1997 subproject = Project(manifest=self.manifest,
1998 name=name,
1999 remote=remote,
2000 gitdir=gitdir,
2001 objdir=objdir,
2002 worktree=worktree,
2003 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002004 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002005 revisionId=rev,
2006 rebase=self.rebase,
2007 groups=self.groups,
2008 sync_c=self.sync_c,
2009 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002010 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002011 parent=self,
2012 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002013 result.append(subproject)
2014 result.extend(subproject.GetDerivedSubprojects())
2015 return result
2016
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002017# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002018 def EnableRepositoryExtension(self, key, value='true', version=1):
2019 """Enable git repository extension |key| with |value|.
2020
2021 Args:
2022 key: The extension to enabled. Omit the "extensions." prefix.
2023 value: The value to use for the extension.
2024 version: The minimum git repository version needed.
2025 """
2026 # Make sure the git repo version is new enough already.
2027 found_version = self.config.GetInt('core.repositoryFormatVersion')
2028 if found_version is None:
2029 found_version = 0
2030 if found_version < version:
2031 self.config.SetString('core.repositoryFormatVersion', str(version))
2032
2033 # Enable the extension!
2034 self.config.SetString('extensions.%s' % (key,), value)
2035
Mike Frysinger50a81de2020-09-06 15:51:21 -04002036 def ResolveRemoteHead(self, name=None):
2037 """Find out what the default branch (HEAD) points to.
2038
2039 Normally this points to refs/heads/master, but projects are moving to main.
2040 Support whatever the server uses rather than hardcoding "master" ourselves.
2041 """
2042 if name is None:
2043 name = self.remote.name
2044
2045 # The output will look like (NB: tabs are separators):
2046 # ref: refs/heads/master HEAD
2047 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2048 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2049
2050 for line in output.splitlines():
2051 lhs, rhs = line.split('\t', 1)
2052 if rhs == 'HEAD' and lhs.startswith('ref:'):
2053 return lhs[4:].strip()
2054
2055 return None
2056
Zac Livingstone4332262017-06-16 08:56:09 -06002057 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002058 try:
2059 # if revision (sha or tag) is not present then following function
2060 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002061 self.bare_git.rev_list('-1', '--missing=allow-any',
2062 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00002063 if self.upstream:
Mike Frysingerdede5642022-07-10 04:56:04 -04002064 rev = self.GetRemote().ToLocal(self.upstream)
Xin Li0e776a52021-06-29 21:42:34 +00002065 self.bare_git.rev_list('-1', '--missing=allow-any',
2066 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002067 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002068 return True
2069 except GitError:
2070 # There is no such persistent revision. We have to fetch it.
2071 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002072
Julien Campergue335f5ef2013-10-16 11:02:35 +02002073 def _FetchArchive(self, tarpath, cwd=None):
2074 cmd = ['archive', '-v', '-o', tarpath]
2075 cmd.append('--remote=%s' % self.remote.url)
LaMont Jones8501d462022-06-22 19:21:15 +00002076 cmd.append('--prefix=%s/' % self.RelPath(local=False))
Julien Campergue335f5ef2013-10-16 11:02:35 +02002077 cmd.append(self.revisionExpr)
2078
2079 command = GitCommand(self, cmd, cwd=cwd,
2080 capture_stdout=True,
2081 capture_stderr=True)
2082
2083 if command.Wait() != 0:
2084 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2085
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002086 def _RemoteFetch(self, name=None,
2087 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002088 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002089 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002090 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002091 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002092 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002093 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002094 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002095 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002096 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002097 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002098 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002099 clone_filter=None,
2100 retry_fetches=2,
2101 retry_sleep_initial_sec=4.0,
2102 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002103 is_sha1 = False
2104 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002105 # The depth should not be used when fetching to a mirror because
2106 # it will result in a shallow repository that cannot be cloned or
2107 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002108 # The repo project should also never be synced with partial depth.
2109 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2110 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002111
Shawn Pearce69e04d82014-01-29 12:48:54 -08002112 if depth:
2113 current_branch_only = True
2114
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002115 if ID_RE.match(self.revisionExpr) is not None:
2116 is_sha1 = True
2117
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002118 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002119 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002120 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002121 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002122 elif self.upstream and self.upstream.startswith(R_TAGS):
2123 # This is a tag and its commit id should never change.
2124 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002125
2126 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002127 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002128 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002129 print('Skipped fetching project %s (already have persistent ref)'
2130 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002131 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002132 if is_sha1 and not depth:
2133 # When syncing a specific commit and --depth is not set:
2134 # * if upstream is explicitly specified and is not a sha1, fetch only
2135 # upstream as users expect only upstream to be fetch.
2136 # Note: The commit might not be in upstream in which case the sync
2137 # will fail.
2138 # * otherwise, fetch all branches to make sure we end up with the
2139 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002140 if self.upstream:
2141 current_branch_only = not ID_RE.match(self.upstream)
2142 else:
2143 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002144
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002145 if not name:
2146 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002147
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002148 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002149 if not remote.PreConnectFetch(ssh_proxy):
2150 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002151
Shawn O. Pearce88443382010-10-08 10:02:09 +02002152 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002153 if alt_dir and 'objects' == os.path.basename(alt_dir):
2154 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002155 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002156
David Pursehouse8a68ff92012-09-24 12:15:13 +09002157 all_refs = self.bare_ref.all
2158 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002159 tmp = set()
2160
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302161 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002162 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002163 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002164 all_refs[r] = ref_id
2165 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002166 continue
2167
David Pursehouse8a68ff92012-09-24 12:15:13 +09002168 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002169 continue
2170
David Pursehouse8a68ff92012-09-24 12:15:13 +09002171 r = 'refs/_alt/%s' % ref_id
2172 all_refs[r] = ref_id
2173 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002174 tmp.add(r)
2175
heping3d7bbc92017-04-12 19:51:47 +08002176 tmp_packed_lines = []
2177 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002178
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302179 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002180 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002181 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002182 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002183 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002184
heping3d7bbc92017-04-12 19:51:47 +08002185 tmp_packed = ''.join(tmp_packed_lines)
2186 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002187 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002188 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002189 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002190
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002191 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002192
Xin Li745be2e2019-06-03 11:24:30 -07002193 if clone_filter:
2194 git_require((2, 19, 0), fail=True, msg='partial clones')
2195 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002196 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002197
Conley Owensf97e8382015-01-21 11:12:46 -08002198 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002199 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002200 else:
2201 # If this repo has shallow objects, then we don't know which refs have
2202 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2203 # do this with projects that don't have shallow objects, since it is less
2204 # efficient.
2205 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2206 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002207
Mike Frysinger4847e052020-02-22 00:07:35 -05002208 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002209 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002210 if not quiet and sys.stdout.isatty():
2211 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002212 if not self.worktree:
2213 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002214 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002215
Mike Frysingere57f1142019-03-18 21:27:54 -04002216 if force_sync:
2217 cmd.append('--force')
2218
David Pursehouse74cfd272015-10-14 10:50:15 +09002219 if prune:
2220 cmd.append('--prune')
2221
LaMont Jonesff6b1da2022-06-01 21:03:34 +00002222 # Always pass something for --recurse-submodules, git with GIT_DIR behaves
2223 # incorrectly when not given `--recurse-submodules=no`. (b/218891912)
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002224 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002225
Kuang-che Wu6856f982019-11-25 12:37:55 +08002226 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002227 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002228 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002229 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002230 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002231 spec.append('tag')
2232 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002233
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302234 if self.manifest.IsMirror and not current_branch_only:
2235 branch = None
2236 else:
2237 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002238 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002239 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002240 # Shallow checkout of a specific commit, fetch from that commit and not
2241 # the heads only as the commit might be deeper in the history.
2242 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002243 if self.upstream:
2244 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002245 else:
2246 if is_sha1:
2247 branch = self.upstream
2248 if branch is not None and branch.strip():
2249 if not branch.startswith('refs/'):
2250 branch = R_HEADS + branch
2251 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2252
2253 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2254 # whole repo.
2255 if self.manifest.IsMirror and not spec:
2256 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2257
2258 # If using depth then we should not get all the tags since they may
2259 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002260 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002261 cmd.append('--no-tags')
2262 else:
2263 cmd.append('--tags')
2264 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2265
Conley Owens80b87fe2014-05-09 17:13:44 -07002266 cmd.extend(spec)
2267
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002268 # At least one retry minimum due to git remote prune.
2269 retry_fetches = max(retry_fetches, 2)
2270 retry_cur_sleep = retry_sleep_initial_sec
2271 ok = prune_tried = False
2272 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002273 gitcmd = GitCommand(
2274 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2275 ssh_proxy=ssh_proxy,
2276 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002277 if gitcmd.stdout and not quiet and output_redir:
2278 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002279 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002280 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002281 ok = True
2282 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002283
2284 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002285 elif (gitcmd.stdout and
2286 'error:' in gitcmd.stdout and
2287 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002288 # Fallthru to sleep+retry logic at the bottom.
2289 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002290
Mike Frysinger6823bc22021-04-15 02:06:28 -04002291 # Try to prune remote branches once in case there are conflicts.
2292 # For example, if the remote had refs/heads/upstream, but deleted that and
2293 # now has refs/heads/upstream/foo.
2294 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002295 'error:' in gitcmd.stdout and
2296 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002297 not prune_tried):
2298 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002299 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002300 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002301 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002302 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002303 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002304 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002305 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002306 continue
Brian Harring14a66742012-09-28 20:21:57 -07002307 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002308 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2309 # in sha1 mode, we just tried sync'ing from the upstream field; it
2310 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002311 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002312 elif ret < 0:
2313 # Git died with a signal, exit immediately
2314 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002315
2316 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002317 if not verbose and gitcmd.stdout:
2318 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002319 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002320 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2321 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002322 time.sleep(retry_cur_sleep)
2323 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2324 MAXIMUM_RETRY_SLEEP_SEC)
2325 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2326 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002327
2328 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002329 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002330 if old_packed != '':
2331 _lwrite(packed_refs, old_packed)
2332 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002333 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002334 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002335
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002336 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002337 # We just synced the upstream given branch; verify we
2338 # got what we wanted, else trigger a second run of all
2339 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002340 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002341 # Sync the current branch only with depth set to None.
2342 # We always pass depth=None down to avoid infinite recursion.
2343 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002344 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002345 current_branch_only=current_branch_only and depth,
2346 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002347 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002348
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002349 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002350
Mike Frysingere50b6a72020-02-19 01:45:48 -05002351 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002352 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002353 return False
2354
Mike Frysingerdede5642022-07-10 04:56:04 -04002355 remote = self.GetRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002356 bundle_url = remote.url + '/clone.bundle'
2357 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002358 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2359 'persistent-http',
2360 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002361 return False
2362
2363 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2364 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2365
2366 exist_dst = os.path.exists(bundle_dst)
2367 exist_tmp = os.path.exists(bundle_tmp)
2368
2369 if not initial and not exist_dst and not exist_tmp:
2370 return False
2371
2372 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002373 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2374 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002375 if not exist_dst:
2376 return False
2377
2378 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002379 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002380 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002381 if not quiet and sys.stdout.isatty():
2382 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002383 if not self.worktree:
2384 cmd.append('--update-head-ok')
2385 cmd.append(bundle_dst)
2386 for f in remote.fetch:
2387 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002388 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002389
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002390 ok = GitCommand(
2391 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002392 platform_utils.remove(bundle_dst, missing_ok=True)
2393 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002394 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002395
Mike Frysingere50b6a72020-02-19 01:45:48 -05002396 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002397 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002398
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002399 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002400 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002401 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002402 if os.path.exists(tmpPath):
2403 size = os.stat(tmpPath).st_size
2404 if size >= 1024:
2405 cmd += ['--continue-at', '%d' % (size,)]
2406 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002407 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002408 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002409 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002410 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002411 if proxy:
2412 cmd += ['--proxy', proxy]
2413 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2414 cmd += ['--proxy', os.environ['http_proxy']]
2415 if srcUrl.startswith('persistent-https'):
2416 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2417 elif srcUrl.startswith('persistent-http'):
2418 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002419 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002420
Joanna Wanga6c52f52022-11-03 16:51:19 -04002421 proc = None
2422 with Trace('Fetching bundle: %s', ' '.join(cmd)):
2423 if verbose:
2424 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2425 stdout = None if verbose else subprocess.PIPE
2426 stderr = None if verbose else subprocess.STDOUT
2427 try:
2428 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
2429 except OSError:
2430 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002431
Mike Frysingere50b6a72020-02-19 01:45:48 -05002432 (output, _) = proc.communicate()
2433 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002434
Dave Borowitz137d0132015-01-02 11:12:54 -08002435 if curlret == 22:
2436 # From curl man page:
2437 # 22: HTTP page not retrieved. The requested url was not found or
2438 # returned another error with the HTTP error code being 400 or above.
2439 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002440 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002441 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2442 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002443 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002444 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002445 elif curlret and not verbose and output:
2446 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002447
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002448 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002449 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002450 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002451 return True
2452 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002453 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002454 return False
2455 else:
2456 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002457
Kris Giesingc8d882a2014-12-23 13:02:32 -08002458 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002459 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002460 with open(path, 'rb') as f:
2461 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002462 return True
2463 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002464 if not quiet:
2465 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002466 return False
2467 except OSError:
2468 return False
2469
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002470 def _Checkout(self, rev, quiet=False):
2471 cmd = ['checkout']
2472 if quiet:
2473 cmd.append('-q')
2474 cmd.append(rev)
2475 cmd.append('--')
2476 if GitCommand(self, cmd).Wait() != 0:
2477 if self._allrefs:
2478 raise GitError('%s checkout %s ' % (self.name, rev))
2479
Mike Frysinger915fda12020-03-22 12:15:20 -04002480 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002481 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002482 if ffonly:
2483 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002484 if record_origin:
2485 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002486 cmd.append(rev)
2487 cmd.append('--')
2488 if GitCommand(self, cmd).Wait() != 0:
2489 if self._allrefs:
2490 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2491
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302492 def _LsRemote(self, refs):
2493 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302494 p = GitCommand(self, cmd, capture_stdout=True)
2495 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002496 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302497 return None
2498
Anthony King7bdac712014-07-16 12:56:40 +01002499 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002500 cmd = ['revert']
2501 cmd.append('--no-edit')
2502 cmd.append(rev)
2503 cmd.append('--')
2504 if GitCommand(self, cmd).Wait() != 0:
2505 if self._allrefs:
2506 raise GitError('%s revert %s ' % (self.name, rev))
2507
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002508 def _ResetHard(self, rev, quiet=True):
2509 cmd = ['reset', '--hard']
2510 if quiet:
2511 cmd.append('-q')
2512 cmd.append(rev)
2513 if GitCommand(self, cmd).Wait() != 0:
2514 raise GitError('%s reset --hard %s ' % (self.name, rev))
2515
Martin Kellye4e94d22017-03-21 16:05:12 -07002516 def _SyncSubmodules(self, quiet=True):
2517 cmd = ['submodule', 'update', '--init', '--recursive']
2518 if quiet:
2519 cmd.append('-q')
2520 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002521 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002522
Anthony King7bdac712014-07-16 12:56:40 +01002523 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002524 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002525 if onto is not None:
2526 cmd.extend(['--onto', onto])
2527 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002528 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002529 raise GitError('%s rebase %s ' % (self.name, upstream))
2530
Pierre Tardy3d125942012-05-04 12:18:12 +02002531 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002532 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002533 if ffonly:
2534 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002535 if GitCommand(self, cmd).Wait() != 0:
2536 raise GitError('%s merge %s ' % (self.name, head))
2537
David Pursehousee8ace262020-02-13 12:41:15 +09002538 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002539 init_git_dir = not os.path.exists(self.gitdir)
2540 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002541 try:
2542 # Initialize the bare repository, which contains all of the objects.
2543 if init_obj_dir:
2544 os.makedirs(self.objdir)
2545 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002546
Mike Frysinger333c0a42021-11-15 12:39:00 -05002547 self._UpdateHooks(quiet=quiet)
2548
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002549 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002550 # Enable per-worktree config file support if possible. This is more a
2551 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002552 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002553 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002554
Kevin Degib1a07b82015-07-27 13:33:43 -06002555 # If we have a separate directory to hold refs, initialize it as well.
2556 if self.objdir != self.gitdir:
2557 if init_git_dir:
2558 os.makedirs(self.gitdir)
2559
2560 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002561 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002562 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002563 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002564 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002565 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002566 print("Retrying clone after deleting %s" %
2567 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002568 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002569 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2570 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002571 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002572 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002573 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2574 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002575 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002576 raise e
2577 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002578
Kevin Degi384b3c52014-10-16 16:02:58 -06002579 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002580 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002581 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002582
LaMont Jonescc879a92021-11-18 22:40:18 +00002583 def _expanded_ref_dirs():
2584 """Iterate through the possible git reference directory paths."""
2585 name = self.name + '.git'
2586 yield mirror_git or os.path.join(ref_dir, name)
2587 for prefix in '', self.remote.name:
2588 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2589 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002590
LaMont Jonescc879a92021-11-18 22:40:18 +00002591 if ref_dir or mirror_git:
2592 found_ref_dir = None
2593 for path in _expanded_ref_dirs():
2594 if os.path.exists(path):
2595 found_ref_dir = path
2596 break
2597 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002598
Kevin Degib1a07b82015-07-27 13:33:43 -06002599 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002600 if not os.path.isabs(ref_dir):
2601 # The alternate directory is relative to the object database.
2602 ref_dir = os.path.relpath(ref_dir,
2603 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002604 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002605 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002606
Kevin Degib1a07b82015-07-27 13:33:43 -06002607 m = self.manifest.manifestProject.config
2608 for key in ['user.name', 'user.email']:
2609 if m.Has(key, include_defaults=False):
2610 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002611 if not self.manifest.EnableGitLfs:
2612 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2613 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002614 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002615 except Exception:
2616 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002617 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002618 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002619 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002620 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002621
David Pursehousee8ace262020-02-13 12:41:15 +09002622 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002623 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002624 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002625
David Pursehousee8ace262020-02-13 12:41:15 +09002626 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002627 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002628 if not os.path.exists(hooks):
2629 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002630
2631 # Delete sample hooks. They're noise.
2632 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002633 try:
2634 platform_utils.remove(hook, missing_ok=True)
2635 except PermissionError:
2636 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002637
Jonathan Nieder93719792015-03-17 11:29:58 -07002638 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002639 name = os.path.basename(stock_hook)
2640
Victor Boivie65e0f352011-04-18 11:23:29 +02002641 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002642 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002643 # Don't install a Gerrit Code Review hook if this
2644 # project does not appear to use it for reviews.
2645 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002646 # Since the manifest project is one of those, but also
2647 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002648 continue
2649
2650 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002651 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002652 continue
2653 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002654 # If the files are the same, we'll leave it alone. We create symlinks
2655 # below by default but fallback to hardlinks if the OS blocks them.
2656 # So if we're here, it's probably because we made a hardlink below.
2657 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002658 if not quiet:
2659 _warn("%s: Not replacing locally modified %s hook",
LaMont Jones8501d462022-06-22 19:21:15 +00002660 self.RelPath(local=False), name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002661 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002662 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002663 platform_utils.symlink(
2664 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002665 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002666 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002667 try:
2668 os.link(stock_hook, dst)
2669 except OSError:
2670 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002671 else:
2672 raise
2673
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002674 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002675 if self.remote.url:
Mike Frysingerdede5642022-07-10 04:56:04 -04002676 remote = self.GetRemote()
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002677 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002678 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002679 remote.review = self.remote.review
2680 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002681
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002682 if self.worktree:
2683 remote.ResetFetch(mirror=False)
2684 else:
2685 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002686 remote.Save()
2687
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002688 def _InitMRef(self):
Mike Frysinger790f4ce2020-12-07 22:04:55 -05002689 """Initialize the pseudo m/<manifest branch> ref."""
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002690 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002691 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002692 # Set up the m/ space to point to the worktree-specific ref space.
2693 # We'll update the worktree-specific ref space on each checkout.
2694 ref = R_M + self.manifest.branch
2695 if not self.bare_ref.symref(ref):
2696 self.bare_git.symbolic_ref(
2697 '-m', 'redirecting to worktree scope',
2698 ref, R_WORKTREE_M + self.manifest.branch)
2699
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002700 # We can't update this ref with git worktrees until it exists.
2701 # We'll wait until the initial checkout to set it.
2702 if not os.path.exists(self.worktree):
2703 return
2704
2705 base = R_WORKTREE_M
2706 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002707
2708 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002709 else:
2710 base = R_M
2711 active_git = self.bare_git
2712
2713 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002714
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002715 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002716 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002717
Remy Böhmer1469c282020-12-15 18:49:02 +01002718 def _InitAnyMRef(self, ref, active_git, detach=False):
Mike Frysinger790f4ce2020-12-07 22:04:55 -05002719 """Initialize |ref| in |active_git| to the value in the manifest.
2720
2721 This points |ref| to the <project> setting in the manifest.
2722
2723 Args:
2724 ref: The branch to update.
2725 active_git: The git repository to make updates in.
2726 detach: Whether to update target of symbolic refs, or overwrite the ref
2727 directly (and thus make it non-symbolic).
2728 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002729 cur = self.bare_ref.symref(ref)
2730
2731 if self.revisionId:
2732 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2733 msg = 'manifest set to %s' % self.revisionId
2734 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002735 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002736 else:
Mike Frysingerdede5642022-07-10 04:56:04 -04002737 remote = self.GetRemote()
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002738 dst = remote.ToLocal(self.revisionExpr)
2739 if cur != dst:
2740 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002741 if detach:
2742 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2743 else:
2744 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002745
Mike Frysingerc72bd842021-11-14 03:58:00 -05002746 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002747 # Git worktrees don't use symlinks to share at all.
2748 if self.use_git_worktrees:
2749 return
2750
Mike Frysingerd33dce02021-12-20 18:16:33 -05002751 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002752 # Try to self-heal a bit in simple cases.
2753 dst_path = os.path.join(destdir, name)
2754 src_path = os.path.join(srcdir, name)
2755
Mike Frysingered4f2112020-02-11 23:06:29 -05002756 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002757 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002758 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002759 # Fail if the links are pointing to the wrong place
2760 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002761 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002762 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002763 'work tree. If you\'re comfortable with the '
2764 'possibility of losing the work tree\'s git metadata,'
2765 ' use `repo sync --force-sync {0}` to '
LaMont Jones8501d462022-06-22 19:21:15 +00002766 'proceed.'.format(self.RelPath(local=False)))
Kevin Degi384b3c52014-10-16 16:02:58 -06002767
Mike Frysingerc72bd842021-11-14 03:58:00 -05002768 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002769 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2770
2771 Args:
2772 gitdir: The bare git repository. Must already be initialized.
2773 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002774 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2775 This saves you the effort of initializing |dotgit| yourself.
2776 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002777 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002778 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002779
2780 to_copy = []
2781 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002782 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002783
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002784 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002785 for name in set(to_copy).union(to_symlink):
2786 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002787 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002788 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002789
Kevin Degi384b3c52014-10-16 16:02:58 -06002790 if os.path.lexists(dst):
2791 continue
David James8d201162013-10-11 17:03:19 -07002792
2793 # If the source dir doesn't exist, create an empty dir.
2794 if name in symlink_dirs and not os.path.lexists(src):
2795 os.makedirs(src)
2796
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002797 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002798 platform_utils.symlink(
2799 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002800 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002801 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002802 shutil.copytree(src, dst)
2803 elif os.path.isfile(src):
2804 shutil.copy(src, dst)
2805
David James8d201162013-10-11 17:03:19 -07002806 except OSError as e:
2807 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002808 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002809 else:
2810 raise
2811
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002812 def _InitGitWorktree(self):
2813 """Init the project using git worktrees."""
2814 self.bare_git.worktree('prune')
2815 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2816 self.worktree, self.GetRevisionId())
2817
2818 # Rewrite the internal state files to use relative paths between the
2819 # checkouts & worktrees.
2820 dotgit = os.path.join(self.worktree, '.git')
2821 with open(dotgit, 'r') as fp:
2822 # Figure out the checkout->worktree path.
2823 setting = fp.read()
2824 assert setting.startswith('gitdir:')
2825 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002826 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2827 # of file permissions. Delete it and recreate it from scratch to avoid.
2828 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002829 # Use relative path from checkout->worktree & maintain Unix line endings
2830 # on all OS's to match git behavior.
2831 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002832 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2833 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002834 # Use relative path from worktree->checkout & maintain Unix line endings
2835 # on all OS's to match git behavior.
2836 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002837 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2838
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002839 self._InitMRef()
2840
Martin Kellye4e94d22017-03-21 16:05:12 -07002841 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002842 """Setup the worktree .git path.
2843
2844 This is the user-visible path like src/foo/.git/.
2845
2846 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2847 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2848
2849 Older checkouts had .git/ directories. If we see that, migrate it.
2850
2851 This also handles changes in the manifest. Maybe this project was backed
2852 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2853 the path we point to under .repo/projects/ to match.
2854 """
2855 dotgit = os.path.join(self.worktree, '.git')
2856
2857 # If using an old layout style (a directory), migrate it.
2858 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2859 self._MigrateOldWorkTreeGitDir(dotgit)
2860
2861 init_dotgit = not os.path.exists(dotgit)
2862 if self.use_git_worktrees:
2863 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002864 self._InitGitWorktree()
2865 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002866 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002867 if not init_dotgit:
2868 # See if the project has changed.
2869 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2870 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002871
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002872 if init_dotgit or not os.path.exists(dotgit):
2873 os.makedirs(self.worktree, exist_ok=True)
2874 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002875
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002876 if init_dotgit:
2877 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002878
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002879 # Finish checking out the worktree.
2880 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2881 if GitCommand(self, cmd).Wait() != 0:
2882 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002883
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002884 if submodules:
2885 self._SyncSubmodules(quiet=True)
2886 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002887
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002888 @classmethod
2889 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2890 """Migrate the old worktree .git/ dir style to a symlink.
2891
2892 This logic specifically only uses state from |dotgit| to figure out where to
2893 move content and not |self|. This way if the backing project also changed
2894 places, we only do the .git/ dir to .git symlink migration here. The path
2895 updates will happen independently.
2896 """
2897 # Figure out where in .repo/projects/ it's pointing to.
2898 if not os.path.islink(os.path.join(dotgit, 'refs')):
2899 raise GitError(f'{dotgit}: unsupported checkout state')
2900 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2901
2902 # Remove known symlink paths that exist in .repo/projects/.
2903 KNOWN_LINKS = {
2904 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2905 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2906 }
2907 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2908 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002909 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2910 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002911 }
2912
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002913 # First see if we'd succeed before starting the migration.
2914 unknown_paths = []
2915 for name in platform_utils.listdir(dotgit):
2916 # Ignore all temporary/backup names. These are common with vim & emacs.
2917 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2918 continue
2919
2920 dotgit_path = os.path.join(dotgit, name)
2921 if name in KNOWN_LINKS:
2922 if not platform_utils.islink(dotgit_path):
2923 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2924 else:
2925 gitdir_path = os.path.join(gitdir, name)
2926 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2927 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2928 if unknown_paths:
2929 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2930
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002931 # Now walk the paths and sync the .git/ to .repo/projects/.
2932 for name in platform_utils.listdir(dotgit):
2933 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002934
2935 # Ignore all temporary/backup names. These are common with vim & emacs.
2936 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2937 platform_utils.remove(dotgit_path)
2938 elif name in KNOWN_LINKS:
2939 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002940 else:
2941 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002942 platform_utils.remove(gitdir_path, missing_ok=True)
2943 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002944
2945 # Now that the dir should be empty, clear it out, and symlink it over.
2946 platform_utils.rmdir(dotgit)
2947 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002948
Renaud Paquay788e9622017-01-27 11:41:12 -08002949 def _get_symlink_error_message(self):
2950 if platform_utils.isWindows():
2951 return ('Unable to create symbolic link. Please re-run the command as '
2952 'Administrator, or see '
2953 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2954 'for other options.')
2955 return 'filesystem must support symlinks'
2956
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002957 def _revlist(self, *args, **kw):
2958 a = []
2959 a.extend(args)
2960 a.append('--')
2961 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002962
2963 @property
2964 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002965 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002966
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002967 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002968 """Get logs between two revisions of this project."""
2969 comp = '..'
2970 if rev1:
2971 revs = [rev1]
2972 if rev2:
2973 revs.extend([comp, rev2])
2974 cmd = ['log', ''.join(revs)]
2975 out = DiffColoring(self.config)
2976 if out.is_on and color:
2977 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002978 if pretty_format is not None:
2979 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002980 if oneline:
2981 cmd.append('--oneline')
2982
2983 try:
2984 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2985 if log.Wait() == 0:
2986 return log.stdout
2987 except GitError:
2988 # worktree may not exist if groups changed for example. In that case,
2989 # try in gitdir instead.
2990 if not os.path.exists(self.worktree):
2991 return self.bare_git.log(*cmd[1:])
2992 else:
2993 raise
2994 return None
2995
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002996 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2997 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002998 """Get the list of logs from this revision to given revisionId"""
2999 logs = {}
3000 selfId = self.GetRevisionId(self._allrefs)
3001 toId = toProject.GetRevisionId(toProject._allrefs)
3002
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003003 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3004 pretty_format=pretty_format)
3005 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3006 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003007 return logs
3008
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003009 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003010
David James8d201162013-10-11 17:03:19 -07003011 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003012 self._project = project
3013 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003014 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003015
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09003016 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
3017 def __getstate__(self):
3018 return (self._project, self._bare, self._gitdir)
3019
3020 def __setstate__(self, state):
3021 self._project, self._bare, self._gitdir = state
3022
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003023 def LsOthers(self):
3024 p = GitCommand(self._project,
3025 ['ls-files',
3026 '-z',
3027 '--others',
3028 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003029 bare=False,
David James8d201162013-10-11 17:03:19 -07003030 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003031 capture_stdout=True,
3032 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003033 if p.Wait() == 0:
3034 out = p.stdout
3035 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003036 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003037 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003038 return []
3039
3040 def DiffZ(self, name, *args):
3041 cmd = [name]
3042 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003043 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003044 cmd.extend(args)
3045 p = GitCommand(self._project,
3046 cmd,
David James8d201162013-10-11 17:03:19 -07003047 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003048 bare=False,
3049 capture_stdout=True,
3050 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05003051 p.Wait()
3052 r = {}
3053 out = p.stdout
3054 if out:
3055 out = iter(out[:-1].split('\0'))
3056 while out:
3057 try:
3058 info = next(out)
3059 path = next(out)
3060 except StopIteration:
3061 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003062
Mike Frysinger84230002021-02-16 17:08:35 -05003063 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003064
Mike Frysinger84230002021-02-16 17:08:35 -05003065 def __init__(self, path, omode, nmode, oid, nid, state):
3066 self.path = path
3067 self.src_path = None
3068 self.old_mode = omode
3069 self.new_mode = nmode
3070 self.old_id = oid
3071 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003072
Mike Frysinger84230002021-02-16 17:08:35 -05003073 if len(state) == 1:
3074 self.status = state
3075 self.level = None
3076 else:
3077 self.status = state[:1]
3078 self.level = state[1:]
3079 while self.level.startswith('0'):
3080 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003081
Mike Frysinger84230002021-02-16 17:08:35 -05003082 info = info[1:].split(' ')
3083 info = _Info(path, *info)
3084 if info.status in ('R', 'C'):
3085 info.src_path = info.path
3086 info.path = next(out)
3087 r[info.path] = info
3088 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003089
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003090 def GetDotgitPath(self, subpath=None):
3091 """Return the full path to the .git dir.
3092
3093 As a convenience, append |subpath| if provided.
3094 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003095 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003096 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003097 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003098 dotgit = os.path.join(self._project.worktree, '.git')
3099 if os.path.isfile(dotgit):
3100 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3101 with open(dotgit) as fp:
3102 setting = fp.read()
3103 assert setting.startswith('gitdir:')
3104 gitdir = setting.split(':', 1)[1].strip()
3105 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3106
3107 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3108
3109 def GetHead(self):
3110 """Return the ref that HEAD points to."""
3111 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003112 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003113 with open(path) as fd:
3114 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003115 except IOError as e:
3116 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003117 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303118 line = line.decode()
3119 except AttributeError:
3120 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003121 if line.startswith('ref: '):
3122 return line[5:-1]
3123 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003124
3125 def SetHead(self, ref, message=None):
3126 cmdv = []
3127 if message is not None:
3128 cmdv.extend(['-m', message])
3129 cmdv.append(HEAD)
3130 cmdv.append(ref)
3131 self.symbolic_ref(*cmdv)
3132
3133 def DetachHead(self, new, message=None):
3134 cmdv = ['--no-deref']
3135 if message is not None:
3136 cmdv.extend(['-m', message])
3137 cmdv.append(HEAD)
3138 cmdv.append(new)
3139 self.update_ref(*cmdv)
3140
3141 def UpdateRef(self, name, new, old=None,
3142 message=None,
3143 detach=False):
3144 cmdv = []
3145 if message is not None:
3146 cmdv.extend(['-m', message])
3147 if detach:
3148 cmdv.append('--no-deref')
3149 cmdv.append(name)
3150 cmdv.append(new)
3151 if old is not None:
3152 cmdv.append(old)
3153 self.update_ref(*cmdv)
3154
3155 def DeleteRef(self, name, old=None):
3156 if not old:
3157 old = self.rev_parse(name)
3158 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003159 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003160
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003161 def rev_list(self, *args, **kw):
3162 if 'format' in kw:
3163 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3164 else:
3165 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003166 cmdv.extend(args)
3167 p = GitCommand(self._project,
3168 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003169 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003170 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003171 capture_stdout=True,
3172 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003173 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003174 raise GitError('%s rev-list %s: %s' %
3175 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003176 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003177
3178 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003179 """Allow arbitrary git commands using pythonic syntax.
3180
3181 This allows you to do things like:
3182 git_obj.rev_parse('HEAD')
3183
3184 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3185 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003186 Any other positional arguments will be passed to the git command, and the
3187 following keyword arguments are supported:
3188 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003189
3190 Args:
3191 name: The name of the git command to call. Any '_' characters will
3192 be replaced with '-'.
3193
3194 Returns:
3195 A callable object that will try to call git with the named command.
3196 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003197 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003198
Dave Borowitz091f8932012-10-23 17:01:04 -07003199 def runner(*args, **kwargs):
3200 cmdv = []
3201 config = kwargs.pop('config', None)
3202 for k in kwargs:
3203 raise TypeError('%s() got an unexpected keyword argument %r'
3204 % (name, k))
3205 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303206 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003207 cmdv.append('-c')
3208 cmdv.append('%s=%s' % (k, v))
3209 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003210 cmdv.extend(args)
3211 p = GitCommand(self._project,
3212 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003213 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003214 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003215 capture_stdout=True,
3216 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003217 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003218 raise GitError('%s %s: %s' %
3219 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003220 r = p.stdout
3221 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3222 return r[:-1]
3223 return r
3224 return runner
3225
3226
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003227class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003228
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003229 def __str__(self):
3230 return 'prior sync failed; rebase still in progress'
3231
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003232
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003233class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003234
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003235 def __str__(self):
3236 return 'contains uncommitted changes'
3237
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003238
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003239class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003240
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003241 def __init__(self, project, text):
3242 self.project = project
3243 self.text = text
3244
3245 def Print(self, syncbuf):
LaMont Jones8501d462022-06-22 19:21:15 +00003246 syncbuf.out.info('%s/: %s', self.project.RelPath(local=False), self.text)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003247 syncbuf.out.nl()
3248
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003249
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003250class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003251
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003252 def __init__(self, project, why):
3253 self.project = project
3254 self.why = why
3255
3256 def Print(self, syncbuf):
3257 syncbuf.out.fail('error: %s/: %s',
LaMont Jones8501d462022-06-22 19:21:15 +00003258 self.project.RelPath(local=False),
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003259 str(self.why))
3260 syncbuf.out.nl()
3261
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003262
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003263class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003264
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003265 def __init__(self, project, action):
3266 self.project = project
3267 self.action = action
3268
3269 def Run(self, syncbuf):
3270 out = syncbuf.out
LaMont Jones8501d462022-06-22 19:21:15 +00003271 out.project('project %s/', self.project.RelPath(local=False))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003272 out.nl()
3273 try:
3274 self.action()
3275 out.nl()
3276 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003277 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003278 out.nl()
3279 return False
3280
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003281
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003282class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003283
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003284 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003285 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003286 self.project = self.printer('header', attr='bold')
3287 self.info = self.printer('info')
3288 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003289
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003290
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003291class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003292
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003293 def __init__(self, config, detach_head=False):
3294 self._messages = []
3295 self._failures = []
3296 self._later_queue1 = []
3297 self._later_queue2 = []
3298
3299 self.out = _SyncColoring(config)
3300 self.out.redirect(sys.stderr)
3301
3302 self.detach_head = detach_head
3303 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003304 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003305
3306 def info(self, project, fmt, *args):
3307 self._messages.append(_InfoMessage(project, fmt % args))
3308
3309 def fail(self, project, err=None):
3310 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003311 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003312
3313 def later1(self, project, what):
3314 self._later_queue1.append(_Later(project, what))
3315
3316 def later2(self, project, what):
3317 self._later_queue2.append(_Later(project, what))
3318
3319 def Finish(self):
3320 self._PrintMessages()
3321 self._RunLater()
3322 self._PrintMessages()
3323 return self.clean
3324
David Rileye0684ad2017-04-05 00:02:59 -07003325 def Recently(self):
3326 recent_clean = self.recent_clean
3327 self.recent_clean = True
3328 return recent_clean
3329
3330 def _MarkUnclean(self):
3331 self.clean = False
3332 self.recent_clean = False
3333
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003334 def _RunLater(self):
3335 for q in ['_later_queue1', '_later_queue2']:
3336 if not self._RunQueue(q):
3337 return
3338
3339 def _RunQueue(self, queue):
3340 for m in getattr(self, queue):
3341 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003342 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003343 return False
3344 setattr(self, queue, [])
3345 return True
3346
3347 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003348 if self._messages or self._failures:
3349 if os.isatty(2):
3350 self.out.write(progress.CSI_ERASE_LINE)
3351 self.out.write('\r')
3352
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003353 for m in self._messages:
3354 m.Print(self)
3355 for m in self._failures:
3356 m.Print(self)
3357
3358 self._messages = []
3359 self._failures = []
3360
3361
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003362class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003363 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003364
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003365 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003366 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003367 manifest=manifest,
3368 name=name,
3369 gitdir=gitdir,
3370 objdir=gitdir,
3371 worktree=worktree,
3372 remote=RemoteSpec('origin'),
3373 relpath='.repo/%s' % name,
3374 revisionExpr='refs/heads/master',
3375 revisionId=None,
3376 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003377
3378 def PreSync(self):
3379 if self.Exists:
3380 cb = self.CurrentBranch
3381 if cb:
3382 base = self.GetBranch(cb).merge
3383 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003384 self.revisionExpr = base
3385 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003386
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003387 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003388 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003389 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003390 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003391 return False
3392
David Pursehouse8a68ff92012-09-24 12:15:13 +09003393 all_refs = self.bare_ref.all
3394 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003395 head = self.work_git.GetHead()
3396 if head.startswith(R_HEADS):
3397 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003398 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003399 except KeyError:
3400 head = None
3401
3402 if revid == head:
3403 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003404 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003405 return True
3406 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003407
3408
3409class RepoProject(MetaProject):
3410 """The MetaProject for repo itself."""
3411
3412 @property
3413 def LastFetch(self):
3414 try:
3415 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3416 return os.path.getmtime(fh)
3417 except OSError:
3418 return 0
3419
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +01003420
LaMont Jones9b72cf22022-03-29 21:54:22 +00003421class ManifestProject(MetaProject):
3422 """The MetaProject for manifests."""
3423
3424 def MetaBranchSwitch(self, submodules=False):
3425 """Prepare for manifest branch switch."""
3426
3427 # detach and delete manifest branch, allowing a new
3428 # branch to take over
3429 syncbuf = SyncBuffer(self.config, detach_head=True)
3430 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3431 syncbuf.Finish()
3432
3433 return GitCommand(self,
3434 ['update-ref', '-d', 'refs/heads/default'],
3435 capture_stdout=True,
3436 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003437
3438 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003439 def standalone_manifest_url(self):
3440 """The URL of the standalone manifest, or None."""
LaMont Jones55ee3042022-04-06 17:10:21 +00003441 return self.config.GetString('manifest.standalone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003442
3443 @property
3444 def manifest_groups(self):
3445 """The manifest groups string."""
3446 return self.config.GetString('manifest.groups')
3447
3448 @property
3449 def reference(self):
3450 """The --reference for this manifest."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003451 return self.config.GetString('repo.reference')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003452
3453 @property
3454 def dissociate(self):
3455 """Whether to dissociate."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003456 return self.config.GetBoolean('repo.dissociate')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003457
3458 @property
3459 def archive(self):
3460 """Whether we use archive."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003461 return self.config.GetBoolean('repo.archive')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003462
3463 @property
3464 def mirror(self):
3465 """Whether we use mirror."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003466 return self.config.GetBoolean('repo.mirror')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003467
3468 @property
3469 def use_worktree(self):
3470 """Whether we use worktree."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003471 return self.config.GetBoolean('repo.worktree')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003472
3473 @property
3474 def clone_bundle(self):
3475 """Whether we use clone_bundle."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003476 return self.config.GetBoolean('repo.clonebundle')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003477
3478 @property
3479 def submodules(self):
3480 """Whether we use submodules."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003481 return self.config.GetBoolean('repo.submodules')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003482
3483 @property
3484 def git_lfs(self):
3485 """Whether we use git_lfs."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003486 return self.config.GetBoolean('repo.git-lfs')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003487
3488 @property
3489 def use_superproject(self):
3490 """Whether we use superproject."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003491 return self.config.GetBoolean('repo.superproject')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003492
3493 @property
3494 def partial_clone(self):
3495 """Whether this is a partial clone."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003496 return self.config.GetBoolean('repo.partialclone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003497
3498 @property
3499 def depth(self):
3500 """Partial clone depth."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003501 return self.config.GetString('repo.depth')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003502
3503 @property
3504 def clone_filter(self):
3505 """The clone filter."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003506 return self.config.GetString('repo.clonefilter')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003507
3508 @property
3509 def partial_clone_exclude(self):
3510 """Partial clone exclude string"""
Joanna Wangea5239d2022-12-02 09:47:29 -05003511 return self.config.GetString('repo.partialcloneexclude')
LaMont Jones4ada0432022-04-14 15:10:43 +00003512
3513 @property
3514 def manifest_platform(self):
3515 """The --platform argument from `repo init`."""
3516 return self.config.GetString('manifest.platform')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003517
3518 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003519 def _platform_name(self):
3520 """Return the name of the platform."""
3521 return platform.system().lower()
3522
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003523 def SyncWithPossibleInit(self, submanifest, verbose=False,
3524 current_branch_only=False, tags='', git_event_log=None):
3525 """Sync a manifestProject, possibly for the first time.
3526
3527 Call Sync() with arguments from the most recent `repo init`. If this is a
3528 new sub manifest, then inherit options from the parent's manifestProject.
3529
3530 This is used by subcmds.Sync() to do an initial download of new sub
3531 manifests.
3532
3533 Args:
3534 submanifest: an XmlSubmanifest, the submanifest to re-sync.
3535 verbose: a boolean, whether to show all output, rather than only errors.
3536 current_branch_only: a boolean, whether to only fetch the current manifest
3537 branch from the server.
3538 tags: a boolean, whether to fetch tags.
3539 git_event_log: an EventLog, for git tracing.
3540 """
3541 # TODO(lamontjones): when refactoring sync (and init?) consider how to
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003542 # better get the init options that we should use for new submanifests that
3543 # are added when syncing an existing workspace.
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003544 git_event_log = git_event_log or EventLog()
3545 spec = submanifest.ToSubmanifestSpec()
3546 # Use the init options from the existing manifestProject, or the parent if
3547 # it doesn't exist.
3548 #
3549 # Today, we only support changing manifest_groups on the sub-manifest, with
3550 # no supported-for-the-user way to change the other arguments from those
3551 # specified by the outermost manifest.
3552 #
3553 # TODO(lamontjones): determine which of these should come from the outermost
3554 # manifest and which should come from the parent manifest.
3555 mp = self if self.Exists else submanifest.parent.manifestProject
3556 return self.Sync(
3557 manifest_url=spec.manifestUrl,
3558 manifest_branch=spec.revision,
3559 standalone_manifest=mp.standalone_manifest_url,
3560 groups=mp.manifest_groups,
3561 platform=mp.manifest_platform,
3562 mirror=mp.mirror,
3563 dissociate=mp.dissociate,
3564 reference=mp.reference,
3565 worktree=mp.use_worktree,
3566 submodules=mp.submodules,
3567 archive=mp.archive,
3568 partial_clone=mp.partial_clone,
3569 clone_filter=mp.clone_filter,
3570 partial_clone_exclude=mp.partial_clone_exclude,
3571 clone_bundle=mp.clone_bundle,
3572 git_lfs=mp.git_lfs,
3573 use_superproject=mp.use_superproject,
3574 verbose=verbose,
3575 current_branch_only=current_branch_only,
3576 tags=tags,
3577 depth=mp.depth,
3578 git_event_log=git_event_log,
3579 manifest_name=spec.manifestName,
3580 this_manifest_only=True,
3581 outer_manifest=False,
3582 )
3583
LaMont Jones9b03f152022-03-29 23:01:18 +00003584 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003585 standalone_manifest=False, groups='', mirror=False, reference='',
3586 dissociate=False, worktree=False, submodules=False, archive=False,
3587 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003588 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3589 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jones55ee3042022-04-06 17:10:21 +00003590 git_event_log=None, platform='', manifest_name='default.xml',
3591 tags='', this_manifest_only=False, outer_manifest=True):
LaMont Jones9b03f152022-03-29 23:01:18 +00003592 """Sync the manifest and all submanifests.
3593
3594 Args:
3595 manifest_url: a string, the URL of the manifest project.
3596 manifest_branch: a string, the manifest branch to use.
3597 standalone_manifest: a boolean, whether to store the manifest as a static
3598 file.
3599 groups: a string, restricts the checkout to projects with the specified
3600 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003601 mirror: a boolean, whether to create a mirror of the remote repository.
3602 reference: a string, location of a repo instance to use as a reference.
3603 dissociate: a boolean, whether to dissociate from reference mirrors after
3604 clone.
3605 worktree: a boolean, whether to use git-worktree to manage projects.
3606 submodules: a boolean, whether sync submodules associated with the
3607 manifest project.
3608 archive: a boolean, whether to checkout each project as an archive. See
3609 git-archive.
3610 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003611 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003612 clone_filter: a string, filter to use with partial_clone.
3613 partial_clone_exclude : a string, comma-delimeted list of project namess
3614 to exclude from partial clone.
3615 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3616 git_lfs: a boolean, whether to enable git LFS support.
3617 use_superproject: a boolean, whether to use the manifest superproject to
3618 sync projects.
3619 verbose: a boolean, whether to show all output, rather than only errors.
3620 current_branch_only: a boolean, whether to only fetch the current manifest
3621 branch from the server.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003622 platform: a string, restrict the checkout to projects with the specified
3623 platform group.
LaMont Jones55ee3042022-04-06 17:10:21 +00003624 git_event_log: an EventLog, for git tracing.
LaMont Jones4ada0432022-04-14 15:10:43 +00003625 tags: a boolean, whether to fetch tags.
LaMont Jones409407a2022-04-05 21:21:56 +00003626 manifest_name: a string, the name of the manifest file to use.
3627 this_manifest_only: a boolean, whether to only operate on the current sub
3628 manifest.
3629 outer_manifest: a boolean, whether to start at the outermost manifest.
LaMont Jones9b03f152022-03-29 23:01:18 +00003630
3631 Returns:
3632 a boolean, whether the sync was successful.
3633 """
3634 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3635
LaMont Jones501733c2022-04-20 16:42:32 +00003636 groups = groups or self.manifest.GetDefaultGroupsStr(with_platform=False)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003637 platform = platform or 'auto'
LaMont Jones55ee3042022-04-06 17:10:21 +00003638 git_event_log = git_event_log or EventLog()
LaMont Jones409407a2022-04-05 21:21:56 +00003639 if outer_manifest and self.manifest.is_submanifest:
3640 # In a multi-manifest checkout, use the outer manifest unless we are told
3641 # not to.
3642 return self.client.outer_manifest.manifestProject.Sync(
3643 manifest_url=manifest_url,
3644 manifest_branch=manifest_branch,
3645 standalone_manifest=standalone_manifest,
3646 groups=groups,
3647 platform=platform,
3648 mirror=mirror,
3649 dissociate=dissociate,
3650 reference=reference,
3651 worktree=worktree,
3652 submodules=submodules,
3653 archive=archive,
3654 partial_clone=partial_clone,
3655 clone_filter=clone_filter,
3656 partial_clone_exclude=partial_clone_exclude,
3657 clone_bundle=clone_bundle,
3658 git_lfs=git_lfs,
3659 use_superproject=use_superproject,
3660 verbose=verbose,
3661 current_branch_only=current_branch_only,
3662 tags=tags,
3663 depth=depth,
LaMont Jones55ee3042022-04-06 17:10:21 +00003664 git_event_log=git_event_log,
LaMont Jones409407a2022-04-05 21:21:56 +00003665 manifest_name=manifest_name,
3666 this_manifest_only=this_manifest_only,
3667 outer_manifest=False)
3668
LaMont Jones9b03f152022-03-29 23:01:18 +00003669 # If repo has already been initialized, we take -u with the absence of
3670 # --standalone-manifest to mean "transition to a standard repo set up",
3671 # which necessitates starting fresh.
3672 # If --standalone-manifest is set, we always tear everything down and start
3673 # anew.
3674 if self.Exists:
3675 was_standalone_manifest = self.config.GetString('manifest.standalone')
3676 if was_standalone_manifest and not manifest_url:
3677 print('fatal: repo was initialized with a standlone manifest, '
3678 'cannot be re-initialized without --manifest-url/-u')
3679 return False
3680
3681 if standalone_manifest or (was_standalone_manifest and manifest_url):
3682 self.config.ClearCache()
3683 if self.gitdir and os.path.exists(self.gitdir):
3684 platform_utils.rmtree(self.gitdir)
3685 if self.worktree and os.path.exists(self.worktree):
3686 platform_utils.rmtree(self.worktree)
3687
3688 is_new = not self.Exists
3689 if is_new:
3690 if not manifest_url:
3691 print('fatal: manifest url is required.', file=sys.stderr)
3692 return False
3693
LaMont Jones409407a2022-04-05 21:21:56 +00003694 if verbose:
LaMont Jones9b03f152022-03-29 23:01:18 +00003695 print('Downloading manifest from %s' %
3696 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3697 file=sys.stderr)
3698
3699 # The manifest project object doesn't keep track of the path on the
3700 # server where this git is located, so let's save that here.
3701 mirrored_manifest_git = None
3702 if reference:
3703 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3704 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3705 if not mirrored_manifest_git.endswith(".git"):
3706 mirrored_manifest_git += ".git"
3707 if not os.path.exists(mirrored_manifest_git):
3708 mirrored_manifest_git = os.path.join(reference,
3709 '.repo/manifests.git')
3710
3711 self._InitGitDir(mirror_git=mirrored_manifest_git)
3712
3713 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3714 # still do much of the manifests.git set up, but will avoid actual syncs to
3715 # a remote.
3716 if standalone_manifest:
3717 self.config.SetString('manifest.standalone', manifest_url)
3718 elif not manifest_url and not manifest_branch:
3719 # If -u is set and --standalone-manifest is not, then we're not in
3720 # standalone mode. Otherwise, use config to infer what we were in the last
3721 # init.
3722 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3723 if not standalone_manifest:
3724 self.config.SetString('manifest.standalone', None)
3725
3726 self._ConfigureDepth(depth)
3727
3728 # Set the remote URL before the remote branch as we might need it below.
3729 if manifest_url:
Mike Frysingerdede5642022-07-10 04:56:04 -04003730 r = self.GetRemote()
LaMont Jones9b03f152022-03-29 23:01:18 +00003731 r.url = manifest_url
3732 r.ResetFetch()
3733 r.Save()
3734
3735 if not standalone_manifest:
3736 if manifest_branch:
3737 if manifest_branch == 'HEAD':
3738 manifest_branch = self.ResolveRemoteHead()
3739 if manifest_branch is None:
3740 print('fatal: unable to resolve HEAD', file=sys.stderr)
3741 return False
3742 self.revisionExpr = manifest_branch
3743 else:
3744 if is_new:
3745 default_branch = self.ResolveRemoteHead()
3746 if default_branch is None:
3747 # If the remote doesn't have HEAD configured, default to master.
3748 default_branch = 'refs/heads/master'
3749 self.revisionExpr = default_branch
3750 else:
3751 self.PreSync()
3752
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003753 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003754 all_platforms = ['linux', 'darwin', 'windows']
3755 platformize = lambda x: 'platform-' + x
3756 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003757 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003758 groups.append(platformize(self._platform_name))
3759 elif platform == 'all':
3760 groups.extend(map(platformize, all_platforms))
3761 elif platform in all_platforms:
3762 groups.append(platformize(platform))
3763 elif platform != 'none':
3764 print('fatal: invalid platform flag', file=sys.stderr)
3765 return False
LaMont Jones4ada0432022-04-14 15:10:43 +00003766 self.config.SetString('manifest.platform', platform)
LaMont Jones9b03f152022-03-29 23:01:18 +00003767
3768 groups = [x for x in groups if x]
3769 groupstr = ','.join(groups)
3770 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3771 groupstr = None
3772 self.config.SetString('manifest.groups', groupstr)
3773
3774 if reference:
3775 self.config.SetString('repo.reference', reference)
3776
3777 if dissociate:
3778 self.config.SetBoolean('repo.dissociate', dissociate)
3779
3780 if worktree:
3781 if mirror:
3782 print('fatal: --mirror and --worktree are incompatible',
3783 file=sys.stderr)
3784 return False
3785 if submodules:
3786 print('fatal: --submodules and --worktree are incompatible',
3787 file=sys.stderr)
3788 return False
3789 self.config.SetBoolean('repo.worktree', worktree)
3790 if is_new:
3791 self.use_git_worktrees = True
3792 print('warning: --worktree is experimental!', file=sys.stderr)
3793
3794 if archive:
3795 if is_new:
3796 self.config.SetBoolean('repo.archive', archive)
3797 else:
3798 print('fatal: --archive is only supported when initializing a new '
3799 'workspace.', file=sys.stderr)
3800 print('Either delete the .repo folder in this workspace, or initialize '
3801 'in another location.', file=sys.stderr)
3802 return False
3803
3804 if mirror:
3805 if is_new:
3806 self.config.SetBoolean('repo.mirror', mirror)
3807 else:
3808 print('fatal: --mirror is only supported when initializing a new '
3809 'workspace.', file=sys.stderr)
3810 print('Either delete the .repo folder in this workspace, or initialize '
3811 'in another location.', file=sys.stderr)
3812 return False
3813
3814 if partial_clone is not None:
3815 if mirror:
3816 print('fatal: --mirror and --partial-clone are mutually exclusive',
3817 file=sys.stderr)
3818 return False
3819 self.config.SetBoolean('repo.partialclone', partial_clone)
3820 if clone_filter:
3821 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jones55ee3042022-04-06 17:10:21 +00003822 elif self.partial_clone:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003823 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003824 else:
3825 clone_filter = None
3826
3827 if partial_clone_exclude is not None:
3828 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3829
3830 if clone_bundle is None:
3831 clone_bundle = False if partial_clone else True
3832 else:
3833 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3834
3835 if submodules:
3836 self.config.SetBoolean('repo.submodules', submodules)
3837
3838 if git_lfs is not None:
3839 if git_lfs:
3840 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3841
3842 self.config.SetBoolean('repo.git-lfs', git_lfs)
3843 if not is_new:
3844 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3845 ' Existing projects will require manual updates.\n', file=sys.stderr)
3846
3847 if use_superproject is not None:
3848 self.config.SetBoolean('repo.superproject', use_superproject)
3849
LaMont Jones0165e202022-04-27 17:34:42 +00003850 if not standalone_manifest:
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +01003851 success = self.Sync_NetworkHalf(
LaMont Jones0165e202022-04-27 17:34:42 +00003852 is_new=is_new, quiet=not verbose, verbose=verbose,
3853 clone_bundle=clone_bundle, current_branch_only=current_branch_only,
3854 tags=tags, submodules=submodules, clone_filter=clone_filter,
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +01003855 partial_clone_exclude=self.manifest.PartialCloneExclude).success
3856 if not success:
Mike Frysingerdede5642022-07-10 04:56:04 -04003857 r = self.GetRemote()
LaMont Jones0165e202022-04-27 17:34:42 +00003858 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
LaMont Jonesaf8fb132022-08-31 20:21:25 +00003859
3860 # Better delete the manifest git dir if we created it; otherwise next
3861 # time (when user fixes problems) we won't go through the "is_new" logic.
3862 if is_new:
3863 platform_utils.rmtree(self.gitdir)
LaMont Jones9b03f152022-03-29 23:01:18 +00003864 return False
3865
LaMont Jones0165e202022-04-27 17:34:42 +00003866 if manifest_branch:
3867 self.MetaBranchSwitch(submodules=submodules)
3868
3869 syncbuf = SyncBuffer(self.config)
3870 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3871 syncbuf.Finish()
3872
3873 if is_new or self.CurrentBranch is None:
3874 if not self.StartBranch('default'):
3875 print('fatal: cannot create default in manifest', file=sys.stderr)
3876 return False
3877
3878 if not manifest_name:
3879 print('fatal: manifest name (-m) is required.', file=sys.stderr)
3880 return False
3881
3882 elif is_new:
3883 # This is a new standalone manifest.
3884 manifest_name = 'default.xml'
3885 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3886 dest = os.path.join(self.worktree, manifest_name)
3887 os.makedirs(os.path.dirname(dest), exist_ok=True)
3888 with open(dest, 'wb') as f:
3889 f.write(manifest_data)
LaMont Jones409407a2022-04-05 21:21:56 +00003890
3891 try:
3892 self.manifest.Link(manifest_name)
3893 except ManifestParseError as e:
3894 print("fatal: manifest '%s' not available" % manifest_name,
3895 file=sys.stderr)
3896 print('fatal: %s' % str(e), file=sys.stderr)
3897 return False
3898
LaMont Jones55ee3042022-04-06 17:10:21 +00003899 if not this_manifest_only:
3900 for submanifest in self.manifest.submanifests.values():
LaMont Jonesb90a4222022-04-14 15:00:09 +00003901 spec = submanifest.ToSubmanifestSpec()
LaMont Jones55ee3042022-04-06 17:10:21 +00003902 submanifest.repo_client.manifestProject.Sync(
3903 manifest_url=spec.manifestUrl,
3904 manifest_branch=spec.revision,
3905 standalone_manifest=standalone_manifest,
3906 groups=self.manifest_groups,
3907 platform=platform,
3908 mirror=mirror,
3909 dissociate=dissociate,
3910 reference=reference,
3911 worktree=worktree,
3912 submodules=submodules,
3913 archive=archive,
3914 partial_clone=partial_clone,
3915 clone_filter=clone_filter,
3916 partial_clone_exclude=partial_clone_exclude,
3917 clone_bundle=clone_bundle,
3918 git_lfs=git_lfs,
3919 use_superproject=use_superproject,
3920 verbose=verbose,
3921 current_branch_only=current_branch_only,
3922 tags=tags,
3923 depth=depth,
3924 git_event_log=git_event_log,
3925 manifest_name=spec.manifestName,
3926 this_manifest_only=False,
3927 outer_manifest=False,
3928 )
LaMont Jones409407a2022-04-05 21:21:56 +00003929
LaMont Jones0ddb6772022-05-20 09:11:54 +00003930 # Lastly, if the manifest has a <superproject> then have the superproject
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003931 # sync it (if it will be used).
3932 if git_superproject.UseSuperproject(use_superproject, self.manifest):
LaMont Jones0ddb6772022-05-20 09:11:54 +00003933 sync_result = self.manifest.superproject.Sync(git_event_log)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003934 if not sync_result.success:
3935 print('warning: git update of superproject for '
3936 f'{self.manifest.path_prefix} failed, repo sync will not use '
3937 'superproject to fetch source; while this error is not fatal, '
3938 'and you can continue to run repo sync, please run repo init '
3939 'with the --no-use-superproject option to stop seeing this '
3940 'warning', file=sys.stderr)
3941 if sync_result.fatal and use_superproject is not None:
3942 return False
LaMont Jones409407a2022-04-05 21:21:56 +00003943
LaMont Jones9b03f152022-03-29 23:01:18 +00003944 return True
3945
3946 def _ConfigureDepth(self, depth):
3947 """Configure the depth we'll sync down.
3948
3949 Args:
3950 depth: an int, how deep of a partial clone to create.
3951 """
3952 # Opt.depth will be non-None if user actually passed --depth to repo init.
3953 if depth is not None:
3954 if depth > 0:
3955 # Positive values will set the depth.
3956 depth = str(depth)
3957 else:
3958 # Negative numbers will clear the depth; passing None to SetString
3959 # will do that.
3960 depth = None
3961
3962 # We store the depth in the main manifest project.
3963 self.config.SetString('repo.depth', depth)