blob: f9524a094c3ad436cc09d3eaf071244513352102 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080015import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070016import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070017import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import os
LaMont Jonesd82be3e2022-04-05 19:30:46 +000019import platform
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070020import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import re
22import shutil
23import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020026import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080027import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070028import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040029import urllib.parse
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from color import Coloring
LaMont Jones0de4fc32022-04-21 17:18:35 +000032import fetch
Dave Borowitzb42b4742012-10-31 12:27:27 -070033from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070034from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
35 ID_RE
LaMont Jonesff6b1da2022-06-01 21:03:34 +000036import git_superproject
LaMont Jones55ee3042022-04-06 17:10:21 +000037from git_trace2_event_log import EventLog
Remy Bohmer16c13282020-09-10 10:38:04 +020038from error import GitError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040039from error import ManifestInvalidRevisionError, ManifestInvalidPathError
LaMont Jones409407a2022-04-05 21:21:56 +000040from error import NoManifestException, ManifestParseError
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070041import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040042import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040043from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070047
George Engelbrecht9bc283e2020-04-02 12:36:09 -060048# Maximum sleep time allowed during retries.
49MAXIMUM_RETRY_SLEEP_SEC = 3600.0
50# +-10% random jitter is added to each Fetches retry sleep duration.
51RETRY_JITTER_PERCENT = 0.1
52
Mike Frysinger1d00a7e2021-12-21 00:40:31 -050053# Whether to use alternates.
54# TODO(vapier): Remove knob once behavior is verified.
55_ALTERNATES = os.environ.get('REPO_USE_ALTERNATES') == '1'
George Engelbrecht9bc283e2020-04-02 12:36:09 -060056
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070057def _lwrite(path, content):
58 lock = '%s.lock' % path
59
Remy Bohmer169b0212020-11-21 10:57:52 +010060 # Maintain Unix line endings on all OS's to match git behavior.
61 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070063
64 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070065 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070066 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080067 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068 raise
69
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070070
Shawn O. Pearce48244782009-04-16 08:25:57 -070071def _error(fmt, *args):
72 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070073 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070074
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070075
David Pursehousef33929d2015-08-24 14:39:14 +090076def _warn(fmt, *args):
77 msg = fmt % args
78 print('warn: %s' % msg, file=sys.stderr)
79
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081def not_rev(r):
82 return '^' + r
83
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080085def sq(r):
86 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087
David Pursehouse819827a2020-02-12 15:20:19 +090088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700106 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700107 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700108 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400138 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139
140 def __init__(self, project, branch, base):
141 self.project = project
142 self.branch = branch
143 self.base = base
144
145 @property
146 def name(self):
147 return self.branch.name
148
149 @property
150 def commits(self):
151 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400152 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
153 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
154 try:
155 self._commit_cache = self.project.bare_git.rev_list(*args)
156 except GitError:
157 # We weren't able to probe the commits for this branch. Was it tracking
158 # a branch that no longer exists? If so, return no commits. Otherwise,
159 # rethrow the error as we don't know what's going on.
160 if self.base_exists:
161 raise
162
163 self._commit_cache = []
164
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 return self._commit_cache
166
167 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800168 def unabbrev_commits(self):
169 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700170 for commit in self.project.bare_git.rev_list(not_rev(self.base),
171 R_HEADS + self.name,
172 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800173 r[commit[0:8]] = commit
174 return r
175
176 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700178 return self.project.bare_git.log('--pretty=format:%cd',
179 '-n', '1',
180 R_HEADS + self.name,
181 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182
Mike Frysinger6da17752019-09-11 18:43:17 -0400183 @property
184 def base_exists(self):
185 """Whether the branch we're tracking exists.
186
187 Normally it should, but sometimes branches we track can get deleted.
188 """
189 if self._base_exists is None:
190 try:
191 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
192 # If we're still here, the base branch exists.
193 self._base_exists = True
194 except GitError:
195 # If we failed to verify, the base branch doesn't exist.
196 self._base_exists = False
197
198 return self._base_exists
199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700200 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500201 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500203 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500204 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200205 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700206 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200207 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200208 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800209 validate_certs=True,
210 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500211 self.project.UploadForReview(branch=self.name,
212 people=people,
213 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700214 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500215 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500216 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200217 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700218 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200219 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200220 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800221 validate_certs=validate_certs,
222 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700224 def GetPublishedRefs(self):
225 refs = {}
226 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700227 self.branch.remote.SshReviewUrl(self.project.UserEmail),
228 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700229 for line in output.split('\n'):
230 try:
231 (sha, ref) = line.split()
232 refs[sha] = ref
233 except ValueError:
234 pass
235
236 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500242 super().__init__(config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100243 self.project = self.printer('header', attr='bold')
244 self.branch = self.printer('header', attr='bold')
245 self.nobranch = self.printer('nobranch', fg='red')
246 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247
Anthony King7bdac712014-07-16 12:56:40 +0100248 self.added = self.printer('added', fg='green')
249 self.changed = self.printer('changed', fg='red')
250 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251
252
253class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700254
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500256 super().__init__(config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100257 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400258 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700260
Jack Neus6ea0cae2021-07-20 20:52:33 +0000261class Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
James W. Mills24c13082012-04-12 15:04:13 -0500263 def __init__(self, name, value, keep):
264 self.name = name
265 self.value = value
266 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700267
Jack Neus6ea0cae2021-07-20 20:52:33 +0000268 def __eq__(self, other):
269 if not isinstance(other, Annotation):
270 return False
271 return self.__dict__ == other.__dict__
272
273 def __lt__(self, other):
274 # This exists just so that lists of Annotation objects can be sorted, for
275 # use in comparisons.
276 if not isinstance(other, Annotation):
277 raise ValueError('comparison is not between two Annotation objects')
278 if self.name == other.name:
279 if self.value == other.value:
280 return self.keep < other.keep
281 return self.value < other.value
282 return self.name < other.name
283
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700284
Mike Frysingere6a202f2019-08-02 15:57:57 -0400285def _SafeExpandPath(base, subpath, skipfinal=False):
286 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700287
Mike Frysingere6a202f2019-08-02 15:57:57 -0400288 We make sure no intermediate symlinks are traversed, and that the final path
289 is not a special file (e.g. not a socket or fifo).
290
291 NB: We rely on a number of paths already being filtered out while parsing the
292 manifest. See the validation logic in manifest_xml.py for more details.
293 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500294 # Split up the path by its components. We can't use os.path.sep exclusively
295 # as some platforms (like Windows) will convert / to \ and that bypasses all
296 # our constructed logic here. Especially since manifest authors only use
297 # / in their paths.
298 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
299 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400300 if skipfinal:
301 # Whether the caller handles the final component itself.
302 finalpart = components.pop()
303
304 path = base
305 for part in components:
306 if part in {'.', '..'}:
307 raise ManifestInvalidPathError(
308 '%s: "%s" not allowed in paths' % (subpath, part))
309
310 path = os.path.join(path, part)
311 if platform_utils.islink(path):
312 raise ManifestInvalidPathError(
313 '%s: traversing symlinks not allow' % (path,))
314
315 if os.path.exists(path):
316 if not os.path.isfile(path) and not platform_utils.isdir(path):
317 raise ManifestInvalidPathError(
318 '%s: only regular files & directories allowed' % (path,))
319
320 if skipfinal:
321 path = os.path.join(path, finalpart)
322
323 return path
324
325
326class _CopyFile(object):
327 """Container for <copyfile> manifest element."""
328
329 def __init__(self, git_worktree, src, topdir, dest):
330 """Register a <copyfile> request.
331
332 Args:
333 git_worktree: Absolute path to the git project checkout.
334 src: Relative path under |git_worktree| of file to read.
335 topdir: Absolute path to the top of the repo client checkout.
336 dest: Relative path under |topdir| of file to write.
337 """
338 self.git_worktree = git_worktree
339 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700340 self.src = src
341 self.dest = dest
342
343 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400344 src = _SafeExpandPath(self.git_worktree, self.src)
345 dest = _SafeExpandPath(self.topdir, self.dest)
346
347 if platform_utils.isdir(src):
348 raise ManifestInvalidPathError(
349 '%s: copying from directory not supported' % (self.src,))
350 if platform_utils.isdir(dest):
351 raise ManifestInvalidPathError(
352 '%s: copying to directory not allowed' % (self.dest,))
353
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700354 # copy file if it does not exist or is out of date
355 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
356 try:
357 # remove existing file first, since it might be read-only
358 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800359 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400360 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200361 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700362 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200363 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700364 shutil.copy(src, dest)
365 # make the file read-only
366 mode = os.stat(dest)[stat.ST_MODE]
367 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
368 os.chmod(dest, mode)
369 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700370 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700371
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700372
Anthony King7bdac712014-07-16 12:56:40 +0100373class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400374 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700375
Mike Frysingere6a202f2019-08-02 15:57:57 -0400376 def __init__(self, git_worktree, src, topdir, dest):
377 """Register a <linkfile> request.
378
379 Args:
380 git_worktree: Absolute path to the git project checkout.
381 src: Target of symlink relative to path under |git_worktree|.
382 topdir: Absolute path to the top of the repo client checkout.
383 dest: Relative path under |topdir| of symlink to create.
384 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700385 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400386 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500387 self.src = src
388 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500389
Wink Saville4c426ef2015-06-03 08:05:17 -0700390 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500391 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700392 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500393 try:
394 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800395 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800396 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500397 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700398 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700399 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500400 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700401 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500402 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700403 _error('Cannot link file %s to %s', relSrc, absDest)
404
405 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400406 """Link the self.src & self.dest paths.
407
408 Handles wild cards on the src linking all of the files in the source in to
409 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700410 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500411 # Some people use src="." to create stable links to projects. Lets allow
412 # that but reject all other uses of "." to keep things simple.
413 if self.src == '.':
414 src = self.git_worktree
415 else:
416 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400417
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300418 if not glob.has_magic(src):
419 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400420 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
421 # dest & src are absolute paths at this point. Make sure the target of
422 # the symlink is relative in the context of the repo client checkout.
423 relpath = os.path.relpath(src, os.path.dirname(dest))
424 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700425 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400426 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300427 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400428 if os.path.exists(dest) and not platform_utils.isdir(dest):
429 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700430 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400431 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700432 # Create a releative path from source dir to destination dir
433 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400434 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700435
436 # Get the source file name
437 srcFile = os.path.basename(absSrcFile)
438
439 # Now form the final full paths to srcFile. They will be
440 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400441 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700442 relSrc = os.path.join(relSrcDir, srcFile)
443 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500444
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700445
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700446class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700447
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700448 def __init__(self,
449 name,
Anthony King7bdac712014-07-16 12:56:40 +0100450 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700451 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100452 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700453 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700454 orig_name=None,
455 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700456 self.name = name
457 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700458 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700459 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100460 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700461 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700462 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700463
Ian Kasprzak0286e312021-02-05 10:06:18 -0800464
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700465class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600466 # These objects can be shared between several working trees.
LaMont Jones68d69632022-06-07 18:24:20 +0000467 @property
468 def shareable_dirs(self):
469 """Return the shareable directories"""
470 if self.UseAlternates:
471 return ['hooks', 'rr-cache']
472 else:
473 return ['hooks', 'objects', 'rr-cache']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700474
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700475 def __init__(self,
476 manifest,
477 name,
478 remote,
479 gitdir,
David James8d201162013-10-11 17:03:19 -0700480 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700481 worktree,
482 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700483 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800484 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100485 rebase=True,
486 groups=None,
487 sync_c=False,
488 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900489 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100490 clone_depth=None,
491 upstream=None,
492 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500493 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100494 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900495 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700496 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600497 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700498 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800499 """Init a Project object.
500
501 Args:
502 manifest: The XmlManifest object.
503 name: The `name` attribute of manifest.xml's project element.
504 remote: RemoteSpec object specifying its remote's properties.
505 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700506 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800507 worktree: Absolute path of git working tree.
508 relpath: Relative path of git working tree to repo's top directory.
509 revisionExpr: The `revision` attribute of manifest.xml's project element.
510 revisionId: git commit id for checking out.
511 rebase: The `rebase` attribute of manifest.xml's project element.
512 groups: The `groups` attribute of manifest.xml's project element.
513 sync_c: The `sync-c` attribute of manifest.xml's project element.
514 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900515 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800516 upstream: The `upstream` attribute of manifest.xml's project element.
517 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500518 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800519 is_derived: False if the project was explicitly defined in the manifest;
520 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400521 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900522 optimized_fetch: If True, when a project is set to a sha1 revision, only
523 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600524 retry_fetches: Retry remote fetches n times upon receiving transient error
525 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700526 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800527 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400528 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700529 self.name = name
530 self.remote = remote
Michael Kelly37c21c22020-06-13 02:10:40 -0700531 self.UpdatePaths(relpath, worktree, gitdir, objdir)
Michael Kelly2f3c3312020-07-21 19:40:38 -0700532 self.SetRevision(revisionExpr, revisionId=revisionId)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700533
Mike Pontillod3153822012-02-28 11:53:24 -0800534 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700535 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700536 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800537 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900538 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900539 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700540 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800541 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500542 # NB: Do not use this setting in __init__ to change behavior so that the
543 # manifest.git checkout can inspect & change it after instantiating. See
544 # the XmlManifest init code for more info.
545 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800546 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900547 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600548 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800549 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800550
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700551 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500553 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500554 self.annotations = []
Bryan Jacobsf609f912013-05-06 13:36:24 -0400555 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700556 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700557
Doug Anderson37282b42011-03-04 11:54:18 -0800558 # This will be filled in if a project is later identified to be the
559 # project containing repo hooks.
560 self.enabled_repo_hooks = []
561
LaMont Jonescc879a92021-11-18 22:40:18 +0000562 def RelPath(self, local=True):
563 """Return the path for the project relative to a manifest.
564
565 Args:
566 local: a boolean, if True, the path is relative to the local
567 (sub)manifest. If false, the path is relative to the
568 outermost manifest.
569 """
570 if local:
571 return self.relpath
572 return os.path.join(self.manifest.path_prefix, self.relpath)
573
Michael Kelly2f3c3312020-07-21 19:40:38 -0700574 def SetRevision(self, revisionExpr, revisionId=None):
575 """Set revisionId based on revision expression and id"""
576 self.revisionExpr = revisionExpr
577 if revisionId is None and revisionExpr and IsId(revisionExpr):
578 self.revisionId = self.revisionExpr
579 else:
580 self.revisionId = revisionId
581
Michael Kelly37c21c22020-06-13 02:10:40 -0700582 def UpdatePaths(self, relpath, worktree, gitdir, objdir):
583 """Update paths used by this project"""
584 self.gitdir = gitdir.replace('\\', '/')
585 self.objdir = objdir.replace('\\', '/')
586 if worktree:
587 self.worktree = os.path.normpath(worktree).replace('\\', '/')
588 else:
589 self.worktree = None
590 self.relpath = relpath
591
592 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
593 defaults=self.manifest.globalConfig)
594
595 if self.worktree:
596 self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
597 else:
598 self.work_git = None
599 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
600 self.bare_ref = GitRefs(self.gitdir)
601 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
602
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700603 @property
LaMont Jones68d69632022-06-07 18:24:20 +0000604 def UseAlternates(self):
605 """Whether git alternates are in use.
606
607 This will be removed once migration to alternates is complete.
608 """
609 return _ALTERNATES or self.manifest.is_multimanifest
610
611 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800612 def Derived(self):
613 return self.is_derived
614
615 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700616 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700617 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700618
619 @property
620 def CurrentBranch(self):
621 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400622
623 The branch name omits the 'refs/heads/' prefix.
624 None is returned if the project is on a detached HEAD, or if the work_git is
625 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700626 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400627 try:
628 b = self.work_git.GetHead()
629 except NoManifestException:
630 # If the local checkout is in a bad state, don't barf. Let the callers
631 # process this like the head is unreadable.
632 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700633 if b.startswith(R_HEADS):
634 return b[len(R_HEADS):]
635 return None
636
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700637 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500638 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
639 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
640 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200641
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700642 def IsDirty(self, consider_untracked=True):
643 """Is the working directory modified in some way?
644 """
645 self.work_git.update_index('-q',
646 '--unmerged',
647 '--ignore-missing',
648 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900649 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700650 return True
651 if self.work_git.DiffZ('diff-files'):
652 return True
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200653 if consider_untracked and self.UntrackedFiles():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654 return True
655 return False
656
657 _userident_name = None
658 _userident_email = None
659
660 @property
661 def UserName(self):
662 """Obtain the user's personal name.
663 """
664 if self._userident_name is None:
665 self._LoadUserIdentity()
666 return self._userident_name
667
668 @property
669 def UserEmail(self):
670 """Obtain the user's email address. This is very likely
671 to be their Gerrit login.
672 """
673 if self._userident_email is None:
674 self._LoadUserIdentity()
675 return self._userident_email
676
677 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900678 u = self.bare_git.var('GIT_COMMITTER_IDENT')
679 m = re.compile("^(.*) <([^>]*)> ").match(u)
680 if m:
681 self._userident_name = m.group(1)
682 self._userident_email = m.group(2)
683 else:
684 self._userident_name = ''
685 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700686
687 def GetRemote(self, name):
688 """Get the configuration for a single remote.
689 """
690 return self.config.GetRemote(name)
691
692 def GetBranch(self, name):
693 """Get the configuration for a single branch.
694 """
695 return self.config.GetBranch(name)
696
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700697 def GetBranches(self):
698 """Get all existing local branches.
699 """
700 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900701 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700702 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700703
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530704 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700705 if name.startswith(R_HEADS):
706 name = name[len(R_HEADS):]
707 b = self.GetBranch(name)
708 b.current = name == current
709 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900710 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700711 heads[name] = b
712
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530713 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700714 if name.startswith(R_PUB):
715 name = name[len(R_PUB):]
716 b = heads.get(name)
717 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900718 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700719
720 return heads
721
Colin Cross5acde752012-03-28 20:15:45 -0700722 def MatchesGroups(self, manifest_groups):
723 """Returns true if the manifest groups specified at init should cause
724 this project to be synced.
725 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700726 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700727
728 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700729 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700730 manifest_groups: "-group1,group2"
731 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500732
733 The special manifest group "default" will match any project that
734 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700735 """
LaMont Jones501733c2022-04-20 16:42:32 +0000736 default_groups = self.manifest.default_groups or ['default']
737 expanded_manifest_groups = manifest_groups or default_groups
Conley Owensbb1b5f52012-08-13 13:11:18 -0700738 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700739 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500740 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700741
Conley Owens971de8e2012-04-16 10:36:08 -0700742 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700743 for group in expanded_manifest_groups:
744 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700745 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700746 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700747 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700748
Conley Owens971de8e2012-04-16 10:36:08 -0700749 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700750
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700751# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700752 def UncommitedFiles(self, get_all=True):
753 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700754
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700755 Args:
756 get_all: a boolean, if True - get information about all different
757 uncommitted files. If False - return as soon as any kind of
758 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500759 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700760 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500761 self.work_git.update_index('-q',
762 '--unmerged',
763 '--ignore-missing',
764 '--refresh')
765 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700766 details.append("rebase in progress")
767 if not get_all:
768 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500769
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700770 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
771 if changes:
772 details.extend(changes)
773 if not get_all:
774 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500775
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700776 changes = self.work_git.DiffZ('diff-files').keys()
777 if changes:
778 details.extend(changes)
779 if not get_all:
780 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500781
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200782 changes = self.UntrackedFiles()
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700783 if changes:
784 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500785
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700786 return details
787
Martin Geisler9fb64ae2022-07-08 10:50:10 +0200788 def UntrackedFiles(self):
789 """Returns a list of strings, untracked files in the git tree."""
790 return self.work_git.LsOthers()
791
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700792 def HasChanges(self):
793 """Returns true if there are uncommitted changes.
794 """
Martin Geisler8db78c72022-07-08 11:05:24 +0200795 return bool(self.UncommitedFiles(get_all=False))
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500796
LaMont Jones8501d462022-06-22 19:21:15 +0000797 def PrintWorkTreeStatus(self, output_redir=None, quiet=False, local=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700798 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200799
800 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200801 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600802 quiet: If True then only print the project name. Do not print
803 the modified files, branch name, etc.
LaMont Jones8501d462022-06-22 19:21:15 +0000804 local: a boolean, if True, the path is relative to the local
805 (sub)manifest. If false, the path is relative to the
806 outermost manifest.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700808 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700809 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200810 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700811 print(file=output_redir)
LaMont Jones8501d462022-06-22 19:21:15 +0000812 print('project %s/' % self.RelPath(local), file=output_redir)
Sarah Owenscecd1d82012-11-01 22:59:27 -0700813 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814 return
815
816 self.work_git.update_index('-q',
817 '--unmerged',
818 '--ignore-missing',
819 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700820 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700821 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
822 df = self.work_git.DiffZ('diff-files')
823 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100824 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700825 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700826
827 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700828 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200829 out.redirect(output_redir)
LaMont Jones8501d462022-06-22 19:21:15 +0000830 out.project('project %-40s', self.RelPath(local) + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700831
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600832 if quiet:
833 out.nl()
834 return 'DIRTY'
835
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 branch = self.CurrentBranch
837 if branch is None:
838 out.nobranch('(*** NO BRANCH ***)')
839 else:
840 out.branch('branch %s', branch)
841 out.nl()
842
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700843 if rb:
844 out.important('prior sync failed; rebase still in progress')
845 out.nl()
846
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700847 paths = list()
848 paths.extend(di.keys())
849 paths.extend(df.keys())
850 paths.extend(do)
851
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530852 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900853 try:
854 i = di[p]
855 except KeyError:
856 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900858 try:
859 f = df[p]
860 except KeyError:
861 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200862
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900863 if i:
864 i_status = i.status.upper()
865 else:
866 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900868 if f:
869 f_status = f.status.lower()
870 else:
871 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872
873 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800874 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700875 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700876 else:
877 line = ' %s%s\t%s' % (i_status, f_status, p)
878
879 if i and not f:
880 out.added('%s', line)
881 elif (i and f) or (not i and f):
882 out.changed('%s', line)
883 elif not i and not f:
884 out.untracked('%s', line)
885 else:
886 out.write('%s', line)
887 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200888
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700889 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890
LaMont Jones8501d462022-06-22 19:21:15 +0000891 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None,
892 local=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893 """Prints the status of the repository to stdout.
894 """
895 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500896 if output_redir:
897 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898 cmd = ['diff']
899 if out.is_on:
900 cmd.append('--color')
901 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300902 if absolute_paths:
LaMont Jones8501d462022-06-22 19:21:15 +0000903 cmd.append('--src-prefix=a/%s/' % self.RelPath(local))
904 cmd.append('--dst-prefix=b/%s/' % self.RelPath(local))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400906 try:
907 p = GitCommand(self,
908 cmd,
909 capture_stdout=True,
910 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500911 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400912 except GitError as e:
913 out.nl()
LaMont Jones8501d462022-06-22 19:21:15 +0000914 out.project('project %s/' % self.RelPath(local))
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400915 out.nl()
916 out.fail('%s', str(e))
917 out.nl()
918 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500919 if p.stdout:
920 out.nl()
LaMont Jones8501d462022-06-22 19:21:15 +0000921 out.project('project %s/' % self.RelPath(local))
Mike Frysinger84230002021-02-16 17:08:35 -0500922 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500923 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400924 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700926# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900927 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928 """Was the branch published (uploaded) for code review?
929 If so, returns the SHA-1 hash of the last published
930 state for the branch.
931 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700932 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900933 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700934 try:
935 return self.bare_git.rev_parse(key)
936 except GitError:
937 return None
938 else:
939 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900940 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700941 except KeyError:
942 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943
David Pursehouse8a68ff92012-09-24 12:15:13 +0900944 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945 """Prunes any stale published refs.
946 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900947 if all_refs is None:
948 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949 heads = set()
950 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530951 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 if name.startswith(R_HEADS):
953 heads.add(name)
954 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900955 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700956
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530957 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958 n = name[len(R_PUB):]
959 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900960 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700961
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700962 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963 """List any branches which can be uploaded for review.
964 """
965 heads = {}
966 pubed = {}
967
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530968 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900970 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700971 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900972 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973
974 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530975 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900976 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700977 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700978 if selected_branch and branch != selected_branch:
979 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800981 rb = self.GetUploadableBranch(branch)
982 if rb:
983 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984 return ready
985
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800986 def GetUploadableBranch(self, branch_name):
987 """Get a single uploadable branch, or None.
988 """
989 branch = self.GetBranch(branch_name)
990 base = branch.LocalMerge
991 if branch.LocalMerge:
992 rb = ReviewableBranch(self, branch, base)
993 if rb.commits:
994 return rb
995 return None
996
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700997 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100998 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500999 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001000 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001001 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001002 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +02001003 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001004 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001005 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001006 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001007 validate_certs=True,
1008 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001009 """Uploads the named branch for code review.
1010 """
1011 if branch is None:
1012 branch = self.CurrentBranch
1013 if branch is None:
1014 raise GitError('not currently on a branch')
1015
1016 branch = self.GetBranch(branch)
1017 if not branch.LocalMerge:
1018 raise GitError('branch %s does not track a remote' % branch.name)
1019 if not branch.remote.review:
1020 raise GitError('remote %s has no review url' % branch.remote.name)
1021
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001022 # Basic validity check on label syntax.
1023 for label in labels:
1024 if not re.match(r'^.+[+-][0-9]+$', label):
1025 raise UploadError(
1026 f'invalid label syntax "{label}": labels use forms like '
1027 'CodeReview+1 or Verified-1')
1028
Bryan Jacobsf609f912013-05-06 13:36:24 -04001029 if dest_branch is None:
1030 dest_branch = self.dest_branch
1031 if dest_branch is None:
1032 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001033 if not dest_branch.startswith(R_HEADS):
1034 dest_branch = R_HEADS + dest_branch
1035
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001036 if not branch.remote.projectname:
1037 branch.remote.projectname = self.name
1038 branch.remote.Save()
1039
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001040 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001041 if url is None:
1042 raise UploadError('review not configured')
1043 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001044 if dryrun:
1045 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001046
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001047 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001048 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001049
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001050 for push_option in (push_options or []):
1051 cmd.append('-o')
1052 cmd.append(push_option)
1053
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001054 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001055
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001056 if dest_branch.startswith(R_HEADS):
1057 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001058
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001059 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001060 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001061 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001062 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001063 opts += ['t=%s' % p for p in hashtags]
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001064 # NB: No need to encode labels as they've been validated above.
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001065 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001066
David Pursehousef25a3702018-11-14 19:01:22 -08001067 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001068 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001069 if notify:
1070 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001071 if private:
1072 opts += ['private']
1073 if wip:
1074 opts += ['wip']
1075 if opts:
1076 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001077 cmd.append(ref_spec)
1078
Anthony King7bdac712014-07-16 12:56:40 +01001079 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001080 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081
Mike Frysingerd7f86832020-11-19 19:18:46 -05001082 if not dryrun:
1083 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1084 self.bare_git.UpdateRef(R_PUB + branch.name,
1085 R_HEADS + branch.name,
1086 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001088# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001089 def _ExtractArchive(self, tarpath, path=None):
1090 """Extract the given tar on its current location
1091
1092 Args:
1093 - tarpath: The path to the actual tar file
1094
1095 """
1096 try:
1097 with tarfile.open(tarpath, 'r') as tar:
1098 tar.extractall(path=path)
1099 return True
1100 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001101 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001102 return False
1103
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001104 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001105 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001106 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001107 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001108 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001109 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001110 force_sync=False,
1111 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001112 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001113 archive=False,
1114 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001115 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001116 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001117 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001118 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001119 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001120 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121 """Perform only the network IO portion of the sync process.
1122 Local working directory/branch state is not affected.
1123 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001124 if archive and not isinstance(self, MetaProject):
1125 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001126 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001127 return False
1128
1129 name = self.relpath.replace('\\', '/')
1130 name = name.replace('/', '_')
1131 tarpath = '%s.tar' % name
1132 topdir = self.manifest.topdir
1133
1134 try:
1135 self._FetchArchive(tarpath, cwd=topdir)
1136 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001137 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001138 return False
1139
1140 # From now on, we only need absolute tarpath
1141 tarpath = os.path.join(topdir, tarpath)
1142
1143 if not self._ExtractArchive(tarpath, path=topdir):
1144 return False
1145 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001146 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001147 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001148 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001149 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001150 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001151
1152 # If the shared object dir already exists, don't try to rebootstrap with a
1153 # clone bundle download. We should have the majority of objects already.
1154 if clone_bundle and os.path.exists(self.objdir):
1155 clone_bundle = False
1156
Raman Tennetif32f2432021-04-12 20:57:25 -07001157 if self.name in partial_clone_exclude:
1158 clone_bundle = True
1159 clone_filter = None
1160
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001161 if is_new is None:
1162 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001163 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001164 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001165 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001166 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001168
LaMont Jones68d69632022-06-07 18:24:20 +00001169 if self.UseAlternates:
Mike Frysinger1d00a7e2021-12-21 00:40:31 -05001170 # If gitdir/objects is a symlink, migrate it from the old layout.
1171 gitdir_objects = os.path.join(self.gitdir, 'objects')
1172 if platform_utils.islink(gitdir_objects):
1173 platform_utils.remove(gitdir_objects, missing_ok=True)
1174 gitdir_alt = os.path.join(self.gitdir, 'objects/info/alternates')
1175 if not os.path.exists(gitdir_alt):
1176 os.makedirs(os.path.dirname(gitdir_alt), exist_ok=True)
1177 _lwrite(gitdir_alt, os.path.join(
1178 os.path.relpath(self.objdir, gitdir_objects), 'objects') + '\n')
1179
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001180 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001181 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001182 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001183 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001184 # This works for both absolute and relative alternate directories.
1185 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001186 except IOError:
1187 alt_dir = None
1188 else:
1189 alt_dir = None
1190
Mike Frysingere50b6a72020-02-19 01:45:48 -05001191 if (clone_bundle
1192 and alt_dir is None
1193 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001194 is_new = False
1195
Mike Frysinger73561142021-05-03 01:10:09 -04001196 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001197 if self.sync_c:
1198 current_branch_only = True
1199 elif not self.manifest._loaded:
1200 # Manifest cannot check defaults until it syncs.
1201 current_branch_only = False
1202 elif self.manifest.default.sync_c:
1203 current_branch_only = True
1204
Mike Frysingerd68ed632021-05-03 01:21:35 -04001205 if tags is None:
1206 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001207
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001208 if self.clone_depth:
1209 depth = self.clone_depth
1210 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001211 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001212
Mike Frysinger521d01b2020-02-17 01:51:49 -05001213 # See if we can skip the network fetch entirely.
1214 if not (optimized_fetch and
1215 (ID_RE.match(self.revisionExpr) and
1216 self._CheckForImmutableRevision())):
1217 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001218 initial=is_new,
1219 quiet=quiet, verbose=verbose, output_redir=output_redir,
1220 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001221 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001222 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001223 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001224 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001225 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001226
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001227 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001228 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001229 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001230 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001231 if os.path.exists(alternates_file):
1232 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001233 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1234 merge_output=bool(output_redir))
1235 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001236 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001237 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001238 return False
1239 platform_utils.remove(alternates_file)
1240
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001241 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001242 self._InitMRef()
1243 else:
1244 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001245 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1246 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001248
1249 def PostRepoUpgrade(self):
1250 self._InitHooks()
1251
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001252 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001253 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001254 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001255 for copyfile in self.copyfiles:
1256 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001257 for linkfile in self.linkfiles:
1258 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001259
Julien Camperguedd654222014-01-09 16:21:37 +01001260 def GetCommitRevisionId(self):
1261 """Get revisionId of a commit.
1262
1263 Use this method instead of GetRevisionId to get the id of the commit rather
1264 than the id of the current git object (for example, a tag)
1265
1266 """
1267 if not self.revisionExpr.startswith(R_TAGS):
1268 return self.GetRevisionId(self._allrefs)
1269
1270 try:
1271 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1272 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001273 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1274 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001275
David Pursehouse8a68ff92012-09-24 12:15:13 +09001276 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001277 if self.revisionId:
1278 return self.revisionId
1279
1280 rem = self.GetRemote(self.remote.name)
1281 rev = rem.ToLocal(self.revisionExpr)
1282
David Pursehouse8a68ff92012-09-24 12:15:13 +09001283 if all_refs is not None and rev in all_refs:
1284 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001285
1286 try:
1287 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1288 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001289 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1290 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001291
Raman Tenneti6a872c92021-01-14 19:17:50 -08001292 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001293 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001294 self.upstream = self.revisionExpr
1295
Raman Tenneti6a872c92021-01-14 19:17:50 -08001296 self.revisionId = revisionId
1297
Martin Kellye4e94d22017-03-21 16:05:12 -07001298 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299 """Perform only the local IO portion of the sync process.
1300 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001302 if not os.path.exists(self.gitdir):
1303 syncbuf.fail(self,
1304 'Cannot checkout %s due to missing network sync; Run '
1305 '`repo sync -n %s` first.' %
1306 (self.name, self.name))
1307 return
1308
Martin Kellye4e94d22017-03-21 16:05:12 -07001309 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001310 all_refs = self.bare_ref.all
1311 self.CleanPublishedCache(all_refs)
1312 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001313
Mike Frysinger0458faa2021-03-10 23:35:44 -05001314 # Special case the root of the repo client checkout. Make sure it doesn't
1315 # contain files being checked out to dirs we don't allow.
1316 if self.relpath == '.':
1317 PROTECTED_PATHS = {'.repo'}
1318 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1319 bad_paths = paths & PROTECTED_PATHS
1320 if bad_paths:
1321 syncbuf.fail(self,
1322 'Refusing to checkout project that writes to protected '
1323 'paths: %s' % (', '.join(bad_paths),))
1324 return
1325
David Pursehouse1d947b32012-10-25 12:23:11 +09001326 def _doff():
1327 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001328 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001329
Martin Kellye4e94d22017-03-21 16:05:12 -07001330 def _dosubmodules():
1331 self._SyncSubmodules(quiet=True)
1332
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001333 head = self.work_git.GetHead()
1334 if head.startswith(R_HEADS):
1335 branch = head[len(R_HEADS):]
1336 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001337 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001338 except KeyError:
1339 head = None
1340 else:
1341 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001343 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344 # Currently on a detached HEAD. The user is assumed to
1345 # not have any local modifications worth worrying about.
1346 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001347 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001348 syncbuf.fail(self, _PriorSyncFailedError())
1349 return
1350
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001351 if head == revid:
1352 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001353 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001354 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001355 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001356 # The copy/linkfile config may have changed.
1357 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001358 return
1359 else:
1360 lost = self._revlist(not_rev(revid), HEAD)
1361 if lost:
1362 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001363
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001365 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001366 if submodules:
1367 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001368 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001369 syncbuf.fail(self, e)
1370 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001371 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001372 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001374 if head == revid:
1375 # No changes; don't do anything further.
1376 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001377 # The copy/linkfile config may have changed.
1378 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001379 return
1380
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001382
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001383 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001384 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001385 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001387 syncbuf.info(self,
1388 "leaving %s; does not track upstream",
1389 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001390 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001391 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001392 if submodules:
1393 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001394 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001395 syncbuf.fail(self, e)
1396 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001397 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001398 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001400 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001401
1402 # See if we can perform a fast forward merge. This can happen if our
1403 # branch isn't in the exact same state as we last published.
1404 try:
1405 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1406 # Skip the published logic.
1407 pub = False
1408 except GitError:
1409 pub = self.WasPublished(branch.name, all_refs)
1410
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001411 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001412 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001413 if not_merged:
1414 if upstream_gain:
1415 # The user has published this branch and some of those
1416 # commits are not yet merged upstream. We do not want
1417 # to rewrite the published commits so we punt.
1418 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001419 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001420 "branch %s is published (but not merged) and is now "
1421 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001422 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001423 elif pub == head:
1424 # All published commits are merged, and thus we are a
1425 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001426 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001427 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001428 if submodules:
1429 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001430 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001431
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001432 # Examine the local commits not in the remote. Find the
1433 # last one attributed to this user, if any.
1434 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001435 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001436 last_mine = None
1437 cnt_mine = 0
1438 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001439 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001440 if committer_email == self.UserEmail:
1441 last_mine = commit_id
1442 cnt_mine += 1
1443
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001444 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001445 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001446
1447 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001448 syncbuf.fail(self, _DirtyError())
1449 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001450
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001451 # If the upstream switched on us, warn the user.
1452 #
1453 if branch.merge != self.revisionExpr:
1454 if branch.merge and self.revisionExpr:
1455 syncbuf.info(self,
1456 'manifest switched %s...%s',
1457 branch.merge,
1458 self.revisionExpr)
1459 elif branch.merge:
1460 syncbuf.info(self,
1461 'manifest no longer tracks %s',
1462 branch.merge)
1463
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001464 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001465 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001466 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001467 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001468 syncbuf.info(self,
1469 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001470 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001471
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001472 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001473 if not ID_RE.match(self.revisionExpr):
1474 # in case of manifest sync the revisionExpr might be a SHA1
1475 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001476 if not branch.merge.startswith('refs/'):
1477 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001478 branch.Save()
1479
Mike Pontillod3153822012-02-28 11:53:24 -08001480 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001481 def _docopyandlink():
1482 self._CopyAndLinkFiles()
1483
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001484 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001485 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001486 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001487 if submodules:
1488 syncbuf.later2(self, _dosubmodules)
1489 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001490 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001492 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001493 if submodules:
1494 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001495 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001496 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001497 syncbuf.fail(self, e)
1498 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001499 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001500 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001501 if submodules:
1502 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001503
Mike Frysingere6a202f2019-08-02 15:57:57 -04001504 def AddCopyFile(self, src, dest, topdir):
1505 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001506
Mike Frysingere6a202f2019-08-02 15:57:57 -04001507 No filesystem changes occur here. Actual copying happens later on.
1508
1509 Paths should have basic validation run on them before being queued.
1510 Further checking will be handled when the actual copy happens.
1511 """
1512 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1513
1514 def AddLinkFile(self, src, dest, topdir):
1515 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1516
1517 No filesystem changes occur here. Actual linking happens later on.
1518
1519 Paths should have basic validation run on them before being queued.
1520 Further checking will be handled when the actual link happens.
1521 """
1522 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001523
James W. Mills24c13082012-04-12 15:04:13 -05001524 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001525 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001526
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001527 def DownloadPatchSet(self, change_id, patch_id):
1528 """Download a single patch set of a single change to FETCH_HEAD.
1529 """
1530 remote = self.GetRemote(self.remote.name)
1531
1532 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001533 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001534 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001535 if GitCommand(self, cmd, bare=True).Wait() != 0:
1536 return None
1537 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001538 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001539 change_id,
1540 patch_id,
1541 self.bare_git.rev_parse('FETCH_HEAD'))
1542
Mike Frysingerc0d18662020-02-19 19:19:18 -05001543 def DeleteWorktree(self, quiet=False, force=False):
1544 """Delete the source checkout and any other housekeeping tasks.
1545
1546 This currently leaves behind the internal .repo/ cache state. This helps
1547 when switching branches or manifest changes get reverted as we don't have
1548 to redownload all the git objects. But we should do some GC at some point.
1549
1550 Args:
1551 quiet: Whether to hide normal messages.
1552 force: Always delete tree even if dirty.
1553
1554 Returns:
1555 True if the worktree was completely cleaned out.
1556 """
1557 if self.IsDirty():
1558 if force:
1559 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
LaMont Jones8501d462022-06-22 19:21:15 +00001560 (self.RelPath(local=False),), file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001561 else:
1562 print('error: %s: Cannot remove project: uncommitted changes are '
LaMont Jones8501d462022-06-22 19:21:15 +00001563 'present.\n' % (self.RelPath(local=False),), file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001564 return False
1565
1566 if not quiet:
LaMont Jones8501d462022-06-22 19:21:15 +00001567 print('%s: Deleting obsolete checkout.' % (self.RelPath(local=False),))
Mike Frysingerc0d18662020-02-19 19:19:18 -05001568
1569 # Unlock and delink from the main worktree. We don't use git's worktree
1570 # remove because it will recursively delete projects -- we handle that
1571 # ourselves below. https://crbug.com/git/48
1572 if self.use_git_worktrees:
1573 needle = platform_utils.realpath(self.gitdir)
1574 # Find the git worktree commondir under .repo/worktrees/.
1575 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1576 assert output.startswith('worktree '), output
1577 commondir = output[9:]
1578 # Walk each of the git worktrees to see where they point.
1579 configs = os.path.join(commondir, 'worktrees')
1580 for name in os.listdir(configs):
1581 gitdir = os.path.join(configs, name, 'gitdir')
1582 with open(gitdir) as fp:
1583 relpath = fp.read().strip()
1584 # Resolve the checkout path and see if it matches this project.
1585 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1586 if fullpath == needle:
1587 platform_utils.rmtree(os.path.join(configs, name))
1588
1589 # Delete the .git directory first, so we're less likely to have a partially
1590 # working git repository around. There shouldn't be any git projects here,
1591 # so rmtree works.
1592
1593 # Try to remove plain files first in case of git worktrees. If this fails
1594 # for any reason, we'll fall back to rmtree, and that'll display errors if
1595 # it can't remove things either.
1596 try:
1597 platform_utils.remove(self.gitdir)
1598 except OSError:
1599 pass
1600 try:
1601 platform_utils.rmtree(self.gitdir)
1602 except OSError as e:
1603 if e.errno != errno.ENOENT:
1604 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1605 print('error: %s: Failed to delete obsolete checkout; remove manually, '
LaMont Jones8501d462022-06-22 19:21:15 +00001606 'then run `repo sync -l`.' % (self.RelPath(local=False),),
1607 file=sys.stderr)
Mike Frysingerc0d18662020-02-19 19:19:18 -05001608 return False
1609
1610 # Delete everything under the worktree, except for directories that contain
1611 # another git project.
1612 dirs_to_remove = []
1613 failed = False
1614 for root, dirs, files in platform_utils.walk(self.worktree):
1615 for f in files:
1616 path = os.path.join(root, f)
1617 try:
1618 platform_utils.remove(path)
1619 except OSError as e:
1620 if e.errno != errno.ENOENT:
1621 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1622 failed = True
1623 dirs[:] = [d for d in dirs
1624 if not os.path.lexists(os.path.join(root, d, '.git'))]
1625 dirs_to_remove += [os.path.join(root, d) for d in dirs
1626 if os.path.join(root, d) not in dirs_to_remove]
1627 for d in reversed(dirs_to_remove):
1628 if platform_utils.islink(d):
1629 try:
1630 platform_utils.remove(d)
1631 except OSError as e:
1632 if e.errno != errno.ENOENT:
1633 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1634 failed = True
1635 elif not platform_utils.listdir(d):
1636 try:
1637 platform_utils.rmdir(d)
1638 except OSError as e:
1639 if e.errno != errno.ENOENT:
1640 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1641 failed = True
1642 if failed:
LaMont Jones8501d462022-06-22 19:21:15 +00001643 print('error: %s: Failed to delete obsolete checkout.' % (self.RelPath(local=False),),
Mike Frysingerc0d18662020-02-19 19:19:18 -05001644 file=sys.stderr)
1645 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1646 return False
1647
1648 # Try deleting parent dirs if they are empty.
1649 path = self.worktree
1650 while path != self.manifest.topdir:
1651 try:
1652 platform_utils.rmdir(path)
1653 except OSError as e:
1654 if e.errno != errno.ENOENT:
1655 break
1656 path = os.path.dirname(path)
1657
1658 return True
1659
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001660# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001661 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001662 """Create a new branch off the manifest's revision.
1663 """
Simran Basib9a1b732015-08-20 12:19:28 -07001664 if not branch_merge:
1665 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001666 head = self.work_git.GetHead()
1667 if head == (R_HEADS + name):
1668 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001669
David Pursehouse8a68ff92012-09-24 12:15:13 +09001670 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001671 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001672 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001673 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001674 capture_stdout=True,
1675 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001676
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001677 branch = self.GetBranch(name)
1678 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001679 branch.merge = branch_merge
1680 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1681 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001682
1683 if revision is None:
1684 revid = self.GetRevisionId(all_refs)
1685 else:
1686 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001687
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001688 if head.startswith(R_HEADS):
1689 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001690 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001691 except KeyError:
1692 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001693 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001694 ref = R_HEADS + name
1695 self.work_git.update_ref(ref, revid)
1696 self.work_git.symbolic_ref(HEAD, ref)
1697 branch.Save()
1698 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001699
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001700 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001701 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001702 capture_stdout=True,
1703 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001704 branch.Save()
1705 return True
1706 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001707
Wink Saville02d79452009-04-10 13:01:24 -07001708 def CheckoutBranch(self, name):
1709 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001710
1711 Args:
1712 name: The name of the branch to checkout.
1713
1714 Returns:
1715 True if the checkout succeeded; False if it didn't; None if the branch
1716 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001717 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001718 rev = R_HEADS + name
1719 head = self.work_git.GetHead()
1720 if head == rev:
1721 # Already on the branch
1722 #
1723 return True
Wink Saville02d79452009-04-10 13:01:24 -07001724
David Pursehouse8a68ff92012-09-24 12:15:13 +09001725 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001726 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001727 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001728 except KeyError:
1729 # Branch does not exist in this project
1730 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001731 return None
Wink Saville02d79452009-04-10 13:01:24 -07001732
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001733 if head.startswith(R_HEADS):
1734 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001735 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001736 except KeyError:
1737 head = None
1738
1739 if head == revid:
1740 # Same revision; just update HEAD to point to the new
1741 # target branch, but otherwise take no other action.
1742 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001743 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1744 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001745 return True
1746
1747 return GitCommand(self,
1748 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001749 capture_stdout=True,
1750 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001751
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001752 def AbandonBranch(self, name):
1753 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001754
1755 Args:
1756 name: The name of the branch to abandon.
1757
1758 Returns:
1759 True if the abandon succeeded; False if it didn't; None if the branch
1760 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001761 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001762 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001763 all_refs = self.bare_ref.all
1764 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001765 # Doesn't exist
1766 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001767
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001768 head = self.work_git.GetHead()
1769 if head == rev:
1770 # We can't destroy the branch while we are sitting
1771 # on it. Switch to a detached HEAD.
1772 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001773 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001774
David Pursehouse8a68ff92012-09-24 12:15:13 +09001775 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001776 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001777 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001778 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001779 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001780
1781 return GitCommand(self,
1782 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001783 capture_stdout=True,
1784 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001785
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001786 def PruneHeads(self):
1787 """Prune any topic branches already merged into upstream.
1788 """
1789 cb = self.CurrentBranch
1790 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001791 left = self._allrefs
1792 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001793 if name.startswith(R_HEADS):
1794 name = name[len(R_HEADS):]
1795 if cb is None or name != cb:
1796 kill.append(name)
1797
Mike Frysingera3794e92021-03-11 23:24:01 -05001798 # Minor optimization: If there's nothing to prune, then don't try to read
1799 # any project state.
1800 if not kill and not cb:
1801 return []
1802
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001803 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001804 if cb is not None \
1805 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001806 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001807 self.work_git.DetachHead(HEAD)
1808 kill.append(cb)
1809
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001810 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001811 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001812
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001813 try:
1814 self.bare_git.DetachHead(rev)
1815
1816 b = ['branch', '-d']
1817 b.extend(kill)
1818 b = GitCommand(self, b, bare=True,
1819 capture_stdout=True,
1820 capture_stderr=True)
1821 b.Wait()
1822 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001823 if ID_RE.match(old):
1824 self.bare_git.DetachHead(old)
1825 else:
1826 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001827 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001828
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001829 for branch in kill:
1830 if (R_HEADS + branch) not in left:
1831 self.CleanPublishedCache()
1832 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001833
1834 if cb and cb not in kill:
1835 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001836 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001837
1838 kept = []
1839 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001840 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001841 branch = self.GetBranch(branch)
1842 base = branch.LocalMerge
1843 if not base:
1844 base = rev
1845 kept.append(ReviewableBranch(self, branch, base))
1846 return kept
1847
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001848# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001849 def GetRegisteredSubprojects(self):
1850 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001851
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001852 def rec(subprojects):
1853 if not subprojects:
1854 return
1855 result.extend(subprojects)
1856 for p in subprojects:
1857 rec(p.subprojects)
1858 rec(self.subprojects)
1859 return result
1860
1861 def _GetSubmodules(self):
1862 # Unfortunately we cannot call `git submodule status --recursive` here
1863 # because the working tree might not exist yet, and it cannot be used
1864 # without a working tree in its current implementation.
1865
1866 def get_submodules(gitdir, rev):
1867 # Parse .gitmodules for submodule sub_paths and sub_urls
1868 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1869 if not sub_paths:
1870 return []
1871 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1872 # revision of submodule repository
1873 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1874 submodules = []
1875 for sub_path, sub_url in zip(sub_paths, sub_urls):
1876 try:
1877 sub_rev = sub_revs[sub_path]
1878 except KeyError:
1879 # Ignore non-exist submodules
1880 continue
1881 submodules.append((sub_rev, sub_path, sub_url))
1882 return submodules
1883
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001884 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1885 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001886
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001887 def parse_gitmodules(gitdir, rev):
1888 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1889 try:
Anthony King7bdac712014-07-16 12:56:40 +01001890 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1891 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001892 except GitError:
1893 return [], []
1894 if p.Wait() != 0:
1895 return [], []
1896
1897 gitmodules_lines = []
1898 fd, temp_gitmodules_path = tempfile.mkstemp()
1899 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001900 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001901 os.close(fd)
1902 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001903 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1904 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001905 if p.Wait() != 0:
1906 return [], []
1907 gitmodules_lines = p.stdout.split('\n')
1908 except GitError:
1909 return [], []
1910 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001911 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001912
1913 names = set()
1914 paths = {}
1915 urls = {}
1916 for line in gitmodules_lines:
1917 if not line:
1918 continue
1919 m = re_path.match(line)
1920 if m:
1921 names.add(m.group(1))
1922 paths[m.group(1)] = m.group(2)
1923 continue
1924 m = re_url.match(line)
1925 if m:
1926 names.add(m.group(1))
1927 urls[m.group(1)] = m.group(2)
1928 continue
1929 names = sorted(names)
1930 return ([paths.get(name, '') for name in names],
1931 [urls.get(name, '') for name in names])
1932
1933 def git_ls_tree(gitdir, rev, paths):
1934 cmd = ['ls-tree', rev, '--']
1935 cmd.extend(paths)
1936 try:
Anthony King7bdac712014-07-16 12:56:40 +01001937 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1938 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001939 except GitError:
1940 return []
1941 if p.Wait() != 0:
1942 return []
1943 objects = {}
1944 for line in p.stdout.split('\n'):
1945 if not line.strip():
1946 continue
1947 object_rev, object_path = line.split()[2:4]
1948 objects[object_path] = object_rev
1949 return objects
1950
1951 try:
1952 rev = self.GetRevisionId()
1953 except GitError:
1954 return []
1955 return get_submodules(self.gitdir, rev)
1956
1957 def GetDerivedSubprojects(self):
1958 result = []
1959 if not self.Exists:
1960 # If git repo does not exist yet, querying its submodules will
1961 # mess up its states; so return here.
1962 return result
1963 for rev, path, url in self._GetSubmodules():
1964 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001965 relpath, worktree, gitdir, objdir = \
1966 self.manifest.GetSubprojectPaths(self, name, path)
1967 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001968 if project:
1969 result.extend(project.GetDerivedSubprojects())
1970 continue
David James8d201162013-10-11 17:03:19 -07001971
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001972 if url.startswith('..'):
1973 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001974 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001975 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001976 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001977 review=self.remote.review,
1978 revision=self.remote.revision)
1979 subproject = Project(manifest=self.manifest,
1980 name=name,
1981 remote=remote,
1982 gitdir=gitdir,
1983 objdir=objdir,
1984 worktree=worktree,
1985 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001986 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001987 revisionId=rev,
1988 rebase=self.rebase,
1989 groups=self.groups,
1990 sync_c=self.sync_c,
1991 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001992 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001993 parent=self,
1994 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001995 result.append(subproject)
1996 result.extend(subproject.GetDerivedSubprojects())
1997 return result
1998
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001999# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002000 def EnableRepositoryExtension(self, key, value='true', version=1):
2001 """Enable git repository extension |key| with |value|.
2002
2003 Args:
2004 key: The extension to enabled. Omit the "extensions." prefix.
2005 value: The value to use for the extension.
2006 version: The minimum git repository version needed.
2007 """
2008 # Make sure the git repo version is new enough already.
2009 found_version = self.config.GetInt('core.repositoryFormatVersion')
2010 if found_version is None:
2011 found_version = 0
2012 if found_version < version:
2013 self.config.SetString('core.repositoryFormatVersion', str(version))
2014
2015 # Enable the extension!
2016 self.config.SetString('extensions.%s' % (key,), value)
2017
Mike Frysinger50a81de2020-09-06 15:51:21 -04002018 def ResolveRemoteHead(self, name=None):
2019 """Find out what the default branch (HEAD) points to.
2020
2021 Normally this points to refs/heads/master, but projects are moving to main.
2022 Support whatever the server uses rather than hardcoding "master" ourselves.
2023 """
2024 if name is None:
2025 name = self.remote.name
2026
2027 # The output will look like (NB: tabs are separators):
2028 # ref: refs/heads/master HEAD
2029 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2030 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2031
2032 for line in output.splitlines():
2033 lhs, rhs = line.split('\t', 1)
2034 if rhs == 'HEAD' and lhs.startswith('ref:'):
2035 return lhs[4:].strip()
2036
2037 return None
2038
Zac Livingstone4332262017-06-16 08:56:09 -06002039 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002040 try:
2041 # if revision (sha or tag) is not present then following function
2042 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002043 self.bare_git.rev_list('-1', '--missing=allow-any',
2044 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00002045 if self.upstream:
2046 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
2047 self.bare_git.rev_list('-1', '--missing=allow-any',
2048 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002049 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002050 return True
2051 except GitError:
2052 # There is no such persistent revision. We have to fetch it.
2053 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002054
Julien Campergue335f5ef2013-10-16 11:02:35 +02002055 def _FetchArchive(self, tarpath, cwd=None):
2056 cmd = ['archive', '-v', '-o', tarpath]
2057 cmd.append('--remote=%s' % self.remote.url)
LaMont Jones8501d462022-06-22 19:21:15 +00002058 cmd.append('--prefix=%s/' % self.RelPath(local=False))
Julien Campergue335f5ef2013-10-16 11:02:35 +02002059 cmd.append(self.revisionExpr)
2060
2061 command = GitCommand(self, cmd, cwd=cwd,
2062 capture_stdout=True,
2063 capture_stderr=True)
2064
2065 if command.Wait() != 0:
2066 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2067
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002068 def _RemoteFetch(self, name=None,
2069 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002070 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002071 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002072 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002073 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002074 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002075 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002076 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002077 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002078 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002079 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002080 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002081 clone_filter=None,
2082 retry_fetches=2,
2083 retry_sleep_initial_sec=4.0,
2084 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002085 is_sha1 = False
2086 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002087 # The depth should not be used when fetching to a mirror because
2088 # it will result in a shallow repository that cannot be cloned or
2089 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002090 # The repo project should also never be synced with partial depth.
2091 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2092 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002093
Shawn Pearce69e04d82014-01-29 12:48:54 -08002094 if depth:
2095 current_branch_only = True
2096
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002097 if ID_RE.match(self.revisionExpr) is not None:
2098 is_sha1 = True
2099
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002100 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002101 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002102 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002103 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002104 elif self.upstream and self.upstream.startswith(R_TAGS):
2105 # This is a tag and its commit id should never change.
2106 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002107
2108 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002109 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002110 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002111 print('Skipped fetching project %s (already have persistent ref)'
2112 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002113 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002114 if is_sha1 and not depth:
2115 # When syncing a specific commit and --depth is not set:
2116 # * if upstream is explicitly specified and is not a sha1, fetch only
2117 # upstream as users expect only upstream to be fetch.
2118 # Note: The commit might not be in upstream in which case the sync
2119 # will fail.
2120 # * otherwise, fetch all branches to make sure we end up with the
2121 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002122 if self.upstream:
2123 current_branch_only = not ID_RE.match(self.upstream)
2124 else:
2125 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002126
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002127 if not name:
2128 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002129
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002130 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002131 if not remote.PreConnectFetch(ssh_proxy):
2132 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002133
Shawn O. Pearce88443382010-10-08 10:02:09 +02002134 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002135 if alt_dir and 'objects' == os.path.basename(alt_dir):
2136 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002137 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002138
David Pursehouse8a68ff92012-09-24 12:15:13 +09002139 all_refs = self.bare_ref.all
2140 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002141 tmp = set()
2142
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302143 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002144 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002145 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002146 all_refs[r] = ref_id
2147 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002148 continue
2149
David Pursehouse8a68ff92012-09-24 12:15:13 +09002150 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002151 continue
2152
David Pursehouse8a68ff92012-09-24 12:15:13 +09002153 r = 'refs/_alt/%s' % ref_id
2154 all_refs[r] = ref_id
2155 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002156 tmp.add(r)
2157
heping3d7bbc92017-04-12 19:51:47 +08002158 tmp_packed_lines = []
2159 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002160
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302161 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002162 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002163 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002164 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002165 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002166
heping3d7bbc92017-04-12 19:51:47 +08002167 tmp_packed = ''.join(tmp_packed_lines)
2168 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002169 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002170 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002171 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002172
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002173 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002174
Xin Li745be2e2019-06-03 11:24:30 -07002175 if clone_filter:
2176 git_require((2, 19, 0), fail=True, msg='partial clones')
2177 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002178 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002179
Conley Owensf97e8382015-01-21 11:12:46 -08002180 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002181 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002182 else:
2183 # If this repo has shallow objects, then we don't know which refs have
2184 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2185 # do this with projects that don't have shallow objects, since it is less
2186 # efficient.
2187 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2188 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002189
Mike Frysinger4847e052020-02-22 00:07:35 -05002190 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002191 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002192 if not quiet and sys.stdout.isatty():
2193 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002194 if not self.worktree:
2195 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002196 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002197
Mike Frysingere57f1142019-03-18 21:27:54 -04002198 if force_sync:
2199 cmd.append('--force')
2200
David Pursehouse74cfd272015-10-14 10:50:15 +09002201 if prune:
2202 cmd.append('--prune')
2203
LaMont Jonesff6b1da2022-06-01 21:03:34 +00002204 # Always pass something for --recurse-submodules, git with GIT_DIR behaves
2205 # incorrectly when not given `--recurse-submodules=no`. (b/218891912)
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002206 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002207
Kuang-che Wu6856f982019-11-25 12:37:55 +08002208 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002209 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002210 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002211 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002212 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002213 spec.append('tag')
2214 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002215
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302216 if self.manifest.IsMirror and not current_branch_only:
2217 branch = None
2218 else:
2219 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002220 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002221 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002222 # Shallow checkout of a specific commit, fetch from that commit and not
2223 # the heads only as the commit might be deeper in the history.
2224 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002225 if self.upstream:
2226 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002227 else:
2228 if is_sha1:
2229 branch = self.upstream
2230 if branch is not None and branch.strip():
2231 if not branch.startswith('refs/'):
2232 branch = R_HEADS + branch
2233 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2234
2235 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2236 # whole repo.
2237 if self.manifest.IsMirror and not spec:
2238 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2239
2240 # If using depth then we should not get all the tags since they may
2241 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002242 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002243 cmd.append('--no-tags')
2244 else:
2245 cmd.append('--tags')
2246 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2247
Conley Owens80b87fe2014-05-09 17:13:44 -07002248 cmd.extend(spec)
2249
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002250 # At least one retry minimum due to git remote prune.
2251 retry_fetches = max(retry_fetches, 2)
2252 retry_cur_sleep = retry_sleep_initial_sec
2253 ok = prune_tried = False
2254 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002255 gitcmd = GitCommand(
2256 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2257 ssh_proxy=ssh_proxy,
2258 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002259 if gitcmd.stdout and not quiet and output_redir:
2260 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002261 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002262 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002263 ok = True
2264 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002265
2266 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002267 elif (gitcmd.stdout and
2268 'error:' in gitcmd.stdout and
2269 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002270 # Fallthru to sleep+retry logic at the bottom.
2271 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002272
Mike Frysinger6823bc22021-04-15 02:06:28 -04002273 # Try to prune remote branches once in case there are conflicts.
2274 # For example, if the remote had refs/heads/upstream, but deleted that and
2275 # now has refs/heads/upstream/foo.
2276 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002277 'error:' in gitcmd.stdout and
2278 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002279 not prune_tried):
2280 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002281 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002282 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002283 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002284 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002285 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002286 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002287 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002288 continue
Brian Harring14a66742012-09-28 20:21:57 -07002289 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002290 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2291 # in sha1 mode, we just tried sync'ing from the upstream field; it
2292 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002293 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002294 elif ret < 0:
2295 # Git died with a signal, exit immediately
2296 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002297
2298 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002299 if not verbose and gitcmd.stdout:
2300 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002301 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002302 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2303 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002304 time.sleep(retry_cur_sleep)
2305 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2306 MAXIMUM_RETRY_SLEEP_SEC)
2307 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2308 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002309
2310 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002311 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002312 if old_packed != '':
2313 _lwrite(packed_refs, old_packed)
2314 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002315 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002316 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002317
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002318 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002319 # We just synced the upstream given branch; verify we
2320 # got what we wanted, else trigger a second run of all
2321 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002322 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002323 # Sync the current branch only with depth set to None.
2324 # We always pass depth=None down to avoid infinite recursion.
2325 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002326 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002327 current_branch_only=current_branch_only and depth,
2328 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002329 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002330
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002331 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002332
Mike Frysingere50b6a72020-02-19 01:45:48 -05002333 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002334 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002335 return False
2336
2337 remote = self.GetRemote(self.remote.name)
2338 bundle_url = remote.url + '/clone.bundle'
2339 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002340 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2341 'persistent-http',
2342 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002343 return False
2344
2345 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2346 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2347
2348 exist_dst = os.path.exists(bundle_dst)
2349 exist_tmp = os.path.exists(bundle_tmp)
2350
2351 if not initial and not exist_dst and not exist_tmp:
2352 return False
2353
2354 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002355 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2356 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002357 if not exist_dst:
2358 return False
2359
2360 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002361 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002362 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002363 if not quiet and sys.stdout.isatty():
2364 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002365 if not self.worktree:
2366 cmd.append('--update-head-ok')
2367 cmd.append(bundle_dst)
2368 for f in remote.fetch:
2369 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002370 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002371
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002372 ok = GitCommand(
2373 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002374 platform_utils.remove(bundle_dst, missing_ok=True)
2375 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002376 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002377
Mike Frysingere50b6a72020-02-19 01:45:48 -05002378 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002379 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002380
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002381 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002382 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002383 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002384 if os.path.exists(tmpPath):
2385 size = os.stat(tmpPath).st_size
2386 if size >= 1024:
2387 cmd += ['--continue-at', '%d' % (size,)]
2388 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002389 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002390 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002391 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002392 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002393 if proxy:
2394 cmd += ['--proxy', proxy]
2395 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2396 cmd += ['--proxy', os.environ['http_proxy']]
2397 if srcUrl.startswith('persistent-https'):
2398 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2399 elif srcUrl.startswith('persistent-http'):
2400 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002401 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002402
Dave Borowitz137d0132015-01-02 11:12:54 -08002403 if IsTrace():
2404 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002405 if verbose:
2406 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2407 stdout = None if verbose else subprocess.PIPE
2408 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002409 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002410 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002411 except OSError:
2412 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002413
Mike Frysingere50b6a72020-02-19 01:45:48 -05002414 (output, _) = proc.communicate()
2415 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002416
Dave Borowitz137d0132015-01-02 11:12:54 -08002417 if curlret == 22:
2418 # From curl man page:
2419 # 22: HTTP page not retrieved. The requested url was not found or
2420 # returned another error with the HTTP error code being 400 or above.
2421 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002422 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002423 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2424 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002425 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002426 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002427 elif curlret and not verbose and output:
2428 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002429
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002430 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002431 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002432 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002433 return True
2434 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002435 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002436 return False
2437 else:
2438 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002439
Kris Giesingc8d882a2014-12-23 13:02:32 -08002440 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002441 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002442 with open(path, 'rb') as f:
2443 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002444 return True
2445 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002446 if not quiet:
2447 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002448 return False
2449 except OSError:
2450 return False
2451
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002452 def _Checkout(self, rev, quiet=False):
2453 cmd = ['checkout']
2454 if quiet:
2455 cmd.append('-q')
2456 cmd.append(rev)
2457 cmd.append('--')
2458 if GitCommand(self, cmd).Wait() != 0:
2459 if self._allrefs:
2460 raise GitError('%s checkout %s ' % (self.name, rev))
2461
Mike Frysinger915fda12020-03-22 12:15:20 -04002462 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002463 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002464 if ffonly:
2465 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002466 if record_origin:
2467 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002468 cmd.append(rev)
2469 cmd.append('--')
2470 if GitCommand(self, cmd).Wait() != 0:
2471 if self._allrefs:
2472 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2473
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302474 def _LsRemote(self, refs):
2475 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302476 p = GitCommand(self, cmd, capture_stdout=True)
2477 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002478 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302479 return None
2480
Anthony King7bdac712014-07-16 12:56:40 +01002481 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002482 cmd = ['revert']
2483 cmd.append('--no-edit')
2484 cmd.append(rev)
2485 cmd.append('--')
2486 if GitCommand(self, cmd).Wait() != 0:
2487 if self._allrefs:
2488 raise GitError('%s revert %s ' % (self.name, rev))
2489
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002490 def _ResetHard(self, rev, quiet=True):
2491 cmd = ['reset', '--hard']
2492 if quiet:
2493 cmd.append('-q')
2494 cmd.append(rev)
2495 if GitCommand(self, cmd).Wait() != 0:
2496 raise GitError('%s reset --hard %s ' % (self.name, rev))
2497
Martin Kellye4e94d22017-03-21 16:05:12 -07002498 def _SyncSubmodules(self, quiet=True):
2499 cmd = ['submodule', 'update', '--init', '--recursive']
2500 if quiet:
2501 cmd.append('-q')
2502 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002503 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002504
Anthony King7bdac712014-07-16 12:56:40 +01002505 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002506 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002507 if onto is not None:
2508 cmd.extend(['--onto', onto])
2509 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002510 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002511 raise GitError('%s rebase %s ' % (self.name, upstream))
2512
Pierre Tardy3d125942012-05-04 12:18:12 +02002513 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002514 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002515 if ffonly:
2516 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002517 if GitCommand(self, cmd).Wait() != 0:
2518 raise GitError('%s merge %s ' % (self.name, head))
2519
David Pursehousee8ace262020-02-13 12:41:15 +09002520 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002521 init_git_dir = not os.path.exists(self.gitdir)
2522 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002523 try:
2524 # Initialize the bare repository, which contains all of the objects.
2525 if init_obj_dir:
2526 os.makedirs(self.objdir)
2527 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002528
Mike Frysinger333c0a42021-11-15 12:39:00 -05002529 self._UpdateHooks(quiet=quiet)
2530
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002531 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002532 # Enable per-worktree config file support if possible. This is more a
2533 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002534 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002535 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002536
Kevin Degib1a07b82015-07-27 13:33:43 -06002537 # If we have a separate directory to hold refs, initialize it as well.
2538 if self.objdir != self.gitdir:
2539 if init_git_dir:
2540 os.makedirs(self.gitdir)
2541
2542 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002543 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002544 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002545 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002546 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002547 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002548 print("Retrying clone after deleting %s" %
2549 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002550 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002551 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2552 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002553 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002554 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002555 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2556 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002557 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002558 raise e
2559 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002560
Kevin Degi384b3c52014-10-16 16:02:58 -06002561 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002562 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002563 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002564
LaMont Jonescc879a92021-11-18 22:40:18 +00002565 def _expanded_ref_dirs():
2566 """Iterate through the possible git reference directory paths."""
2567 name = self.name + '.git'
2568 yield mirror_git or os.path.join(ref_dir, name)
2569 for prefix in '', self.remote.name:
2570 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2571 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002572
LaMont Jonescc879a92021-11-18 22:40:18 +00002573 if ref_dir or mirror_git:
2574 found_ref_dir = None
2575 for path in _expanded_ref_dirs():
2576 if os.path.exists(path):
2577 found_ref_dir = path
2578 break
2579 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002580
Kevin Degib1a07b82015-07-27 13:33:43 -06002581 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002582 if not os.path.isabs(ref_dir):
2583 # The alternate directory is relative to the object database.
2584 ref_dir = os.path.relpath(ref_dir,
2585 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002586 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002587 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002588
Kevin Degib1a07b82015-07-27 13:33:43 -06002589 m = self.manifest.manifestProject.config
2590 for key in ['user.name', 'user.email']:
2591 if m.Has(key, include_defaults=False):
2592 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002593 if not self.manifest.EnableGitLfs:
2594 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2595 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002596 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002597 except Exception:
2598 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002599 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002600 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002601 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002602 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002603
David Pursehousee8ace262020-02-13 12:41:15 +09002604 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002605 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002606 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002607
David Pursehousee8ace262020-02-13 12:41:15 +09002608 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002609 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002610 if not os.path.exists(hooks):
2611 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002612
2613 # Delete sample hooks. They're noise.
2614 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002615 try:
2616 platform_utils.remove(hook, missing_ok=True)
2617 except PermissionError:
2618 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002619
Jonathan Nieder93719792015-03-17 11:29:58 -07002620 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002621 name = os.path.basename(stock_hook)
2622
Victor Boivie65e0f352011-04-18 11:23:29 +02002623 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002624 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002625 # Don't install a Gerrit Code Review hook if this
2626 # project does not appear to use it for reviews.
2627 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002628 # Since the manifest project is one of those, but also
2629 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002630 continue
2631
2632 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002633 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002634 continue
2635 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002636 # If the files are the same, we'll leave it alone. We create symlinks
2637 # below by default but fallback to hardlinks if the OS blocks them.
2638 # So if we're here, it's probably because we made a hardlink below.
2639 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002640 if not quiet:
2641 _warn("%s: Not replacing locally modified %s hook",
LaMont Jones8501d462022-06-22 19:21:15 +00002642 self.RelPath(local=False), name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002643 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002644 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002645 platform_utils.symlink(
2646 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002647 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002648 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002649 try:
2650 os.link(stock_hook, dst)
2651 except OSError:
2652 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002653 else:
2654 raise
2655
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002656 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002657 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002658 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002659 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002660 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002661 remote.review = self.remote.review
2662 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002663
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002664 if self.worktree:
2665 remote.ResetFetch(mirror=False)
2666 else:
2667 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002668 remote.Save()
2669
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002670 def _InitMRef(self):
2671 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002672 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002673 # Set up the m/ space to point to the worktree-specific ref space.
2674 # We'll update the worktree-specific ref space on each checkout.
2675 ref = R_M + self.manifest.branch
2676 if not self.bare_ref.symref(ref):
2677 self.bare_git.symbolic_ref(
2678 '-m', 'redirecting to worktree scope',
2679 ref, R_WORKTREE_M + self.manifest.branch)
2680
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002681 # We can't update this ref with git worktrees until it exists.
2682 # We'll wait until the initial checkout to set it.
2683 if not os.path.exists(self.worktree):
2684 return
2685
2686 base = R_WORKTREE_M
2687 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002688
2689 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002690 else:
2691 base = R_M
2692 active_git = self.bare_git
2693
2694 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002695
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002696 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002697 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002698
Remy Böhmer1469c282020-12-15 18:49:02 +01002699 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002700 cur = self.bare_ref.symref(ref)
2701
2702 if self.revisionId:
2703 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2704 msg = 'manifest set to %s' % self.revisionId
2705 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002706 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002707 else:
2708 remote = self.GetRemote(self.remote.name)
2709 dst = remote.ToLocal(self.revisionExpr)
2710 if cur != dst:
2711 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002712 if detach:
2713 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2714 else:
2715 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002716
Mike Frysingerc72bd842021-11-14 03:58:00 -05002717 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002718 # Git worktrees don't use symlinks to share at all.
2719 if self.use_git_worktrees:
2720 return
2721
Mike Frysingerd33dce02021-12-20 18:16:33 -05002722 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002723 # Try to self-heal a bit in simple cases.
2724 dst_path = os.path.join(destdir, name)
2725 src_path = os.path.join(srcdir, name)
2726
Mike Frysingered4f2112020-02-11 23:06:29 -05002727 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002728 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002729 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002730 # Fail if the links are pointing to the wrong place
2731 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002732 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002733 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002734 'work tree. If you\'re comfortable with the '
2735 'possibility of losing the work tree\'s git metadata,'
2736 ' use `repo sync --force-sync {0}` to '
LaMont Jones8501d462022-06-22 19:21:15 +00002737 'proceed.'.format(self.RelPath(local=False)))
Kevin Degi384b3c52014-10-16 16:02:58 -06002738
Mike Frysingerc72bd842021-11-14 03:58:00 -05002739 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002740 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2741
2742 Args:
2743 gitdir: The bare git repository. Must already be initialized.
2744 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002745 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2746 This saves you the effort of initializing |dotgit| yourself.
2747 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002748 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002749 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002750
2751 to_copy = []
2752 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002753 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002754
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002755 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002756 for name in set(to_copy).union(to_symlink):
2757 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002758 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002759 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002760
Kevin Degi384b3c52014-10-16 16:02:58 -06002761 if os.path.lexists(dst):
2762 continue
David James8d201162013-10-11 17:03:19 -07002763
2764 # If the source dir doesn't exist, create an empty dir.
2765 if name in symlink_dirs and not os.path.lexists(src):
2766 os.makedirs(src)
2767
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002768 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002769 platform_utils.symlink(
2770 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002771 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002772 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002773 shutil.copytree(src, dst)
2774 elif os.path.isfile(src):
2775 shutil.copy(src, dst)
2776
David James8d201162013-10-11 17:03:19 -07002777 except OSError as e:
2778 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002779 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002780 else:
2781 raise
2782
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002783 def _InitGitWorktree(self):
2784 """Init the project using git worktrees."""
2785 self.bare_git.worktree('prune')
2786 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2787 self.worktree, self.GetRevisionId())
2788
2789 # Rewrite the internal state files to use relative paths between the
2790 # checkouts & worktrees.
2791 dotgit = os.path.join(self.worktree, '.git')
2792 with open(dotgit, 'r') as fp:
2793 # Figure out the checkout->worktree path.
2794 setting = fp.read()
2795 assert setting.startswith('gitdir:')
2796 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002797 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2798 # of file permissions. Delete it and recreate it from scratch to avoid.
2799 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002800 # Use relative path from checkout->worktree & maintain Unix line endings
2801 # on all OS's to match git behavior.
2802 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002803 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2804 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002805 # Use relative path from worktree->checkout & maintain Unix line endings
2806 # on all OS's to match git behavior.
2807 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002808 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2809
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002810 self._InitMRef()
2811
Martin Kellye4e94d22017-03-21 16:05:12 -07002812 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002813 """Setup the worktree .git path.
2814
2815 This is the user-visible path like src/foo/.git/.
2816
2817 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2818 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2819
2820 Older checkouts had .git/ directories. If we see that, migrate it.
2821
2822 This also handles changes in the manifest. Maybe this project was backed
2823 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2824 the path we point to under .repo/projects/ to match.
2825 """
2826 dotgit = os.path.join(self.worktree, '.git')
2827
2828 # If using an old layout style (a directory), migrate it.
2829 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2830 self._MigrateOldWorkTreeGitDir(dotgit)
2831
2832 init_dotgit = not os.path.exists(dotgit)
2833 if self.use_git_worktrees:
2834 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002835 self._InitGitWorktree()
2836 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002837 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002838 if not init_dotgit:
2839 # See if the project has changed.
2840 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2841 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002842
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002843 if init_dotgit or not os.path.exists(dotgit):
2844 os.makedirs(self.worktree, exist_ok=True)
2845 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002846
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002847 if init_dotgit:
2848 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002849
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002850 # Finish checking out the worktree.
2851 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2852 if GitCommand(self, cmd).Wait() != 0:
2853 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002854
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002855 if submodules:
2856 self._SyncSubmodules(quiet=True)
2857 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002858
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002859 @classmethod
2860 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2861 """Migrate the old worktree .git/ dir style to a symlink.
2862
2863 This logic specifically only uses state from |dotgit| to figure out where to
2864 move content and not |self|. This way if the backing project also changed
2865 places, we only do the .git/ dir to .git symlink migration here. The path
2866 updates will happen independently.
2867 """
2868 # Figure out where in .repo/projects/ it's pointing to.
2869 if not os.path.islink(os.path.join(dotgit, 'refs')):
2870 raise GitError(f'{dotgit}: unsupported checkout state')
2871 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2872
2873 # Remove known symlink paths that exist in .repo/projects/.
2874 KNOWN_LINKS = {
2875 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2876 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2877 }
2878 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2879 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002880 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2881 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002882 }
2883
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002884 # First see if we'd succeed before starting the migration.
2885 unknown_paths = []
2886 for name in platform_utils.listdir(dotgit):
2887 # Ignore all temporary/backup names. These are common with vim & emacs.
2888 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2889 continue
2890
2891 dotgit_path = os.path.join(dotgit, name)
2892 if name in KNOWN_LINKS:
2893 if not platform_utils.islink(dotgit_path):
2894 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2895 else:
2896 gitdir_path = os.path.join(gitdir, name)
2897 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2898 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2899 if unknown_paths:
2900 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2901
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002902 # Now walk the paths and sync the .git/ to .repo/projects/.
2903 for name in platform_utils.listdir(dotgit):
2904 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002905
2906 # Ignore all temporary/backup names. These are common with vim & emacs.
2907 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2908 platform_utils.remove(dotgit_path)
2909 elif name in KNOWN_LINKS:
2910 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002911 else:
2912 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002913 platform_utils.remove(gitdir_path, missing_ok=True)
2914 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002915
2916 # Now that the dir should be empty, clear it out, and symlink it over.
2917 platform_utils.rmdir(dotgit)
2918 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002919
Renaud Paquay788e9622017-01-27 11:41:12 -08002920 def _get_symlink_error_message(self):
2921 if platform_utils.isWindows():
2922 return ('Unable to create symbolic link. Please re-run the command as '
2923 'Administrator, or see '
2924 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2925 'for other options.')
2926 return 'filesystem must support symlinks'
2927
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002928 def _revlist(self, *args, **kw):
2929 a = []
2930 a.extend(args)
2931 a.append('--')
2932 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002933
2934 @property
2935 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002936 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002937
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002938 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002939 """Get logs between two revisions of this project."""
2940 comp = '..'
2941 if rev1:
2942 revs = [rev1]
2943 if rev2:
2944 revs.extend([comp, rev2])
2945 cmd = ['log', ''.join(revs)]
2946 out = DiffColoring(self.config)
2947 if out.is_on and color:
2948 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002949 if pretty_format is not None:
2950 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002951 if oneline:
2952 cmd.append('--oneline')
2953
2954 try:
2955 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2956 if log.Wait() == 0:
2957 return log.stdout
2958 except GitError:
2959 # worktree may not exist if groups changed for example. In that case,
2960 # try in gitdir instead.
2961 if not os.path.exists(self.worktree):
2962 return self.bare_git.log(*cmd[1:])
2963 else:
2964 raise
2965 return None
2966
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002967 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2968 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002969 """Get the list of logs from this revision to given revisionId"""
2970 logs = {}
2971 selfId = self.GetRevisionId(self._allrefs)
2972 toId = toProject.GetRevisionId(toProject._allrefs)
2973
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002974 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2975 pretty_format=pretty_format)
2976 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2977 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002978 return logs
2979
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002980 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002981
David James8d201162013-10-11 17:03:19 -07002982 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002983 self._project = project
2984 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002985 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002986
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002987 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2988 def __getstate__(self):
2989 return (self._project, self._bare, self._gitdir)
2990
2991 def __setstate__(self, state):
2992 self._project, self._bare, self._gitdir = state
2993
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002994 def LsOthers(self):
2995 p = GitCommand(self._project,
2996 ['ls-files',
2997 '-z',
2998 '--others',
2999 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003000 bare=False,
David James8d201162013-10-11 17:03:19 -07003001 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003002 capture_stdout=True,
3003 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003004 if p.Wait() == 0:
3005 out = p.stdout
3006 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003007 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003008 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003009 return []
3010
3011 def DiffZ(self, name, *args):
3012 cmd = [name]
3013 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003014 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003015 cmd.extend(args)
3016 p = GitCommand(self._project,
3017 cmd,
David James8d201162013-10-11 17:03:19 -07003018 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003019 bare=False,
3020 capture_stdout=True,
3021 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05003022 p.Wait()
3023 r = {}
3024 out = p.stdout
3025 if out:
3026 out = iter(out[:-1].split('\0'))
3027 while out:
3028 try:
3029 info = next(out)
3030 path = next(out)
3031 except StopIteration:
3032 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003033
Mike Frysinger84230002021-02-16 17:08:35 -05003034 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003035
Mike Frysinger84230002021-02-16 17:08:35 -05003036 def __init__(self, path, omode, nmode, oid, nid, state):
3037 self.path = path
3038 self.src_path = None
3039 self.old_mode = omode
3040 self.new_mode = nmode
3041 self.old_id = oid
3042 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003043
Mike Frysinger84230002021-02-16 17:08:35 -05003044 if len(state) == 1:
3045 self.status = state
3046 self.level = None
3047 else:
3048 self.status = state[:1]
3049 self.level = state[1:]
3050 while self.level.startswith('0'):
3051 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003052
Mike Frysinger84230002021-02-16 17:08:35 -05003053 info = info[1:].split(' ')
3054 info = _Info(path, *info)
3055 if info.status in ('R', 'C'):
3056 info.src_path = info.path
3057 info.path = next(out)
3058 r[info.path] = info
3059 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003060
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003061 def GetDotgitPath(self, subpath=None):
3062 """Return the full path to the .git dir.
3063
3064 As a convenience, append |subpath| if provided.
3065 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003066 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003067 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003068 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003069 dotgit = os.path.join(self._project.worktree, '.git')
3070 if os.path.isfile(dotgit):
3071 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3072 with open(dotgit) as fp:
3073 setting = fp.read()
3074 assert setting.startswith('gitdir:')
3075 gitdir = setting.split(':', 1)[1].strip()
3076 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3077
3078 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3079
3080 def GetHead(self):
3081 """Return the ref that HEAD points to."""
3082 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003083 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003084 with open(path) as fd:
3085 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003086 except IOError as e:
3087 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003088 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303089 line = line.decode()
3090 except AttributeError:
3091 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003092 if line.startswith('ref: '):
3093 return line[5:-1]
3094 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003095
3096 def SetHead(self, ref, message=None):
3097 cmdv = []
3098 if message is not None:
3099 cmdv.extend(['-m', message])
3100 cmdv.append(HEAD)
3101 cmdv.append(ref)
3102 self.symbolic_ref(*cmdv)
3103
3104 def DetachHead(self, new, message=None):
3105 cmdv = ['--no-deref']
3106 if message is not None:
3107 cmdv.extend(['-m', message])
3108 cmdv.append(HEAD)
3109 cmdv.append(new)
3110 self.update_ref(*cmdv)
3111
3112 def UpdateRef(self, name, new, old=None,
3113 message=None,
3114 detach=False):
3115 cmdv = []
3116 if message is not None:
3117 cmdv.extend(['-m', message])
3118 if detach:
3119 cmdv.append('--no-deref')
3120 cmdv.append(name)
3121 cmdv.append(new)
3122 if old is not None:
3123 cmdv.append(old)
3124 self.update_ref(*cmdv)
3125
3126 def DeleteRef(self, name, old=None):
3127 if not old:
3128 old = self.rev_parse(name)
3129 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003130 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003131
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003132 def rev_list(self, *args, **kw):
3133 if 'format' in kw:
3134 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3135 else:
3136 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003137 cmdv.extend(args)
3138 p = GitCommand(self._project,
3139 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003140 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003141 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003142 capture_stdout=True,
3143 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003144 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003145 raise GitError('%s rev-list %s: %s' %
3146 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003147 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003148
3149 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003150 """Allow arbitrary git commands using pythonic syntax.
3151
3152 This allows you to do things like:
3153 git_obj.rev_parse('HEAD')
3154
3155 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3156 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003157 Any other positional arguments will be passed to the git command, and the
3158 following keyword arguments are supported:
3159 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003160
3161 Args:
3162 name: The name of the git command to call. Any '_' characters will
3163 be replaced with '-'.
3164
3165 Returns:
3166 A callable object that will try to call git with the named command.
3167 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003168 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003169
Dave Borowitz091f8932012-10-23 17:01:04 -07003170 def runner(*args, **kwargs):
3171 cmdv = []
3172 config = kwargs.pop('config', None)
3173 for k in kwargs:
3174 raise TypeError('%s() got an unexpected keyword argument %r'
3175 % (name, k))
3176 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303177 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003178 cmdv.append('-c')
3179 cmdv.append('%s=%s' % (k, v))
3180 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003181 cmdv.extend(args)
3182 p = GitCommand(self._project,
3183 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003184 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003185 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003186 capture_stdout=True,
3187 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003188 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003189 raise GitError('%s %s: %s' %
3190 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003191 r = p.stdout
3192 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3193 return r[:-1]
3194 return r
3195 return runner
3196
3197
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003198class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003199
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003200 def __str__(self):
3201 return 'prior sync failed; rebase still in progress'
3202
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003203
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003204class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003205
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003206 def __str__(self):
3207 return 'contains uncommitted changes'
3208
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003209
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003210class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003211
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003212 def __init__(self, project, text):
3213 self.project = project
3214 self.text = text
3215
3216 def Print(self, syncbuf):
LaMont Jones8501d462022-06-22 19:21:15 +00003217 syncbuf.out.info('%s/: %s', self.project.RelPath(local=False), self.text)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003218 syncbuf.out.nl()
3219
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003220
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003221class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003222
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003223 def __init__(self, project, why):
3224 self.project = project
3225 self.why = why
3226
3227 def Print(self, syncbuf):
3228 syncbuf.out.fail('error: %s/: %s',
LaMont Jones8501d462022-06-22 19:21:15 +00003229 self.project.RelPath(local=False),
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003230 str(self.why))
3231 syncbuf.out.nl()
3232
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003233
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003234class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003235
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003236 def __init__(self, project, action):
3237 self.project = project
3238 self.action = action
3239
3240 def Run(self, syncbuf):
3241 out = syncbuf.out
LaMont Jones8501d462022-06-22 19:21:15 +00003242 out.project('project %s/', self.project.RelPath(local=False))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003243 out.nl()
3244 try:
3245 self.action()
3246 out.nl()
3247 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003248 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003249 out.nl()
3250 return False
3251
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003252
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003253class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003254
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003255 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003256 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003257 self.project = self.printer('header', attr='bold')
3258 self.info = self.printer('info')
3259 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003260
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003261
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003262class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003263
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003264 def __init__(self, config, detach_head=False):
3265 self._messages = []
3266 self._failures = []
3267 self._later_queue1 = []
3268 self._later_queue2 = []
3269
3270 self.out = _SyncColoring(config)
3271 self.out.redirect(sys.stderr)
3272
3273 self.detach_head = detach_head
3274 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003275 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003276
3277 def info(self, project, fmt, *args):
3278 self._messages.append(_InfoMessage(project, fmt % args))
3279
3280 def fail(self, project, err=None):
3281 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003282 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003283
3284 def later1(self, project, what):
3285 self._later_queue1.append(_Later(project, what))
3286
3287 def later2(self, project, what):
3288 self._later_queue2.append(_Later(project, what))
3289
3290 def Finish(self):
3291 self._PrintMessages()
3292 self._RunLater()
3293 self._PrintMessages()
3294 return self.clean
3295
David Rileye0684ad2017-04-05 00:02:59 -07003296 def Recently(self):
3297 recent_clean = self.recent_clean
3298 self.recent_clean = True
3299 return recent_clean
3300
3301 def _MarkUnclean(self):
3302 self.clean = False
3303 self.recent_clean = False
3304
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003305 def _RunLater(self):
3306 for q in ['_later_queue1', '_later_queue2']:
3307 if not self._RunQueue(q):
3308 return
3309
3310 def _RunQueue(self, queue):
3311 for m in getattr(self, queue):
3312 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003313 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003314 return False
3315 setattr(self, queue, [])
3316 return True
3317
3318 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003319 if self._messages or self._failures:
3320 if os.isatty(2):
3321 self.out.write(progress.CSI_ERASE_LINE)
3322 self.out.write('\r')
3323
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003324 for m in self._messages:
3325 m.Print(self)
3326 for m in self._failures:
3327 m.Print(self)
3328
3329 self._messages = []
3330 self._failures = []
3331
3332
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003333class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003334 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003335
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003336 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003337 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003338 manifest=manifest,
3339 name=name,
3340 gitdir=gitdir,
3341 objdir=gitdir,
3342 worktree=worktree,
3343 remote=RemoteSpec('origin'),
3344 relpath='.repo/%s' % name,
3345 revisionExpr='refs/heads/master',
3346 revisionId=None,
3347 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003348
3349 def PreSync(self):
3350 if self.Exists:
3351 cb = self.CurrentBranch
3352 if cb:
3353 base = self.GetBranch(cb).merge
3354 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003355 self.revisionExpr = base
3356 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003357
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003358 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003359 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003360 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003361 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003362 return False
3363
David Pursehouse8a68ff92012-09-24 12:15:13 +09003364 all_refs = self.bare_ref.all
3365 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003366 head = self.work_git.GetHead()
3367 if head.startswith(R_HEADS):
3368 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003369 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003370 except KeyError:
3371 head = None
3372
3373 if revid == head:
3374 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003375 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003376 return True
3377 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003378
3379
3380class RepoProject(MetaProject):
3381 """The MetaProject for repo itself."""
3382
3383 @property
3384 def LastFetch(self):
3385 try:
3386 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3387 return os.path.getmtime(fh)
3388 except OSError:
3389 return 0
3390
3391class ManifestProject(MetaProject):
3392 """The MetaProject for manifests."""
3393
3394 def MetaBranchSwitch(self, submodules=False):
3395 """Prepare for manifest branch switch."""
3396
3397 # detach and delete manifest branch, allowing a new
3398 # branch to take over
3399 syncbuf = SyncBuffer(self.config, detach_head=True)
3400 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3401 syncbuf.Finish()
3402
3403 return GitCommand(self,
3404 ['update-ref', '-d', 'refs/heads/default'],
3405 capture_stdout=True,
3406 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003407
3408 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003409 def standalone_manifest_url(self):
3410 """The URL of the standalone manifest, or None."""
LaMont Jones55ee3042022-04-06 17:10:21 +00003411 return self.config.GetString('manifest.standalone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003412
3413 @property
3414 def manifest_groups(self):
3415 """The manifest groups string."""
3416 return self.config.GetString('manifest.groups')
3417
3418 @property
3419 def reference(self):
3420 """The --reference for this manifest."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003421 return self.config.GetString('repo.reference')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003422
3423 @property
3424 def dissociate(self):
3425 """Whether to dissociate."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003426 return self.config.GetBoolean('repo.dissociate')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003427
3428 @property
3429 def archive(self):
3430 """Whether we use archive."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003431 return self.config.GetBoolean('repo.archive')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003432
3433 @property
3434 def mirror(self):
3435 """Whether we use mirror."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003436 return self.config.GetBoolean('repo.mirror')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003437
3438 @property
3439 def use_worktree(self):
3440 """Whether we use worktree."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003441 return self.config.GetBoolean('repo.worktree')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003442
3443 @property
3444 def clone_bundle(self):
3445 """Whether we use clone_bundle."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003446 return self.config.GetBoolean('repo.clonebundle')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003447
3448 @property
3449 def submodules(self):
3450 """Whether we use submodules."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003451 return self.config.GetBoolean('repo.submodules')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003452
3453 @property
3454 def git_lfs(self):
3455 """Whether we use git_lfs."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003456 return self.config.GetBoolean('repo.git-lfs')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003457
3458 @property
3459 def use_superproject(self):
3460 """Whether we use superproject."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003461 return self.config.GetBoolean('repo.superproject')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003462
3463 @property
3464 def partial_clone(self):
3465 """Whether this is a partial clone."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003466 return self.config.GetBoolean('repo.partialclone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003467
3468 @property
3469 def depth(self):
3470 """Partial clone depth."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003471 return self.config.GetString('repo.depth')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003472
3473 @property
3474 def clone_filter(self):
3475 """The clone filter."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003476 return self.config.GetString('repo.clonefilter')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003477
3478 @property
3479 def partial_clone_exclude(self):
3480 """Partial clone exclude string"""
LaMont Jones4ada0432022-04-14 15:10:43 +00003481 return self.config.GetBoolean('repo.partialcloneexclude')
3482
3483 @property
3484 def manifest_platform(self):
3485 """The --platform argument from `repo init`."""
3486 return self.config.GetString('manifest.platform')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003487
3488 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003489 def _platform_name(self):
3490 """Return the name of the platform."""
3491 return platform.system().lower()
3492
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003493 def SyncWithPossibleInit(self, submanifest, verbose=False,
3494 current_branch_only=False, tags='', git_event_log=None):
3495 """Sync a manifestProject, possibly for the first time.
3496
3497 Call Sync() with arguments from the most recent `repo init`. If this is a
3498 new sub manifest, then inherit options from the parent's manifestProject.
3499
3500 This is used by subcmds.Sync() to do an initial download of new sub
3501 manifests.
3502
3503 Args:
3504 submanifest: an XmlSubmanifest, the submanifest to re-sync.
3505 verbose: a boolean, whether to show all output, rather than only errors.
3506 current_branch_only: a boolean, whether to only fetch the current manifest
3507 branch from the server.
3508 tags: a boolean, whether to fetch tags.
3509 git_event_log: an EventLog, for git tracing.
3510 """
3511 # TODO(lamontjones): when refactoring sync (and init?) consider how to
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003512 # better get the init options that we should use for new submanifests that
3513 # are added when syncing an existing workspace.
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003514 git_event_log = git_event_log or EventLog()
3515 spec = submanifest.ToSubmanifestSpec()
3516 # Use the init options from the existing manifestProject, or the parent if
3517 # it doesn't exist.
3518 #
3519 # Today, we only support changing manifest_groups on the sub-manifest, with
3520 # no supported-for-the-user way to change the other arguments from those
3521 # specified by the outermost manifest.
3522 #
3523 # TODO(lamontjones): determine which of these should come from the outermost
3524 # manifest and which should come from the parent manifest.
3525 mp = self if self.Exists else submanifest.parent.manifestProject
3526 return self.Sync(
3527 manifest_url=spec.manifestUrl,
3528 manifest_branch=spec.revision,
3529 standalone_manifest=mp.standalone_manifest_url,
3530 groups=mp.manifest_groups,
3531 platform=mp.manifest_platform,
3532 mirror=mp.mirror,
3533 dissociate=mp.dissociate,
3534 reference=mp.reference,
3535 worktree=mp.use_worktree,
3536 submodules=mp.submodules,
3537 archive=mp.archive,
3538 partial_clone=mp.partial_clone,
3539 clone_filter=mp.clone_filter,
3540 partial_clone_exclude=mp.partial_clone_exclude,
3541 clone_bundle=mp.clone_bundle,
3542 git_lfs=mp.git_lfs,
3543 use_superproject=mp.use_superproject,
3544 verbose=verbose,
3545 current_branch_only=current_branch_only,
3546 tags=tags,
3547 depth=mp.depth,
3548 git_event_log=git_event_log,
3549 manifest_name=spec.manifestName,
3550 this_manifest_only=True,
3551 outer_manifest=False,
3552 )
3553
LaMont Jones9b03f152022-03-29 23:01:18 +00003554 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003555 standalone_manifest=False, groups='', mirror=False, reference='',
3556 dissociate=False, worktree=False, submodules=False, archive=False,
3557 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003558 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3559 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jones55ee3042022-04-06 17:10:21 +00003560 git_event_log=None, platform='', manifest_name='default.xml',
3561 tags='', this_manifest_only=False, outer_manifest=True):
LaMont Jones9b03f152022-03-29 23:01:18 +00003562 """Sync the manifest and all submanifests.
3563
3564 Args:
3565 manifest_url: a string, the URL of the manifest project.
3566 manifest_branch: a string, the manifest branch to use.
3567 standalone_manifest: a boolean, whether to store the manifest as a static
3568 file.
3569 groups: a string, restricts the checkout to projects with the specified
3570 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003571 mirror: a boolean, whether to create a mirror of the remote repository.
3572 reference: a string, location of a repo instance to use as a reference.
3573 dissociate: a boolean, whether to dissociate from reference mirrors after
3574 clone.
3575 worktree: a boolean, whether to use git-worktree to manage projects.
3576 submodules: a boolean, whether sync submodules associated with the
3577 manifest project.
3578 archive: a boolean, whether to checkout each project as an archive. See
3579 git-archive.
3580 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003581 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003582 clone_filter: a string, filter to use with partial_clone.
3583 partial_clone_exclude : a string, comma-delimeted list of project namess
3584 to exclude from partial clone.
3585 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3586 git_lfs: a boolean, whether to enable git LFS support.
3587 use_superproject: a boolean, whether to use the manifest superproject to
3588 sync projects.
3589 verbose: a boolean, whether to show all output, rather than only errors.
3590 current_branch_only: a boolean, whether to only fetch the current manifest
3591 branch from the server.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003592 platform: a string, restrict the checkout to projects with the specified
3593 platform group.
LaMont Jones55ee3042022-04-06 17:10:21 +00003594 git_event_log: an EventLog, for git tracing.
LaMont Jones4ada0432022-04-14 15:10:43 +00003595 tags: a boolean, whether to fetch tags.
LaMont Jones409407a2022-04-05 21:21:56 +00003596 manifest_name: a string, the name of the manifest file to use.
3597 this_manifest_only: a boolean, whether to only operate on the current sub
3598 manifest.
3599 outer_manifest: a boolean, whether to start at the outermost manifest.
LaMont Jones9b03f152022-03-29 23:01:18 +00003600
3601 Returns:
3602 a boolean, whether the sync was successful.
3603 """
3604 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3605
LaMont Jones501733c2022-04-20 16:42:32 +00003606 groups = groups or self.manifest.GetDefaultGroupsStr(with_platform=False)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003607 platform = platform or 'auto'
LaMont Jones55ee3042022-04-06 17:10:21 +00003608 git_event_log = git_event_log or EventLog()
LaMont Jones409407a2022-04-05 21:21:56 +00003609 if outer_manifest and self.manifest.is_submanifest:
3610 # In a multi-manifest checkout, use the outer manifest unless we are told
3611 # not to.
3612 return self.client.outer_manifest.manifestProject.Sync(
3613 manifest_url=manifest_url,
3614 manifest_branch=manifest_branch,
3615 standalone_manifest=standalone_manifest,
3616 groups=groups,
3617 platform=platform,
3618 mirror=mirror,
3619 dissociate=dissociate,
3620 reference=reference,
3621 worktree=worktree,
3622 submodules=submodules,
3623 archive=archive,
3624 partial_clone=partial_clone,
3625 clone_filter=clone_filter,
3626 partial_clone_exclude=partial_clone_exclude,
3627 clone_bundle=clone_bundle,
3628 git_lfs=git_lfs,
3629 use_superproject=use_superproject,
3630 verbose=verbose,
3631 current_branch_only=current_branch_only,
3632 tags=tags,
3633 depth=depth,
LaMont Jones55ee3042022-04-06 17:10:21 +00003634 git_event_log=git_event_log,
LaMont Jones409407a2022-04-05 21:21:56 +00003635 manifest_name=manifest_name,
3636 this_manifest_only=this_manifest_only,
3637 outer_manifest=False)
3638
LaMont Jones9b03f152022-03-29 23:01:18 +00003639 # If repo has already been initialized, we take -u with the absence of
3640 # --standalone-manifest to mean "transition to a standard repo set up",
3641 # which necessitates starting fresh.
3642 # If --standalone-manifest is set, we always tear everything down and start
3643 # anew.
3644 if self.Exists:
3645 was_standalone_manifest = self.config.GetString('manifest.standalone')
3646 if was_standalone_manifest and not manifest_url:
3647 print('fatal: repo was initialized with a standlone manifest, '
3648 'cannot be re-initialized without --manifest-url/-u')
3649 return False
3650
3651 if standalone_manifest or (was_standalone_manifest and manifest_url):
3652 self.config.ClearCache()
3653 if self.gitdir and os.path.exists(self.gitdir):
3654 platform_utils.rmtree(self.gitdir)
3655 if self.worktree and os.path.exists(self.worktree):
3656 platform_utils.rmtree(self.worktree)
3657
3658 is_new = not self.Exists
3659 if is_new:
3660 if not manifest_url:
3661 print('fatal: manifest url is required.', file=sys.stderr)
3662 return False
3663
LaMont Jones409407a2022-04-05 21:21:56 +00003664 if verbose:
LaMont Jones9b03f152022-03-29 23:01:18 +00003665 print('Downloading manifest from %s' %
3666 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3667 file=sys.stderr)
3668
3669 # The manifest project object doesn't keep track of the path on the
3670 # server where this git is located, so let's save that here.
3671 mirrored_manifest_git = None
3672 if reference:
3673 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3674 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3675 if not mirrored_manifest_git.endswith(".git"):
3676 mirrored_manifest_git += ".git"
3677 if not os.path.exists(mirrored_manifest_git):
3678 mirrored_manifest_git = os.path.join(reference,
3679 '.repo/manifests.git')
3680
3681 self._InitGitDir(mirror_git=mirrored_manifest_git)
3682
3683 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3684 # still do much of the manifests.git set up, but will avoid actual syncs to
3685 # a remote.
3686 if standalone_manifest:
3687 self.config.SetString('manifest.standalone', manifest_url)
3688 elif not manifest_url and not manifest_branch:
3689 # If -u is set and --standalone-manifest is not, then we're not in
3690 # standalone mode. Otherwise, use config to infer what we were in the last
3691 # init.
3692 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3693 if not standalone_manifest:
3694 self.config.SetString('manifest.standalone', None)
3695
3696 self._ConfigureDepth(depth)
3697
3698 # Set the remote URL before the remote branch as we might need it below.
3699 if manifest_url:
3700 r = self.GetRemote(self.remote.name)
3701 r.url = manifest_url
3702 r.ResetFetch()
3703 r.Save()
3704
3705 if not standalone_manifest:
3706 if manifest_branch:
3707 if manifest_branch == 'HEAD':
3708 manifest_branch = self.ResolveRemoteHead()
3709 if manifest_branch is None:
3710 print('fatal: unable to resolve HEAD', file=sys.stderr)
3711 return False
3712 self.revisionExpr = manifest_branch
3713 else:
3714 if is_new:
3715 default_branch = self.ResolveRemoteHead()
3716 if default_branch is None:
3717 # If the remote doesn't have HEAD configured, default to master.
3718 default_branch = 'refs/heads/master'
3719 self.revisionExpr = default_branch
3720 else:
3721 self.PreSync()
3722
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003723 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003724 all_platforms = ['linux', 'darwin', 'windows']
3725 platformize = lambda x: 'platform-' + x
3726 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003727 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003728 groups.append(platformize(self._platform_name))
3729 elif platform == 'all':
3730 groups.extend(map(platformize, all_platforms))
3731 elif platform in all_platforms:
3732 groups.append(platformize(platform))
3733 elif platform != 'none':
3734 print('fatal: invalid platform flag', file=sys.stderr)
3735 return False
LaMont Jones4ada0432022-04-14 15:10:43 +00003736 self.config.SetString('manifest.platform', platform)
LaMont Jones9b03f152022-03-29 23:01:18 +00003737
3738 groups = [x for x in groups if x]
3739 groupstr = ','.join(groups)
3740 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3741 groupstr = None
3742 self.config.SetString('manifest.groups', groupstr)
3743
3744 if reference:
3745 self.config.SetString('repo.reference', reference)
3746
3747 if dissociate:
3748 self.config.SetBoolean('repo.dissociate', dissociate)
3749
3750 if worktree:
3751 if mirror:
3752 print('fatal: --mirror and --worktree are incompatible',
3753 file=sys.stderr)
3754 return False
3755 if submodules:
3756 print('fatal: --submodules and --worktree are incompatible',
3757 file=sys.stderr)
3758 return False
3759 self.config.SetBoolean('repo.worktree', worktree)
3760 if is_new:
3761 self.use_git_worktrees = True
3762 print('warning: --worktree is experimental!', file=sys.stderr)
3763
3764 if archive:
3765 if is_new:
3766 self.config.SetBoolean('repo.archive', archive)
3767 else:
3768 print('fatal: --archive is only supported when initializing a new '
3769 'workspace.', file=sys.stderr)
3770 print('Either delete the .repo folder in this workspace, or initialize '
3771 'in another location.', file=sys.stderr)
3772 return False
3773
3774 if mirror:
3775 if is_new:
3776 self.config.SetBoolean('repo.mirror', mirror)
3777 else:
3778 print('fatal: --mirror is only supported when initializing a new '
3779 'workspace.', file=sys.stderr)
3780 print('Either delete the .repo folder in this workspace, or initialize '
3781 'in another location.', file=sys.stderr)
3782 return False
3783
3784 if partial_clone is not None:
3785 if mirror:
3786 print('fatal: --mirror and --partial-clone are mutually exclusive',
3787 file=sys.stderr)
3788 return False
3789 self.config.SetBoolean('repo.partialclone', partial_clone)
3790 if clone_filter:
3791 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jones55ee3042022-04-06 17:10:21 +00003792 elif self.partial_clone:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003793 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003794 else:
3795 clone_filter = None
3796
3797 if partial_clone_exclude is not None:
3798 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3799
3800 if clone_bundle is None:
3801 clone_bundle = False if partial_clone else True
3802 else:
3803 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3804
3805 if submodules:
3806 self.config.SetBoolean('repo.submodules', submodules)
3807
3808 if git_lfs is not None:
3809 if git_lfs:
3810 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3811
3812 self.config.SetBoolean('repo.git-lfs', git_lfs)
3813 if not is_new:
3814 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3815 ' Existing projects will require manual updates.\n', file=sys.stderr)
3816
3817 if use_superproject is not None:
3818 self.config.SetBoolean('repo.superproject', use_superproject)
3819
LaMont Jones0165e202022-04-27 17:34:42 +00003820 if not standalone_manifest:
3821 if not self.Sync_NetworkHalf(
3822 is_new=is_new, quiet=not verbose, verbose=verbose,
3823 clone_bundle=clone_bundle, current_branch_only=current_branch_only,
3824 tags=tags, submodules=submodules, clone_filter=clone_filter,
3825 partial_clone_exclude=self.manifest.PartialCloneExclude):
3826 r = self.GetRemote(self.remote.name)
3827 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
LaMont Jones9b03f152022-03-29 23:01:18 +00003828
LaMont Jones0165e202022-04-27 17:34:42 +00003829 # Better delete the manifest git dir if we created it; otherwise next
3830 # time (when user fixes problems) we won't go through the "is_new" logic.
3831 if is_new:
3832 platform_utils.rmtree(self.gitdir)
LaMont Jones9b03f152022-03-29 23:01:18 +00003833 return False
3834
LaMont Jones0165e202022-04-27 17:34:42 +00003835 if manifest_branch:
3836 self.MetaBranchSwitch(submodules=submodules)
3837
3838 syncbuf = SyncBuffer(self.config)
3839 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3840 syncbuf.Finish()
3841
3842 if is_new or self.CurrentBranch is None:
3843 if not self.StartBranch('default'):
3844 print('fatal: cannot create default in manifest', file=sys.stderr)
3845 return False
3846
3847 if not manifest_name:
3848 print('fatal: manifest name (-m) is required.', file=sys.stderr)
3849 return False
3850
3851 elif is_new:
3852 # This is a new standalone manifest.
3853 manifest_name = 'default.xml'
3854 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3855 dest = os.path.join(self.worktree, manifest_name)
3856 os.makedirs(os.path.dirname(dest), exist_ok=True)
3857 with open(dest, 'wb') as f:
3858 f.write(manifest_data)
LaMont Jones409407a2022-04-05 21:21:56 +00003859
3860 try:
3861 self.manifest.Link(manifest_name)
3862 except ManifestParseError as e:
3863 print("fatal: manifest '%s' not available" % manifest_name,
3864 file=sys.stderr)
3865 print('fatal: %s' % str(e), file=sys.stderr)
3866 return False
3867
LaMont Jones55ee3042022-04-06 17:10:21 +00003868 if not this_manifest_only:
3869 for submanifest in self.manifest.submanifests.values():
LaMont Jonesb90a4222022-04-14 15:00:09 +00003870 spec = submanifest.ToSubmanifestSpec()
LaMont Jones55ee3042022-04-06 17:10:21 +00003871 submanifest.repo_client.manifestProject.Sync(
3872 manifest_url=spec.manifestUrl,
3873 manifest_branch=spec.revision,
3874 standalone_manifest=standalone_manifest,
3875 groups=self.manifest_groups,
3876 platform=platform,
3877 mirror=mirror,
3878 dissociate=dissociate,
3879 reference=reference,
3880 worktree=worktree,
3881 submodules=submodules,
3882 archive=archive,
3883 partial_clone=partial_clone,
3884 clone_filter=clone_filter,
3885 partial_clone_exclude=partial_clone_exclude,
3886 clone_bundle=clone_bundle,
3887 git_lfs=git_lfs,
3888 use_superproject=use_superproject,
3889 verbose=verbose,
3890 current_branch_only=current_branch_only,
3891 tags=tags,
3892 depth=depth,
3893 git_event_log=git_event_log,
3894 manifest_name=spec.manifestName,
3895 this_manifest_only=False,
3896 outer_manifest=False,
3897 )
LaMont Jones409407a2022-04-05 21:21:56 +00003898
LaMont Jones0ddb6772022-05-20 09:11:54 +00003899 # Lastly, if the manifest has a <superproject> then have the superproject
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003900 # sync it (if it will be used).
3901 if git_superproject.UseSuperproject(use_superproject, self.manifest):
LaMont Jones0ddb6772022-05-20 09:11:54 +00003902 sync_result = self.manifest.superproject.Sync(git_event_log)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003903 if not sync_result.success:
3904 print('warning: git update of superproject for '
3905 f'{self.manifest.path_prefix} failed, repo sync will not use '
3906 'superproject to fetch source; while this error is not fatal, '
3907 'and you can continue to run repo sync, please run repo init '
3908 'with the --no-use-superproject option to stop seeing this '
3909 'warning', file=sys.stderr)
3910 if sync_result.fatal and use_superproject is not None:
3911 return False
LaMont Jones409407a2022-04-05 21:21:56 +00003912
LaMont Jones9b03f152022-03-29 23:01:18 +00003913 return True
3914
3915 def _ConfigureDepth(self, depth):
3916 """Configure the depth we'll sync down.
3917
3918 Args:
3919 depth: an int, how deep of a partial clone to create.
3920 """
3921 # Opt.depth will be non-None if user actually passed --depth to repo init.
3922 if depth is not None:
3923 if depth > 0:
3924 # Positive values will set the depth.
3925 depth = str(depth)
3926 else:
3927 # Negative numbers will clear the depth; passing None to SetString
3928 # will do that.
3929 depth = None
3930
3931 # We store the depth in the main manifest project.
3932 self.config.SetString('repo.depth', depth)