blob: cf58a6240cc194b607d8b969f14e76bcdbdc2e0c [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
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600797 def PrintWorkTreeStatus(self, output_redir=None, quiet=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.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700804 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700805 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700806 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200807 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700808 print(file=output_redir)
809 print('project %s/' % self.relpath, file=output_redir)
810 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811 return
812
813 self.work_git.update_index('-q',
814 '--unmerged',
815 '--ignore-missing',
816 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700817 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700818 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
819 df = self.work_git.DiffZ('diff-files')
820 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100821 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700822 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700823
824 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700825 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200826 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700827 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600829 if quiet:
830 out.nl()
831 return 'DIRTY'
832
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700833 branch = self.CurrentBranch
834 if branch is None:
835 out.nobranch('(*** NO BRANCH ***)')
836 else:
837 out.branch('branch %s', branch)
838 out.nl()
839
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700840 if rb:
841 out.important('prior sync failed; rebase still in progress')
842 out.nl()
843
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844 paths = list()
845 paths.extend(di.keys())
846 paths.extend(df.keys())
847 paths.extend(do)
848
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530849 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900850 try:
851 i = di[p]
852 except KeyError:
853 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900855 try:
856 f = df[p]
857 except KeyError:
858 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200859
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900860 if i:
861 i_status = i.status.upper()
862 else:
863 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700864
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900865 if f:
866 f_status = f.status.lower()
867 else:
868 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869
870 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800871 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700872 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700873 else:
874 line = ' %s%s\t%s' % (i_status, f_status, p)
875
876 if i and not f:
877 out.added('%s', line)
878 elif (i and f) or (not i and f):
879 out.changed('%s', line)
880 elif not i and not f:
881 out.untracked('%s', line)
882 else:
883 out.write('%s', line)
884 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200885
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700886 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700887
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500888 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889 """Prints the status of the repository to stdout.
890 """
891 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500892 if output_redir:
893 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894 cmd = ['diff']
895 if out.is_on:
896 cmd.append('--color')
897 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300898 if absolute_paths:
899 cmd.append('--src-prefix=a/%s/' % self.relpath)
900 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400902 try:
903 p = GitCommand(self,
904 cmd,
905 capture_stdout=True,
906 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500907 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400908 except GitError as e:
909 out.nl()
910 out.project('project %s/' % self.relpath)
911 out.nl()
912 out.fail('%s', str(e))
913 out.nl()
914 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500915 if p.stdout:
916 out.nl()
917 out.project('project %s/' % self.relpath)
918 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500919 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400920 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700922# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900923 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 """Was the branch published (uploaded) for code review?
925 If so, returns the SHA-1 hash of the last published
926 state for the branch.
927 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700928 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900929 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700930 try:
931 return self.bare_git.rev_parse(key)
932 except GitError:
933 return None
934 else:
935 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900936 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700937 except KeyError:
938 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939
David Pursehouse8a68ff92012-09-24 12:15:13 +0900940 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941 """Prunes any stale published refs.
942 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900943 if all_refs is None:
944 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945 heads = set()
946 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530947 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 if name.startswith(R_HEADS):
949 heads.add(name)
950 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900951 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530953 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954 n = name[len(R_PUB):]
955 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900956 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700958 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700959 """List any branches which can be uploaded for review.
960 """
961 heads = {}
962 pubed = {}
963
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530964 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700965 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900966 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900968 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969
970 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530971 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900972 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700974 if selected_branch and branch != selected_branch:
975 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700976
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800977 rb = self.GetUploadableBranch(branch)
978 if rb:
979 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980 return ready
981
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800982 def GetUploadableBranch(self, branch_name):
983 """Get a single uploadable branch, or None.
984 """
985 branch = self.GetBranch(branch_name)
986 base = branch.LocalMerge
987 if branch.LocalMerge:
988 rb = ReviewableBranch(self, branch, base)
989 if rb.commits:
990 return rb
991 return None
992
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700993 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100994 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500995 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700996 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500997 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500998 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200999 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001000 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001001 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001002 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001003 validate_certs=True,
1004 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001005 """Uploads the named branch for code review.
1006 """
1007 if branch is None:
1008 branch = self.CurrentBranch
1009 if branch is None:
1010 raise GitError('not currently on a branch')
1011
1012 branch = self.GetBranch(branch)
1013 if not branch.LocalMerge:
1014 raise GitError('branch %s does not track a remote' % branch.name)
1015 if not branch.remote.review:
1016 raise GitError('remote %s has no review url' % branch.remote.name)
1017
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001018 # Basic validity check on label syntax.
1019 for label in labels:
1020 if not re.match(r'^.+[+-][0-9]+$', label):
1021 raise UploadError(
1022 f'invalid label syntax "{label}": labels use forms like '
1023 'CodeReview+1 or Verified-1')
1024
Bryan Jacobsf609f912013-05-06 13:36:24 -04001025 if dest_branch is None:
1026 dest_branch = self.dest_branch
1027 if dest_branch is None:
1028 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001029 if not dest_branch.startswith(R_HEADS):
1030 dest_branch = R_HEADS + dest_branch
1031
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001032 if not branch.remote.projectname:
1033 branch.remote.projectname = self.name
1034 branch.remote.Save()
1035
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001036 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001037 if url is None:
1038 raise UploadError('review not configured')
1039 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001040 if dryrun:
1041 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001042
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001043 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001044 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001045
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001046 for push_option in (push_options or []):
1047 cmd.append('-o')
1048 cmd.append(push_option)
1049
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001050 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001051
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001052 if dest_branch.startswith(R_HEADS):
1053 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001054
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001055 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001056 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001057 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001058 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001059 opts += ['t=%s' % p for p in hashtags]
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001060 # NB: No need to encode labels as they've been validated above.
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001061 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001062
David Pursehousef25a3702018-11-14 19:01:22 -08001063 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001064 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001065 if notify:
1066 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001067 if private:
1068 opts += ['private']
1069 if wip:
1070 opts += ['wip']
1071 if opts:
1072 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001073 cmd.append(ref_spec)
1074
Anthony King7bdac712014-07-16 12:56:40 +01001075 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001076 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001077
Mike Frysingerd7f86832020-11-19 19:18:46 -05001078 if not dryrun:
1079 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1080 self.bare_git.UpdateRef(R_PUB + branch.name,
1081 R_HEADS + branch.name,
1082 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001084# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001085 def _ExtractArchive(self, tarpath, path=None):
1086 """Extract the given tar on its current location
1087
1088 Args:
1089 - tarpath: The path to the actual tar file
1090
1091 """
1092 try:
1093 with tarfile.open(tarpath, 'r') as tar:
1094 tar.extractall(path=path)
1095 return True
1096 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001097 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001098 return False
1099
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001100 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001101 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001102 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001103 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001104 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001105 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001106 force_sync=False,
1107 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001108 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001109 archive=False,
1110 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001111 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001112 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001113 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001114 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001115 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001116 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117 """Perform only the network IO portion of the sync process.
1118 Local working directory/branch state is not affected.
1119 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001120 if archive and not isinstance(self, MetaProject):
1121 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001122 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001123 return False
1124
1125 name = self.relpath.replace('\\', '/')
1126 name = name.replace('/', '_')
1127 tarpath = '%s.tar' % name
1128 topdir = self.manifest.topdir
1129
1130 try:
1131 self._FetchArchive(tarpath, cwd=topdir)
1132 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001133 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001134 return False
1135
1136 # From now on, we only need absolute tarpath
1137 tarpath = os.path.join(topdir, tarpath)
1138
1139 if not self._ExtractArchive(tarpath, path=topdir):
1140 return False
1141 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001142 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001143 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001144 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001145 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001146 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001147
1148 # If the shared object dir already exists, don't try to rebootstrap with a
1149 # clone bundle download. We should have the majority of objects already.
1150 if clone_bundle and os.path.exists(self.objdir):
1151 clone_bundle = False
1152
Raman Tennetif32f2432021-04-12 20:57:25 -07001153 if self.name in partial_clone_exclude:
1154 clone_bundle = True
1155 clone_filter = None
1156
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001157 if is_new is None:
1158 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001159 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001160 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001161 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001162 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001164
LaMont Jones68d69632022-06-07 18:24:20 +00001165 if self.UseAlternates:
Mike Frysinger1d00a7e2021-12-21 00:40:31 -05001166 # If gitdir/objects is a symlink, migrate it from the old layout.
1167 gitdir_objects = os.path.join(self.gitdir, 'objects')
1168 if platform_utils.islink(gitdir_objects):
1169 platform_utils.remove(gitdir_objects, missing_ok=True)
1170 gitdir_alt = os.path.join(self.gitdir, 'objects/info/alternates')
1171 if not os.path.exists(gitdir_alt):
1172 os.makedirs(os.path.dirname(gitdir_alt), exist_ok=True)
1173 _lwrite(gitdir_alt, os.path.join(
1174 os.path.relpath(self.objdir, gitdir_objects), 'objects') + '\n')
1175
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001176 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001177 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001178 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001179 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001180 # This works for both absolute and relative alternate directories.
1181 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001182 except IOError:
1183 alt_dir = None
1184 else:
1185 alt_dir = None
1186
Mike Frysingere50b6a72020-02-19 01:45:48 -05001187 if (clone_bundle
1188 and alt_dir is None
1189 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001190 is_new = False
1191
Mike Frysinger73561142021-05-03 01:10:09 -04001192 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001193 if self.sync_c:
1194 current_branch_only = True
1195 elif not self.manifest._loaded:
1196 # Manifest cannot check defaults until it syncs.
1197 current_branch_only = False
1198 elif self.manifest.default.sync_c:
1199 current_branch_only = True
1200
Mike Frysingerd68ed632021-05-03 01:21:35 -04001201 if tags is None:
1202 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001203
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001204 if self.clone_depth:
1205 depth = self.clone_depth
1206 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001207 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001208
Mike Frysinger521d01b2020-02-17 01:51:49 -05001209 # See if we can skip the network fetch entirely.
1210 if not (optimized_fetch and
1211 (ID_RE.match(self.revisionExpr) and
1212 self._CheckForImmutableRevision())):
1213 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001214 initial=is_new,
1215 quiet=quiet, verbose=verbose, output_redir=output_redir,
1216 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001217 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001218 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001219 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001220 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001221 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001222
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001223 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001224 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001225 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001226 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001227 if os.path.exists(alternates_file):
1228 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001229 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1230 merge_output=bool(output_redir))
1231 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001232 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001233 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001234 return False
1235 platform_utils.remove(alternates_file)
1236
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001237 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001238 self._InitMRef()
1239 else:
1240 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001241 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1242 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001243 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001244
1245 def PostRepoUpgrade(self):
1246 self._InitHooks()
1247
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001248 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001249 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001250 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001251 for copyfile in self.copyfiles:
1252 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001253 for linkfile in self.linkfiles:
1254 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001255
Julien Camperguedd654222014-01-09 16:21:37 +01001256 def GetCommitRevisionId(self):
1257 """Get revisionId of a commit.
1258
1259 Use this method instead of GetRevisionId to get the id of the commit rather
1260 than the id of the current git object (for example, a tag)
1261
1262 """
1263 if not self.revisionExpr.startswith(R_TAGS):
1264 return self.GetRevisionId(self._allrefs)
1265
1266 try:
1267 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1268 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001269 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1270 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001271
David Pursehouse8a68ff92012-09-24 12:15:13 +09001272 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001273 if self.revisionId:
1274 return self.revisionId
1275
1276 rem = self.GetRemote(self.remote.name)
1277 rev = rem.ToLocal(self.revisionExpr)
1278
David Pursehouse8a68ff92012-09-24 12:15:13 +09001279 if all_refs is not None and rev in all_refs:
1280 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001281
1282 try:
1283 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1284 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001285 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1286 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001287
Raman Tenneti6a872c92021-01-14 19:17:50 -08001288 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001289 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001290 self.upstream = self.revisionExpr
1291
Raman Tenneti6a872c92021-01-14 19:17:50 -08001292 self.revisionId = revisionId
1293
Martin Kellye4e94d22017-03-21 16:05:12 -07001294 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001295 """Perform only the local IO portion of the sync process.
1296 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001298 if not os.path.exists(self.gitdir):
1299 syncbuf.fail(self,
1300 'Cannot checkout %s due to missing network sync; Run '
1301 '`repo sync -n %s` first.' %
1302 (self.name, self.name))
1303 return
1304
Martin Kellye4e94d22017-03-21 16:05:12 -07001305 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001306 all_refs = self.bare_ref.all
1307 self.CleanPublishedCache(all_refs)
1308 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001309
Mike Frysinger0458faa2021-03-10 23:35:44 -05001310 # Special case the root of the repo client checkout. Make sure it doesn't
1311 # contain files being checked out to dirs we don't allow.
1312 if self.relpath == '.':
1313 PROTECTED_PATHS = {'.repo'}
1314 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1315 bad_paths = paths & PROTECTED_PATHS
1316 if bad_paths:
1317 syncbuf.fail(self,
1318 'Refusing to checkout project that writes to protected '
1319 'paths: %s' % (', '.join(bad_paths),))
1320 return
1321
David Pursehouse1d947b32012-10-25 12:23:11 +09001322 def _doff():
1323 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001324 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001325
Martin Kellye4e94d22017-03-21 16:05:12 -07001326 def _dosubmodules():
1327 self._SyncSubmodules(quiet=True)
1328
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001329 head = self.work_git.GetHead()
1330 if head.startswith(R_HEADS):
1331 branch = head[len(R_HEADS):]
1332 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001333 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001334 except KeyError:
1335 head = None
1336 else:
1337 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001338
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001339 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001340 # Currently on a detached HEAD. The user is assumed to
1341 # not have any local modifications worth worrying about.
1342 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001343 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001344 syncbuf.fail(self, _PriorSyncFailedError())
1345 return
1346
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001347 if head == revid:
1348 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001349 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001350 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001351 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001352 # The copy/linkfile config may have changed.
1353 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001354 return
1355 else:
1356 lost = self._revlist(not_rev(revid), HEAD)
1357 if lost:
1358 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001359
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001360 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001361 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001362 if submodules:
1363 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001364 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001365 syncbuf.fail(self, e)
1366 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001367 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001368 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001369
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001370 if head == revid:
1371 # No changes; don't do anything further.
1372 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001373 # The copy/linkfile config may have changed.
1374 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001375 return
1376
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001377 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001378
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001379 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001380 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001381 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001382 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001383 syncbuf.info(self,
1384 "leaving %s; does not track upstream",
1385 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001387 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001388 if submodules:
1389 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001390 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001391 syncbuf.fail(self, e)
1392 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001393 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001394 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001395
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001396 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001397
1398 # See if we can perform a fast forward merge. This can happen if our
1399 # branch isn't in the exact same state as we last published.
1400 try:
1401 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1402 # Skip the published logic.
1403 pub = False
1404 except GitError:
1405 pub = self.WasPublished(branch.name, all_refs)
1406
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001408 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001409 if not_merged:
1410 if upstream_gain:
1411 # The user has published this branch and some of those
1412 # commits are not yet merged upstream. We do not want
1413 # to rewrite the published commits so we punt.
1414 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001415 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001416 "branch %s is published (but not merged) and is now "
1417 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001418 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001419 elif pub == head:
1420 # All published commits are merged, and thus we are a
1421 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001422 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001423 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001424 if submodules:
1425 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001426 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001427
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001428 # Examine the local commits not in the remote. Find the
1429 # last one attributed to this user, if any.
1430 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001431 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001432 last_mine = None
1433 cnt_mine = 0
1434 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001435 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001436 if committer_email == self.UserEmail:
1437 last_mine = commit_id
1438 cnt_mine += 1
1439
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001440 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001441 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001442
1443 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001444 syncbuf.fail(self, _DirtyError())
1445 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001446
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001447 # If the upstream switched on us, warn the user.
1448 #
1449 if branch.merge != self.revisionExpr:
1450 if branch.merge and self.revisionExpr:
1451 syncbuf.info(self,
1452 'manifest switched %s...%s',
1453 branch.merge,
1454 self.revisionExpr)
1455 elif branch.merge:
1456 syncbuf.info(self,
1457 'manifest no longer tracks %s',
1458 branch.merge)
1459
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001460 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001461 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001462 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001463 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001464 syncbuf.info(self,
1465 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001466 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001467
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001468 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001469 if not ID_RE.match(self.revisionExpr):
1470 # in case of manifest sync the revisionExpr might be a SHA1
1471 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001472 if not branch.merge.startswith('refs/'):
1473 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001474 branch.Save()
1475
Mike Pontillod3153822012-02-28 11:53:24 -08001476 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001477 def _docopyandlink():
1478 self._CopyAndLinkFiles()
1479
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001480 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001481 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001482 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001483 if submodules:
1484 syncbuf.later2(self, _dosubmodules)
1485 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001486 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001487 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001488 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001489 if submodules:
1490 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001491 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001492 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001493 syncbuf.fail(self, e)
1494 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001495 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001496 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001497 if submodules:
1498 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001499
Mike Frysingere6a202f2019-08-02 15:57:57 -04001500 def AddCopyFile(self, src, dest, topdir):
1501 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001502
Mike Frysingere6a202f2019-08-02 15:57:57 -04001503 No filesystem changes occur here. Actual copying happens later on.
1504
1505 Paths should have basic validation run on them before being queued.
1506 Further checking will be handled when the actual copy happens.
1507 """
1508 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1509
1510 def AddLinkFile(self, src, dest, topdir):
1511 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1512
1513 No filesystem changes occur here. Actual linking happens later on.
1514
1515 Paths should have basic validation run on them before being queued.
1516 Further checking will be handled when the actual link happens.
1517 """
1518 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001519
James W. Mills24c13082012-04-12 15:04:13 -05001520 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001521 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001522
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001523 def DownloadPatchSet(self, change_id, patch_id):
1524 """Download a single patch set of a single change to FETCH_HEAD.
1525 """
1526 remote = self.GetRemote(self.remote.name)
1527
1528 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001529 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001530 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001531 if GitCommand(self, cmd, bare=True).Wait() != 0:
1532 return None
1533 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001534 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001535 change_id,
1536 patch_id,
1537 self.bare_git.rev_parse('FETCH_HEAD'))
1538
Mike Frysingerc0d18662020-02-19 19:19:18 -05001539 def DeleteWorktree(self, quiet=False, force=False):
1540 """Delete the source checkout and any other housekeeping tasks.
1541
1542 This currently leaves behind the internal .repo/ cache state. This helps
1543 when switching branches or manifest changes get reverted as we don't have
1544 to redownload all the git objects. But we should do some GC at some point.
1545
1546 Args:
1547 quiet: Whether to hide normal messages.
1548 force: Always delete tree even if dirty.
1549
1550 Returns:
1551 True if the worktree was completely cleaned out.
1552 """
1553 if self.IsDirty():
1554 if force:
1555 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1556 (self.relpath,), file=sys.stderr)
1557 else:
1558 print('error: %s: Cannot remove project: uncommitted changes are '
1559 'present.\n' % (self.relpath,), file=sys.stderr)
1560 return False
1561
1562 if not quiet:
1563 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1564
1565 # Unlock and delink from the main worktree. We don't use git's worktree
1566 # remove because it will recursively delete projects -- we handle that
1567 # ourselves below. https://crbug.com/git/48
1568 if self.use_git_worktrees:
1569 needle = platform_utils.realpath(self.gitdir)
1570 # Find the git worktree commondir under .repo/worktrees/.
1571 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1572 assert output.startswith('worktree '), output
1573 commondir = output[9:]
1574 # Walk each of the git worktrees to see where they point.
1575 configs = os.path.join(commondir, 'worktrees')
1576 for name in os.listdir(configs):
1577 gitdir = os.path.join(configs, name, 'gitdir')
1578 with open(gitdir) as fp:
1579 relpath = fp.read().strip()
1580 # Resolve the checkout path and see if it matches this project.
1581 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1582 if fullpath == needle:
1583 platform_utils.rmtree(os.path.join(configs, name))
1584
1585 # Delete the .git directory first, so we're less likely to have a partially
1586 # working git repository around. There shouldn't be any git projects here,
1587 # so rmtree works.
1588
1589 # Try to remove plain files first in case of git worktrees. If this fails
1590 # for any reason, we'll fall back to rmtree, and that'll display errors if
1591 # it can't remove things either.
1592 try:
1593 platform_utils.remove(self.gitdir)
1594 except OSError:
1595 pass
1596 try:
1597 platform_utils.rmtree(self.gitdir)
1598 except OSError as e:
1599 if e.errno != errno.ENOENT:
1600 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1601 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1602 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1603 return False
1604
1605 # Delete everything under the worktree, except for directories that contain
1606 # another git project.
1607 dirs_to_remove = []
1608 failed = False
1609 for root, dirs, files in platform_utils.walk(self.worktree):
1610 for f in files:
1611 path = os.path.join(root, f)
1612 try:
1613 platform_utils.remove(path)
1614 except OSError as e:
1615 if e.errno != errno.ENOENT:
1616 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1617 failed = True
1618 dirs[:] = [d for d in dirs
1619 if not os.path.lexists(os.path.join(root, d, '.git'))]
1620 dirs_to_remove += [os.path.join(root, d) for d in dirs
1621 if os.path.join(root, d) not in dirs_to_remove]
1622 for d in reversed(dirs_to_remove):
1623 if platform_utils.islink(d):
1624 try:
1625 platform_utils.remove(d)
1626 except OSError as e:
1627 if e.errno != errno.ENOENT:
1628 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1629 failed = True
1630 elif not platform_utils.listdir(d):
1631 try:
1632 platform_utils.rmdir(d)
1633 except OSError as e:
1634 if e.errno != errno.ENOENT:
1635 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1636 failed = True
1637 if failed:
1638 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1639 file=sys.stderr)
1640 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1641 return False
1642
1643 # Try deleting parent dirs if they are empty.
1644 path = self.worktree
1645 while path != self.manifest.topdir:
1646 try:
1647 platform_utils.rmdir(path)
1648 except OSError as e:
1649 if e.errno != errno.ENOENT:
1650 break
1651 path = os.path.dirname(path)
1652
1653 return True
1654
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001655# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001656 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001657 """Create a new branch off the manifest's revision.
1658 """
Simran Basib9a1b732015-08-20 12:19:28 -07001659 if not branch_merge:
1660 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001661 head = self.work_git.GetHead()
1662 if head == (R_HEADS + name):
1663 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001664
David Pursehouse8a68ff92012-09-24 12:15:13 +09001665 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001666 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001667 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001668 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001669 capture_stdout=True,
1670 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001671
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001672 branch = self.GetBranch(name)
1673 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001674 branch.merge = branch_merge
1675 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1676 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001677
1678 if revision is None:
1679 revid = self.GetRevisionId(all_refs)
1680 else:
1681 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001682
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001683 if head.startswith(R_HEADS):
1684 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001685 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001686 except KeyError:
1687 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001688 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001689 ref = R_HEADS + name
1690 self.work_git.update_ref(ref, revid)
1691 self.work_git.symbolic_ref(HEAD, ref)
1692 branch.Save()
1693 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001694
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001695 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001696 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001697 capture_stdout=True,
1698 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001699 branch.Save()
1700 return True
1701 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001702
Wink Saville02d79452009-04-10 13:01:24 -07001703 def CheckoutBranch(self, name):
1704 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001705
1706 Args:
1707 name: The name of the branch to checkout.
1708
1709 Returns:
1710 True if the checkout succeeded; False if it didn't; None if the branch
1711 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001712 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001713 rev = R_HEADS + name
1714 head = self.work_git.GetHead()
1715 if head == rev:
1716 # Already on the branch
1717 #
1718 return True
Wink Saville02d79452009-04-10 13:01:24 -07001719
David Pursehouse8a68ff92012-09-24 12:15:13 +09001720 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001721 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001722 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001723 except KeyError:
1724 # Branch does not exist in this project
1725 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001726 return None
Wink Saville02d79452009-04-10 13:01:24 -07001727
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001728 if head.startswith(R_HEADS):
1729 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001730 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001731 except KeyError:
1732 head = None
1733
1734 if head == revid:
1735 # Same revision; just update HEAD to point to the new
1736 # target branch, but otherwise take no other action.
1737 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001738 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1739 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001740 return True
1741
1742 return GitCommand(self,
1743 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001744 capture_stdout=True,
1745 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001746
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001747 def AbandonBranch(self, name):
1748 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001749
1750 Args:
1751 name: The name of the branch to abandon.
1752
1753 Returns:
1754 True if the abandon succeeded; False if it didn't; None if the branch
1755 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001756 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001757 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001758 all_refs = self.bare_ref.all
1759 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001760 # Doesn't exist
1761 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001762
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001763 head = self.work_git.GetHead()
1764 if head == rev:
1765 # We can't destroy the branch while we are sitting
1766 # on it. Switch to a detached HEAD.
1767 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001768 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001769
David Pursehouse8a68ff92012-09-24 12:15:13 +09001770 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001771 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001772 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001773 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001774 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001775
1776 return GitCommand(self,
1777 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001778 capture_stdout=True,
1779 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001780
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001781 def PruneHeads(self):
1782 """Prune any topic branches already merged into upstream.
1783 """
1784 cb = self.CurrentBranch
1785 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001786 left = self._allrefs
1787 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001788 if name.startswith(R_HEADS):
1789 name = name[len(R_HEADS):]
1790 if cb is None or name != cb:
1791 kill.append(name)
1792
Mike Frysingera3794e92021-03-11 23:24:01 -05001793 # Minor optimization: If there's nothing to prune, then don't try to read
1794 # any project state.
1795 if not kill and not cb:
1796 return []
1797
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001798 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001799 if cb is not None \
1800 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001801 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001802 self.work_git.DetachHead(HEAD)
1803 kill.append(cb)
1804
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001805 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001806 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001807
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001808 try:
1809 self.bare_git.DetachHead(rev)
1810
1811 b = ['branch', '-d']
1812 b.extend(kill)
1813 b = GitCommand(self, b, bare=True,
1814 capture_stdout=True,
1815 capture_stderr=True)
1816 b.Wait()
1817 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001818 if ID_RE.match(old):
1819 self.bare_git.DetachHead(old)
1820 else:
1821 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001822 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001823
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001824 for branch in kill:
1825 if (R_HEADS + branch) not in left:
1826 self.CleanPublishedCache()
1827 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001828
1829 if cb and cb not in kill:
1830 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001831 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001832
1833 kept = []
1834 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001835 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001836 branch = self.GetBranch(branch)
1837 base = branch.LocalMerge
1838 if not base:
1839 base = rev
1840 kept.append(ReviewableBranch(self, branch, base))
1841 return kept
1842
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001843# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001844 def GetRegisteredSubprojects(self):
1845 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001846
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001847 def rec(subprojects):
1848 if not subprojects:
1849 return
1850 result.extend(subprojects)
1851 for p in subprojects:
1852 rec(p.subprojects)
1853 rec(self.subprojects)
1854 return result
1855
1856 def _GetSubmodules(self):
1857 # Unfortunately we cannot call `git submodule status --recursive` here
1858 # because the working tree might not exist yet, and it cannot be used
1859 # without a working tree in its current implementation.
1860
1861 def get_submodules(gitdir, rev):
1862 # Parse .gitmodules for submodule sub_paths and sub_urls
1863 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1864 if not sub_paths:
1865 return []
1866 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1867 # revision of submodule repository
1868 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1869 submodules = []
1870 for sub_path, sub_url in zip(sub_paths, sub_urls):
1871 try:
1872 sub_rev = sub_revs[sub_path]
1873 except KeyError:
1874 # Ignore non-exist submodules
1875 continue
1876 submodules.append((sub_rev, sub_path, sub_url))
1877 return submodules
1878
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001879 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1880 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001881
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001882 def parse_gitmodules(gitdir, rev):
1883 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1884 try:
Anthony King7bdac712014-07-16 12:56:40 +01001885 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1886 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001887 except GitError:
1888 return [], []
1889 if p.Wait() != 0:
1890 return [], []
1891
1892 gitmodules_lines = []
1893 fd, temp_gitmodules_path = tempfile.mkstemp()
1894 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001895 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001896 os.close(fd)
1897 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001898 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1899 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001900 if p.Wait() != 0:
1901 return [], []
1902 gitmodules_lines = p.stdout.split('\n')
1903 except GitError:
1904 return [], []
1905 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001906 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001907
1908 names = set()
1909 paths = {}
1910 urls = {}
1911 for line in gitmodules_lines:
1912 if not line:
1913 continue
1914 m = re_path.match(line)
1915 if m:
1916 names.add(m.group(1))
1917 paths[m.group(1)] = m.group(2)
1918 continue
1919 m = re_url.match(line)
1920 if m:
1921 names.add(m.group(1))
1922 urls[m.group(1)] = m.group(2)
1923 continue
1924 names = sorted(names)
1925 return ([paths.get(name, '') for name in names],
1926 [urls.get(name, '') for name in names])
1927
1928 def git_ls_tree(gitdir, rev, paths):
1929 cmd = ['ls-tree', rev, '--']
1930 cmd.extend(paths)
1931 try:
Anthony King7bdac712014-07-16 12:56:40 +01001932 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1933 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001934 except GitError:
1935 return []
1936 if p.Wait() != 0:
1937 return []
1938 objects = {}
1939 for line in p.stdout.split('\n'):
1940 if not line.strip():
1941 continue
1942 object_rev, object_path = line.split()[2:4]
1943 objects[object_path] = object_rev
1944 return objects
1945
1946 try:
1947 rev = self.GetRevisionId()
1948 except GitError:
1949 return []
1950 return get_submodules(self.gitdir, rev)
1951
1952 def GetDerivedSubprojects(self):
1953 result = []
1954 if not self.Exists:
1955 # If git repo does not exist yet, querying its submodules will
1956 # mess up its states; so return here.
1957 return result
1958 for rev, path, url in self._GetSubmodules():
1959 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001960 relpath, worktree, gitdir, objdir = \
1961 self.manifest.GetSubprojectPaths(self, name, path)
1962 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001963 if project:
1964 result.extend(project.GetDerivedSubprojects())
1965 continue
David James8d201162013-10-11 17:03:19 -07001966
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001967 if url.startswith('..'):
1968 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001969 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001970 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001971 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001972 review=self.remote.review,
1973 revision=self.remote.revision)
1974 subproject = Project(manifest=self.manifest,
1975 name=name,
1976 remote=remote,
1977 gitdir=gitdir,
1978 objdir=objdir,
1979 worktree=worktree,
1980 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001981 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001982 revisionId=rev,
1983 rebase=self.rebase,
1984 groups=self.groups,
1985 sync_c=self.sync_c,
1986 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001987 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001988 parent=self,
1989 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001990 result.append(subproject)
1991 result.extend(subproject.GetDerivedSubprojects())
1992 return result
1993
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001994# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001995 def EnableRepositoryExtension(self, key, value='true', version=1):
1996 """Enable git repository extension |key| with |value|.
1997
1998 Args:
1999 key: The extension to enabled. Omit the "extensions." prefix.
2000 value: The value to use for the extension.
2001 version: The minimum git repository version needed.
2002 """
2003 # Make sure the git repo version is new enough already.
2004 found_version = self.config.GetInt('core.repositoryFormatVersion')
2005 if found_version is None:
2006 found_version = 0
2007 if found_version < version:
2008 self.config.SetString('core.repositoryFormatVersion', str(version))
2009
2010 # Enable the extension!
2011 self.config.SetString('extensions.%s' % (key,), value)
2012
Mike Frysinger50a81de2020-09-06 15:51:21 -04002013 def ResolveRemoteHead(self, name=None):
2014 """Find out what the default branch (HEAD) points to.
2015
2016 Normally this points to refs/heads/master, but projects are moving to main.
2017 Support whatever the server uses rather than hardcoding "master" ourselves.
2018 """
2019 if name is None:
2020 name = self.remote.name
2021
2022 # The output will look like (NB: tabs are separators):
2023 # ref: refs/heads/master HEAD
2024 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2025 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2026
2027 for line in output.splitlines():
2028 lhs, rhs = line.split('\t', 1)
2029 if rhs == 'HEAD' and lhs.startswith('ref:'):
2030 return lhs[4:].strip()
2031
2032 return None
2033
Zac Livingstone4332262017-06-16 08:56:09 -06002034 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002035 try:
2036 # if revision (sha or tag) is not present then following function
2037 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002038 self.bare_git.rev_list('-1', '--missing=allow-any',
2039 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00002040 if self.upstream:
2041 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
2042 self.bare_git.rev_list('-1', '--missing=allow-any',
2043 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002044 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002045 return True
2046 except GitError:
2047 # There is no such persistent revision. We have to fetch it.
2048 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002049
Julien Campergue335f5ef2013-10-16 11:02:35 +02002050 def _FetchArchive(self, tarpath, cwd=None):
2051 cmd = ['archive', '-v', '-o', tarpath]
2052 cmd.append('--remote=%s' % self.remote.url)
2053 cmd.append('--prefix=%s/' % self.relpath)
2054 cmd.append(self.revisionExpr)
2055
2056 command = GitCommand(self, cmd, cwd=cwd,
2057 capture_stdout=True,
2058 capture_stderr=True)
2059
2060 if command.Wait() != 0:
2061 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2062
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002063 def _RemoteFetch(self, name=None,
2064 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002065 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002066 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002067 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002068 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002069 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002070 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002071 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002072 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002073 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002074 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002075 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002076 clone_filter=None,
2077 retry_fetches=2,
2078 retry_sleep_initial_sec=4.0,
2079 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002080 is_sha1 = False
2081 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002082 # The depth should not be used when fetching to a mirror because
2083 # it will result in a shallow repository that cannot be cloned or
2084 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002085 # The repo project should also never be synced with partial depth.
2086 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2087 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002088
Shawn Pearce69e04d82014-01-29 12:48:54 -08002089 if depth:
2090 current_branch_only = True
2091
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002092 if ID_RE.match(self.revisionExpr) is not None:
2093 is_sha1 = True
2094
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002095 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002096 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002097 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002098 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002099 elif self.upstream and self.upstream.startswith(R_TAGS):
2100 # This is a tag and its commit id should never change.
2101 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002102
2103 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002104 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002105 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002106 print('Skipped fetching project %s (already have persistent ref)'
2107 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002108 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002109 if is_sha1 and not depth:
2110 # When syncing a specific commit and --depth is not set:
2111 # * if upstream is explicitly specified and is not a sha1, fetch only
2112 # upstream as users expect only upstream to be fetch.
2113 # Note: The commit might not be in upstream in which case the sync
2114 # will fail.
2115 # * otherwise, fetch all branches to make sure we end up with the
2116 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002117 if self.upstream:
2118 current_branch_only = not ID_RE.match(self.upstream)
2119 else:
2120 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002121
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002122 if not name:
2123 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002124
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002125 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002126 if not remote.PreConnectFetch(ssh_proxy):
2127 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002128
Shawn O. Pearce88443382010-10-08 10:02:09 +02002129 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002130 if alt_dir and 'objects' == os.path.basename(alt_dir):
2131 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002132 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002133
David Pursehouse8a68ff92012-09-24 12:15:13 +09002134 all_refs = self.bare_ref.all
2135 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002136 tmp = set()
2137
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302138 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002139 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002140 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002141 all_refs[r] = ref_id
2142 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002143 continue
2144
David Pursehouse8a68ff92012-09-24 12:15:13 +09002145 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002146 continue
2147
David Pursehouse8a68ff92012-09-24 12:15:13 +09002148 r = 'refs/_alt/%s' % ref_id
2149 all_refs[r] = ref_id
2150 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002151 tmp.add(r)
2152
heping3d7bbc92017-04-12 19:51:47 +08002153 tmp_packed_lines = []
2154 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002155
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302156 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002157 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002158 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002159 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002160 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002161
heping3d7bbc92017-04-12 19:51:47 +08002162 tmp_packed = ''.join(tmp_packed_lines)
2163 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002164 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002165 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002166 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002167
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002168 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002169
Xin Li745be2e2019-06-03 11:24:30 -07002170 if clone_filter:
2171 git_require((2, 19, 0), fail=True, msg='partial clones')
2172 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002173 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002174
Conley Owensf97e8382015-01-21 11:12:46 -08002175 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002176 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002177 else:
2178 # If this repo has shallow objects, then we don't know which refs have
2179 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2180 # do this with projects that don't have shallow objects, since it is less
2181 # efficient.
2182 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2183 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002184
Mike Frysinger4847e052020-02-22 00:07:35 -05002185 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002186 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002187 if not quiet and sys.stdout.isatty():
2188 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002189 if not self.worktree:
2190 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002191 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002192
Mike Frysingere57f1142019-03-18 21:27:54 -04002193 if force_sync:
2194 cmd.append('--force')
2195
David Pursehouse74cfd272015-10-14 10:50:15 +09002196 if prune:
2197 cmd.append('--prune')
2198
LaMont Jonesff6b1da2022-06-01 21:03:34 +00002199 # Always pass something for --recurse-submodules, git with GIT_DIR behaves
2200 # incorrectly when not given `--recurse-submodules=no`. (b/218891912)
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002201 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002202
Kuang-che Wu6856f982019-11-25 12:37:55 +08002203 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002204 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002205 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002206 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002207 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002208 spec.append('tag')
2209 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002210
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302211 if self.manifest.IsMirror and not current_branch_only:
2212 branch = None
2213 else:
2214 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002215 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002216 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002217 # Shallow checkout of a specific commit, fetch from that commit and not
2218 # the heads only as the commit might be deeper in the history.
2219 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002220 if self.upstream:
2221 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002222 else:
2223 if is_sha1:
2224 branch = self.upstream
2225 if branch is not None and branch.strip():
2226 if not branch.startswith('refs/'):
2227 branch = R_HEADS + branch
2228 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2229
2230 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2231 # whole repo.
2232 if self.manifest.IsMirror and not spec:
2233 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2234
2235 # If using depth then we should not get all the tags since they may
2236 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002237 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002238 cmd.append('--no-tags')
2239 else:
2240 cmd.append('--tags')
2241 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2242
Conley Owens80b87fe2014-05-09 17:13:44 -07002243 cmd.extend(spec)
2244
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002245 # At least one retry minimum due to git remote prune.
2246 retry_fetches = max(retry_fetches, 2)
2247 retry_cur_sleep = retry_sleep_initial_sec
2248 ok = prune_tried = False
2249 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002250 gitcmd = GitCommand(
2251 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2252 ssh_proxy=ssh_proxy,
2253 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002254 if gitcmd.stdout and not quiet and output_redir:
2255 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002256 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002257 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002258 ok = True
2259 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002260
2261 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002262 elif (gitcmd.stdout and
2263 'error:' in gitcmd.stdout and
2264 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002265 # Fallthru to sleep+retry logic at the bottom.
2266 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002267
Mike Frysinger6823bc22021-04-15 02:06:28 -04002268 # Try to prune remote branches once in case there are conflicts.
2269 # For example, if the remote had refs/heads/upstream, but deleted that and
2270 # now has refs/heads/upstream/foo.
2271 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002272 'error:' in gitcmd.stdout and
2273 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002274 not prune_tried):
2275 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002276 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002277 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002278 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002279 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002280 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002281 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002282 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002283 continue
Brian Harring14a66742012-09-28 20:21:57 -07002284 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002285 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2286 # in sha1 mode, we just tried sync'ing from the upstream field; it
2287 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002288 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002289 elif ret < 0:
2290 # Git died with a signal, exit immediately
2291 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002292
2293 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002294 if not verbose and gitcmd.stdout:
2295 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002296 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002297 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2298 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002299 time.sleep(retry_cur_sleep)
2300 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2301 MAXIMUM_RETRY_SLEEP_SEC)
2302 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2303 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002304
2305 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002306 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002307 if old_packed != '':
2308 _lwrite(packed_refs, old_packed)
2309 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002310 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002311 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002312
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002313 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002314 # We just synced the upstream given branch; verify we
2315 # got what we wanted, else trigger a second run of all
2316 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002317 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002318 # Sync the current branch only with depth set to None.
2319 # We always pass depth=None down to avoid infinite recursion.
2320 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002321 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002322 current_branch_only=current_branch_only and depth,
2323 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002324 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002325
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002326 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002327
Mike Frysingere50b6a72020-02-19 01:45:48 -05002328 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002329 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002330 return False
2331
2332 remote = self.GetRemote(self.remote.name)
2333 bundle_url = remote.url + '/clone.bundle'
2334 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002335 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2336 'persistent-http',
2337 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002338 return False
2339
2340 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2341 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2342
2343 exist_dst = os.path.exists(bundle_dst)
2344 exist_tmp = os.path.exists(bundle_tmp)
2345
2346 if not initial and not exist_dst and not exist_tmp:
2347 return False
2348
2349 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002350 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2351 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002352 if not exist_dst:
2353 return False
2354
2355 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002356 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002357 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002358 if not quiet and sys.stdout.isatty():
2359 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002360 if not self.worktree:
2361 cmd.append('--update-head-ok')
2362 cmd.append(bundle_dst)
2363 for f in remote.fetch:
2364 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002365 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002366
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002367 ok = GitCommand(
2368 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002369 platform_utils.remove(bundle_dst, missing_ok=True)
2370 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002371 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002372
Mike Frysingere50b6a72020-02-19 01:45:48 -05002373 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002374 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002375
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002376 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002377 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002378 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002379 if os.path.exists(tmpPath):
2380 size = os.stat(tmpPath).st_size
2381 if size >= 1024:
2382 cmd += ['--continue-at', '%d' % (size,)]
2383 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002384 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002385 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002386 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002387 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002388 if proxy:
2389 cmd += ['--proxy', proxy]
2390 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2391 cmd += ['--proxy', os.environ['http_proxy']]
2392 if srcUrl.startswith('persistent-https'):
2393 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2394 elif srcUrl.startswith('persistent-http'):
2395 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002396 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002397
Dave Borowitz137d0132015-01-02 11:12:54 -08002398 if IsTrace():
2399 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002400 if verbose:
2401 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2402 stdout = None if verbose else subprocess.PIPE
2403 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002404 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002405 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002406 except OSError:
2407 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002408
Mike Frysingere50b6a72020-02-19 01:45:48 -05002409 (output, _) = proc.communicate()
2410 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002411
Dave Borowitz137d0132015-01-02 11:12:54 -08002412 if curlret == 22:
2413 # From curl man page:
2414 # 22: HTTP page not retrieved. The requested url was not found or
2415 # returned another error with the HTTP error code being 400 or above.
2416 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002417 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002418 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2419 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002420 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002421 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002422 elif curlret and not verbose and output:
2423 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002424
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002425 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002426 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002427 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002428 return True
2429 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002430 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002431 return False
2432 else:
2433 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002434
Kris Giesingc8d882a2014-12-23 13:02:32 -08002435 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002436 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002437 with open(path, 'rb') as f:
2438 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002439 return True
2440 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002441 if not quiet:
2442 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002443 return False
2444 except OSError:
2445 return False
2446
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002447 def _Checkout(self, rev, quiet=False):
2448 cmd = ['checkout']
2449 if quiet:
2450 cmd.append('-q')
2451 cmd.append(rev)
2452 cmd.append('--')
2453 if GitCommand(self, cmd).Wait() != 0:
2454 if self._allrefs:
2455 raise GitError('%s checkout %s ' % (self.name, rev))
2456
Mike Frysinger915fda12020-03-22 12:15:20 -04002457 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002458 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002459 if ffonly:
2460 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002461 if record_origin:
2462 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002463 cmd.append(rev)
2464 cmd.append('--')
2465 if GitCommand(self, cmd).Wait() != 0:
2466 if self._allrefs:
2467 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2468
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302469 def _LsRemote(self, refs):
2470 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302471 p = GitCommand(self, cmd, capture_stdout=True)
2472 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002473 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302474 return None
2475
Anthony King7bdac712014-07-16 12:56:40 +01002476 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002477 cmd = ['revert']
2478 cmd.append('--no-edit')
2479 cmd.append(rev)
2480 cmd.append('--')
2481 if GitCommand(self, cmd).Wait() != 0:
2482 if self._allrefs:
2483 raise GitError('%s revert %s ' % (self.name, rev))
2484
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002485 def _ResetHard(self, rev, quiet=True):
2486 cmd = ['reset', '--hard']
2487 if quiet:
2488 cmd.append('-q')
2489 cmd.append(rev)
2490 if GitCommand(self, cmd).Wait() != 0:
2491 raise GitError('%s reset --hard %s ' % (self.name, rev))
2492
Martin Kellye4e94d22017-03-21 16:05:12 -07002493 def _SyncSubmodules(self, quiet=True):
2494 cmd = ['submodule', 'update', '--init', '--recursive']
2495 if quiet:
2496 cmd.append('-q')
2497 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002498 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002499
Anthony King7bdac712014-07-16 12:56:40 +01002500 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002501 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002502 if onto is not None:
2503 cmd.extend(['--onto', onto])
2504 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002505 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002506 raise GitError('%s rebase %s ' % (self.name, upstream))
2507
Pierre Tardy3d125942012-05-04 12:18:12 +02002508 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002509 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002510 if ffonly:
2511 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002512 if GitCommand(self, cmd).Wait() != 0:
2513 raise GitError('%s merge %s ' % (self.name, head))
2514
David Pursehousee8ace262020-02-13 12:41:15 +09002515 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002516 init_git_dir = not os.path.exists(self.gitdir)
2517 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002518 try:
2519 # Initialize the bare repository, which contains all of the objects.
2520 if init_obj_dir:
2521 os.makedirs(self.objdir)
2522 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002523
Mike Frysinger333c0a42021-11-15 12:39:00 -05002524 self._UpdateHooks(quiet=quiet)
2525
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002526 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002527 # Enable per-worktree config file support if possible. This is more a
2528 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002529 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002530 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002531
Kevin Degib1a07b82015-07-27 13:33:43 -06002532 # If we have a separate directory to hold refs, initialize it as well.
2533 if self.objdir != self.gitdir:
2534 if init_git_dir:
2535 os.makedirs(self.gitdir)
2536
2537 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002538 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002539 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002540 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002541 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002542 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002543 print("Retrying clone after deleting %s" %
2544 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002545 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002546 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2547 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002548 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002549 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002550 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2551 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002552 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002553 raise e
2554 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002555
Kevin Degi384b3c52014-10-16 16:02:58 -06002556 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002557 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002558 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002559
LaMont Jonescc879a92021-11-18 22:40:18 +00002560 def _expanded_ref_dirs():
2561 """Iterate through the possible git reference directory paths."""
2562 name = self.name + '.git'
2563 yield mirror_git or os.path.join(ref_dir, name)
2564 for prefix in '', self.remote.name:
2565 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2566 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002567
LaMont Jonescc879a92021-11-18 22:40:18 +00002568 if ref_dir or mirror_git:
2569 found_ref_dir = None
2570 for path in _expanded_ref_dirs():
2571 if os.path.exists(path):
2572 found_ref_dir = path
2573 break
2574 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002575
Kevin Degib1a07b82015-07-27 13:33:43 -06002576 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002577 if not os.path.isabs(ref_dir):
2578 # The alternate directory is relative to the object database.
2579 ref_dir = os.path.relpath(ref_dir,
2580 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002581 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002582 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002583
Kevin Degib1a07b82015-07-27 13:33:43 -06002584 m = self.manifest.manifestProject.config
2585 for key in ['user.name', 'user.email']:
2586 if m.Has(key, include_defaults=False):
2587 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002588 if not self.manifest.EnableGitLfs:
2589 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2590 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002591 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002592 except Exception:
2593 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002594 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002595 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002596 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002597 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002598
David Pursehousee8ace262020-02-13 12:41:15 +09002599 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002600 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002601 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002602
David Pursehousee8ace262020-02-13 12:41:15 +09002603 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002604 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002605 if not os.path.exists(hooks):
2606 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002607
2608 # Delete sample hooks. They're noise.
2609 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002610 try:
2611 platform_utils.remove(hook, missing_ok=True)
2612 except PermissionError:
2613 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002614
Jonathan Nieder93719792015-03-17 11:29:58 -07002615 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002616 name = os.path.basename(stock_hook)
2617
Victor Boivie65e0f352011-04-18 11:23:29 +02002618 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002619 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002620 # Don't install a Gerrit Code Review hook if this
2621 # project does not appear to use it for reviews.
2622 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002623 # Since the manifest project is one of those, but also
2624 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002625 continue
2626
2627 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002628 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002629 continue
2630 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002631 # If the files are the same, we'll leave it alone. We create symlinks
2632 # below by default but fallback to hardlinks if the OS blocks them.
2633 # So if we're here, it's probably because we made a hardlink below.
2634 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002635 if not quiet:
2636 _warn("%s: Not replacing locally modified %s hook",
2637 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002638 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002639 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002640 platform_utils.symlink(
2641 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002642 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002643 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002644 try:
2645 os.link(stock_hook, dst)
2646 except OSError:
2647 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002648 else:
2649 raise
2650
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002651 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002652 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002653 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002654 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002655 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002656 remote.review = self.remote.review
2657 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002658
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002659 if self.worktree:
2660 remote.ResetFetch(mirror=False)
2661 else:
2662 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002663 remote.Save()
2664
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002665 def _InitMRef(self):
2666 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002667 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002668 # Set up the m/ space to point to the worktree-specific ref space.
2669 # We'll update the worktree-specific ref space on each checkout.
2670 ref = R_M + self.manifest.branch
2671 if not self.bare_ref.symref(ref):
2672 self.bare_git.symbolic_ref(
2673 '-m', 'redirecting to worktree scope',
2674 ref, R_WORKTREE_M + self.manifest.branch)
2675
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002676 # We can't update this ref with git worktrees until it exists.
2677 # We'll wait until the initial checkout to set it.
2678 if not os.path.exists(self.worktree):
2679 return
2680
2681 base = R_WORKTREE_M
2682 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002683
2684 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002685 else:
2686 base = R_M
2687 active_git = self.bare_git
2688
2689 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002690
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002691 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002692 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002693
Remy Böhmer1469c282020-12-15 18:49:02 +01002694 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002695 cur = self.bare_ref.symref(ref)
2696
2697 if self.revisionId:
2698 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2699 msg = 'manifest set to %s' % self.revisionId
2700 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002701 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002702 else:
2703 remote = self.GetRemote(self.remote.name)
2704 dst = remote.ToLocal(self.revisionExpr)
2705 if cur != dst:
2706 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002707 if detach:
2708 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2709 else:
2710 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002711
Mike Frysingerc72bd842021-11-14 03:58:00 -05002712 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002713 # Git worktrees don't use symlinks to share at all.
2714 if self.use_git_worktrees:
2715 return
2716
Mike Frysingerd33dce02021-12-20 18:16:33 -05002717 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002718 # Try to self-heal a bit in simple cases.
2719 dst_path = os.path.join(destdir, name)
2720 src_path = os.path.join(srcdir, name)
2721
Mike Frysingered4f2112020-02-11 23:06:29 -05002722 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002723 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002724 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002725 # Fail if the links are pointing to the wrong place
2726 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002727 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002728 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002729 'work tree. If you\'re comfortable with the '
2730 'possibility of losing the work tree\'s git metadata,'
2731 ' use `repo sync --force-sync {0}` to '
2732 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002733
Mike Frysingerc72bd842021-11-14 03:58:00 -05002734 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002735 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2736
2737 Args:
2738 gitdir: The bare git repository. Must already be initialized.
2739 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002740 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2741 This saves you the effort of initializing |dotgit| yourself.
2742 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002743 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002744 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002745
2746 to_copy = []
2747 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002748 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002749
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002750 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002751 for name in set(to_copy).union(to_symlink):
2752 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002753 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002754 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002755
Kevin Degi384b3c52014-10-16 16:02:58 -06002756 if os.path.lexists(dst):
2757 continue
David James8d201162013-10-11 17:03:19 -07002758
2759 # If the source dir doesn't exist, create an empty dir.
2760 if name in symlink_dirs and not os.path.lexists(src):
2761 os.makedirs(src)
2762
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002763 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002764 platform_utils.symlink(
2765 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002766 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002767 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002768 shutil.copytree(src, dst)
2769 elif os.path.isfile(src):
2770 shutil.copy(src, dst)
2771
David James8d201162013-10-11 17:03:19 -07002772 except OSError as e:
2773 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002774 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002775 else:
2776 raise
2777
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002778 def _InitGitWorktree(self):
2779 """Init the project using git worktrees."""
2780 self.bare_git.worktree('prune')
2781 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2782 self.worktree, self.GetRevisionId())
2783
2784 # Rewrite the internal state files to use relative paths between the
2785 # checkouts & worktrees.
2786 dotgit = os.path.join(self.worktree, '.git')
2787 with open(dotgit, 'r') as fp:
2788 # Figure out the checkout->worktree path.
2789 setting = fp.read()
2790 assert setting.startswith('gitdir:')
2791 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002792 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2793 # of file permissions. Delete it and recreate it from scratch to avoid.
2794 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002795 # Use relative path from checkout->worktree & maintain Unix line endings
2796 # on all OS's to match git behavior.
2797 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002798 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2799 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002800 # Use relative path from worktree->checkout & maintain Unix line endings
2801 # on all OS's to match git behavior.
2802 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002803 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2804
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002805 self._InitMRef()
2806
Martin Kellye4e94d22017-03-21 16:05:12 -07002807 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002808 """Setup the worktree .git path.
2809
2810 This is the user-visible path like src/foo/.git/.
2811
2812 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2813 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2814
2815 Older checkouts had .git/ directories. If we see that, migrate it.
2816
2817 This also handles changes in the manifest. Maybe this project was backed
2818 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2819 the path we point to under .repo/projects/ to match.
2820 """
2821 dotgit = os.path.join(self.worktree, '.git')
2822
2823 # If using an old layout style (a directory), migrate it.
2824 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2825 self._MigrateOldWorkTreeGitDir(dotgit)
2826
2827 init_dotgit = not os.path.exists(dotgit)
2828 if self.use_git_worktrees:
2829 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002830 self._InitGitWorktree()
2831 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002832 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002833 if not init_dotgit:
2834 # See if the project has changed.
2835 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2836 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002837
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002838 if init_dotgit or not os.path.exists(dotgit):
2839 os.makedirs(self.worktree, exist_ok=True)
2840 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002841
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002842 if init_dotgit:
2843 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002844
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002845 # Finish checking out the worktree.
2846 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2847 if GitCommand(self, cmd).Wait() != 0:
2848 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002849
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002850 if submodules:
2851 self._SyncSubmodules(quiet=True)
2852 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002853
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002854 @classmethod
2855 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2856 """Migrate the old worktree .git/ dir style to a symlink.
2857
2858 This logic specifically only uses state from |dotgit| to figure out where to
2859 move content and not |self|. This way if the backing project also changed
2860 places, we only do the .git/ dir to .git symlink migration here. The path
2861 updates will happen independently.
2862 """
2863 # Figure out where in .repo/projects/ it's pointing to.
2864 if not os.path.islink(os.path.join(dotgit, 'refs')):
2865 raise GitError(f'{dotgit}: unsupported checkout state')
2866 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2867
2868 # Remove known symlink paths that exist in .repo/projects/.
2869 KNOWN_LINKS = {
2870 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2871 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2872 }
2873 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2874 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002875 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2876 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002877 }
2878
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002879 # First see if we'd succeed before starting the migration.
2880 unknown_paths = []
2881 for name in platform_utils.listdir(dotgit):
2882 # Ignore all temporary/backup names. These are common with vim & emacs.
2883 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2884 continue
2885
2886 dotgit_path = os.path.join(dotgit, name)
2887 if name in KNOWN_LINKS:
2888 if not platform_utils.islink(dotgit_path):
2889 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2890 else:
2891 gitdir_path = os.path.join(gitdir, name)
2892 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2893 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2894 if unknown_paths:
2895 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2896
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002897 # Now walk the paths and sync the .git/ to .repo/projects/.
2898 for name in platform_utils.listdir(dotgit):
2899 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002900
2901 # Ignore all temporary/backup names. These are common with vim & emacs.
2902 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2903 platform_utils.remove(dotgit_path)
2904 elif name in KNOWN_LINKS:
2905 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002906 else:
2907 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002908 platform_utils.remove(gitdir_path, missing_ok=True)
2909 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002910
2911 # Now that the dir should be empty, clear it out, and symlink it over.
2912 platform_utils.rmdir(dotgit)
2913 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002914
Renaud Paquay788e9622017-01-27 11:41:12 -08002915 def _get_symlink_error_message(self):
2916 if platform_utils.isWindows():
2917 return ('Unable to create symbolic link. Please re-run the command as '
2918 'Administrator, or see '
2919 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2920 'for other options.')
2921 return 'filesystem must support symlinks'
2922
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002923 def _revlist(self, *args, **kw):
2924 a = []
2925 a.extend(args)
2926 a.append('--')
2927 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002928
2929 @property
2930 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002931 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002932
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002933 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002934 """Get logs between two revisions of this project."""
2935 comp = '..'
2936 if rev1:
2937 revs = [rev1]
2938 if rev2:
2939 revs.extend([comp, rev2])
2940 cmd = ['log', ''.join(revs)]
2941 out = DiffColoring(self.config)
2942 if out.is_on and color:
2943 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002944 if pretty_format is not None:
2945 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002946 if oneline:
2947 cmd.append('--oneline')
2948
2949 try:
2950 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2951 if log.Wait() == 0:
2952 return log.stdout
2953 except GitError:
2954 # worktree may not exist if groups changed for example. In that case,
2955 # try in gitdir instead.
2956 if not os.path.exists(self.worktree):
2957 return self.bare_git.log(*cmd[1:])
2958 else:
2959 raise
2960 return None
2961
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002962 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2963 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002964 """Get the list of logs from this revision to given revisionId"""
2965 logs = {}
2966 selfId = self.GetRevisionId(self._allrefs)
2967 toId = toProject.GetRevisionId(toProject._allrefs)
2968
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002969 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2970 pretty_format=pretty_format)
2971 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2972 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002973 return logs
2974
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002975 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002976
David James8d201162013-10-11 17:03:19 -07002977 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002978 self._project = project
2979 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002980 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002981
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002982 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2983 def __getstate__(self):
2984 return (self._project, self._bare, self._gitdir)
2985
2986 def __setstate__(self, state):
2987 self._project, self._bare, self._gitdir = state
2988
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002989 def LsOthers(self):
2990 p = GitCommand(self._project,
2991 ['ls-files',
2992 '-z',
2993 '--others',
2994 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002995 bare=False,
David James8d201162013-10-11 17:03:19 -07002996 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002997 capture_stdout=True,
2998 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002999 if p.Wait() == 0:
3000 out = p.stdout
3001 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003002 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003003 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003004 return []
3005
3006 def DiffZ(self, name, *args):
3007 cmd = [name]
3008 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003009 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003010 cmd.extend(args)
3011 p = GitCommand(self._project,
3012 cmd,
David James8d201162013-10-11 17:03:19 -07003013 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003014 bare=False,
3015 capture_stdout=True,
3016 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05003017 p.Wait()
3018 r = {}
3019 out = p.stdout
3020 if out:
3021 out = iter(out[:-1].split('\0'))
3022 while out:
3023 try:
3024 info = next(out)
3025 path = next(out)
3026 except StopIteration:
3027 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003028
Mike Frysinger84230002021-02-16 17:08:35 -05003029 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003030
Mike Frysinger84230002021-02-16 17:08:35 -05003031 def __init__(self, path, omode, nmode, oid, nid, state):
3032 self.path = path
3033 self.src_path = None
3034 self.old_mode = omode
3035 self.new_mode = nmode
3036 self.old_id = oid
3037 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003038
Mike Frysinger84230002021-02-16 17:08:35 -05003039 if len(state) == 1:
3040 self.status = state
3041 self.level = None
3042 else:
3043 self.status = state[:1]
3044 self.level = state[1:]
3045 while self.level.startswith('0'):
3046 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003047
Mike Frysinger84230002021-02-16 17:08:35 -05003048 info = info[1:].split(' ')
3049 info = _Info(path, *info)
3050 if info.status in ('R', 'C'):
3051 info.src_path = info.path
3052 info.path = next(out)
3053 r[info.path] = info
3054 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003055
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003056 def GetDotgitPath(self, subpath=None):
3057 """Return the full path to the .git dir.
3058
3059 As a convenience, append |subpath| if provided.
3060 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003061 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003062 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003063 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003064 dotgit = os.path.join(self._project.worktree, '.git')
3065 if os.path.isfile(dotgit):
3066 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3067 with open(dotgit) as fp:
3068 setting = fp.read()
3069 assert setting.startswith('gitdir:')
3070 gitdir = setting.split(':', 1)[1].strip()
3071 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3072
3073 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3074
3075 def GetHead(self):
3076 """Return the ref that HEAD points to."""
3077 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003078 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003079 with open(path) as fd:
3080 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003081 except IOError as e:
3082 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003083 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303084 line = line.decode()
3085 except AttributeError:
3086 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003087 if line.startswith('ref: '):
3088 return line[5:-1]
3089 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003090
3091 def SetHead(self, ref, message=None):
3092 cmdv = []
3093 if message is not None:
3094 cmdv.extend(['-m', message])
3095 cmdv.append(HEAD)
3096 cmdv.append(ref)
3097 self.symbolic_ref(*cmdv)
3098
3099 def DetachHead(self, new, message=None):
3100 cmdv = ['--no-deref']
3101 if message is not None:
3102 cmdv.extend(['-m', message])
3103 cmdv.append(HEAD)
3104 cmdv.append(new)
3105 self.update_ref(*cmdv)
3106
3107 def UpdateRef(self, name, new, old=None,
3108 message=None,
3109 detach=False):
3110 cmdv = []
3111 if message is not None:
3112 cmdv.extend(['-m', message])
3113 if detach:
3114 cmdv.append('--no-deref')
3115 cmdv.append(name)
3116 cmdv.append(new)
3117 if old is not None:
3118 cmdv.append(old)
3119 self.update_ref(*cmdv)
3120
3121 def DeleteRef(self, name, old=None):
3122 if not old:
3123 old = self.rev_parse(name)
3124 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003125 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003126
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003127 def rev_list(self, *args, **kw):
3128 if 'format' in kw:
3129 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3130 else:
3131 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003132 cmdv.extend(args)
3133 p = GitCommand(self._project,
3134 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003135 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003136 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003137 capture_stdout=True,
3138 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003139 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003140 raise GitError('%s rev-list %s: %s' %
3141 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003142 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003143
3144 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003145 """Allow arbitrary git commands using pythonic syntax.
3146
3147 This allows you to do things like:
3148 git_obj.rev_parse('HEAD')
3149
3150 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3151 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003152 Any other positional arguments will be passed to the git command, and the
3153 following keyword arguments are supported:
3154 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003155
3156 Args:
3157 name: The name of the git command to call. Any '_' characters will
3158 be replaced with '-'.
3159
3160 Returns:
3161 A callable object that will try to call git with the named command.
3162 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003163 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003164
Dave Borowitz091f8932012-10-23 17:01:04 -07003165 def runner(*args, **kwargs):
3166 cmdv = []
3167 config = kwargs.pop('config', None)
3168 for k in kwargs:
3169 raise TypeError('%s() got an unexpected keyword argument %r'
3170 % (name, k))
3171 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303172 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003173 cmdv.append('-c')
3174 cmdv.append('%s=%s' % (k, v))
3175 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003176 cmdv.extend(args)
3177 p = GitCommand(self._project,
3178 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003179 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003180 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003181 capture_stdout=True,
3182 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003183 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003184 raise GitError('%s %s: %s' %
3185 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003186 r = p.stdout
3187 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3188 return r[:-1]
3189 return r
3190 return runner
3191
3192
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003193class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003194
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003195 def __str__(self):
3196 return 'prior sync failed; rebase still in progress'
3197
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003198
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003199class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003200
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003201 def __str__(self):
3202 return 'contains uncommitted changes'
3203
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003204
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003205class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003206
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003207 def __init__(self, project, text):
3208 self.project = project
3209 self.text = text
3210
3211 def Print(self, syncbuf):
3212 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3213 syncbuf.out.nl()
3214
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003215
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003216class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003217
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003218 def __init__(self, project, why):
3219 self.project = project
3220 self.why = why
3221
3222 def Print(self, syncbuf):
3223 syncbuf.out.fail('error: %s/: %s',
3224 self.project.relpath,
3225 str(self.why))
3226 syncbuf.out.nl()
3227
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003228
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003229class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003230
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003231 def __init__(self, project, action):
3232 self.project = project
3233 self.action = action
3234
3235 def Run(self, syncbuf):
3236 out = syncbuf.out
3237 out.project('project %s/', self.project.relpath)
3238 out.nl()
3239 try:
3240 self.action()
3241 out.nl()
3242 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003243 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003244 out.nl()
3245 return False
3246
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003247
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003248class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003249
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003250 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003251 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003252 self.project = self.printer('header', attr='bold')
3253 self.info = self.printer('info')
3254 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003255
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003256
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003257class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003258
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003259 def __init__(self, config, detach_head=False):
3260 self._messages = []
3261 self._failures = []
3262 self._later_queue1 = []
3263 self._later_queue2 = []
3264
3265 self.out = _SyncColoring(config)
3266 self.out.redirect(sys.stderr)
3267
3268 self.detach_head = detach_head
3269 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003270 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003271
3272 def info(self, project, fmt, *args):
3273 self._messages.append(_InfoMessage(project, fmt % args))
3274
3275 def fail(self, project, err=None):
3276 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003277 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003278
3279 def later1(self, project, what):
3280 self._later_queue1.append(_Later(project, what))
3281
3282 def later2(self, project, what):
3283 self._later_queue2.append(_Later(project, what))
3284
3285 def Finish(self):
3286 self._PrintMessages()
3287 self._RunLater()
3288 self._PrintMessages()
3289 return self.clean
3290
David Rileye0684ad2017-04-05 00:02:59 -07003291 def Recently(self):
3292 recent_clean = self.recent_clean
3293 self.recent_clean = True
3294 return recent_clean
3295
3296 def _MarkUnclean(self):
3297 self.clean = False
3298 self.recent_clean = False
3299
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003300 def _RunLater(self):
3301 for q in ['_later_queue1', '_later_queue2']:
3302 if not self._RunQueue(q):
3303 return
3304
3305 def _RunQueue(self, queue):
3306 for m in getattr(self, queue):
3307 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003308 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003309 return False
3310 setattr(self, queue, [])
3311 return True
3312
3313 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003314 if self._messages or self._failures:
3315 if os.isatty(2):
3316 self.out.write(progress.CSI_ERASE_LINE)
3317 self.out.write('\r')
3318
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003319 for m in self._messages:
3320 m.Print(self)
3321 for m in self._failures:
3322 m.Print(self)
3323
3324 self._messages = []
3325 self._failures = []
3326
3327
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003328class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003329 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003331 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003332 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003333 manifest=manifest,
3334 name=name,
3335 gitdir=gitdir,
3336 objdir=gitdir,
3337 worktree=worktree,
3338 remote=RemoteSpec('origin'),
3339 relpath='.repo/%s' % name,
3340 revisionExpr='refs/heads/master',
3341 revisionId=None,
3342 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003343
3344 def PreSync(self):
3345 if self.Exists:
3346 cb = self.CurrentBranch
3347 if cb:
3348 base = self.GetBranch(cb).merge
3349 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003350 self.revisionExpr = base
3351 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003352
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003353 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003354 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003355 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003356 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003357 return False
3358
David Pursehouse8a68ff92012-09-24 12:15:13 +09003359 all_refs = self.bare_ref.all
3360 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003361 head = self.work_git.GetHead()
3362 if head.startswith(R_HEADS):
3363 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003364 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003365 except KeyError:
3366 head = None
3367
3368 if revid == head:
3369 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003370 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003371 return True
3372 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003373
3374
3375class RepoProject(MetaProject):
3376 """The MetaProject for repo itself."""
3377
3378 @property
3379 def LastFetch(self):
3380 try:
3381 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3382 return os.path.getmtime(fh)
3383 except OSError:
3384 return 0
3385
3386class ManifestProject(MetaProject):
3387 """The MetaProject for manifests."""
3388
3389 def MetaBranchSwitch(self, submodules=False):
3390 """Prepare for manifest branch switch."""
3391
3392 # detach and delete manifest branch, allowing a new
3393 # branch to take over
3394 syncbuf = SyncBuffer(self.config, detach_head=True)
3395 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3396 syncbuf.Finish()
3397
3398 return GitCommand(self,
3399 ['update-ref', '-d', 'refs/heads/default'],
3400 capture_stdout=True,
3401 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003402
3403 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003404 def standalone_manifest_url(self):
3405 """The URL of the standalone manifest, or None."""
LaMont Jones55ee3042022-04-06 17:10:21 +00003406 return self.config.GetString('manifest.standalone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003407
3408 @property
3409 def manifest_groups(self):
3410 """The manifest groups string."""
3411 return self.config.GetString('manifest.groups')
3412
3413 @property
3414 def reference(self):
3415 """The --reference for this manifest."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003416 return self.config.GetString('repo.reference')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003417
3418 @property
3419 def dissociate(self):
3420 """Whether to dissociate."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003421 return self.config.GetBoolean('repo.dissociate')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003422
3423 @property
3424 def archive(self):
3425 """Whether we use archive."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003426 return self.config.GetBoolean('repo.archive')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003427
3428 @property
3429 def mirror(self):
3430 """Whether we use mirror."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003431 return self.config.GetBoolean('repo.mirror')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003432
3433 @property
3434 def use_worktree(self):
3435 """Whether we use worktree."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003436 return self.config.GetBoolean('repo.worktree')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003437
3438 @property
3439 def clone_bundle(self):
3440 """Whether we use clone_bundle."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003441 return self.config.GetBoolean('repo.clonebundle')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003442
3443 @property
3444 def submodules(self):
3445 """Whether we use submodules."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003446 return self.config.GetBoolean('repo.submodules')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003447
3448 @property
3449 def git_lfs(self):
3450 """Whether we use git_lfs."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003451 return self.config.GetBoolean('repo.git-lfs')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003452
3453 @property
3454 def use_superproject(self):
3455 """Whether we use superproject."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003456 return self.config.GetBoolean('repo.superproject')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003457
3458 @property
3459 def partial_clone(self):
3460 """Whether this is a partial clone."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003461 return self.config.GetBoolean('repo.partialclone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003462
3463 @property
3464 def depth(self):
3465 """Partial clone depth."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003466 return self.config.GetString('repo.depth')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003467
3468 @property
3469 def clone_filter(self):
3470 """The clone filter."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003471 return self.config.GetString('repo.clonefilter')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003472
3473 @property
3474 def partial_clone_exclude(self):
3475 """Partial clone exclude string"""
LaMont Jones4ada0432022-04-14 15:10:43 +00003476 return self.config.GetBoolean('repo.partialcloneexclude')
3477
3478 @property
3479 def manifest_platform(self):
3480 """The --platform argument from `repo init`."""
3481 return self.config.GetString('manifest.platform')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003482
3483 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003484 def _platform_name(self):
3485 """Return the name of the platform."""
3486 return platform.system().lower()
3487
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003488 def SyncWithPossibleInit(self, submanifest, verbose=False,
3489 current_branch_only=False, tags='', git_event_log=None):
3490 """Sync a manifestProject, possibly for the first time.
3491
3492 Call Sync() with arguments from the most recent `repo init`. If this is a
3493 new sub manifest, then inherit options from the parent's manifestProject.
3494
3495 This is used by subcmds.Sync() to do an initial download of new sub
3496 manifests.
3497
3498 Args:
3499 submanifest: an XmlSubmanifest, the submanifest to re-sync.
3500 verbose: a boolean, whether to show all output, rather than only errors.
3501 current_branch_only: a boolean, whether to only fetch the current manifest
3502 branch from the server.
3503 tags: a boolean, whether to fetch tags.
3504 git_event_log: an EventLog, for git tracing.
3505 """
3506 # TODO(lamontjones): when refactoring sync (and init?) consider how to
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003507 # better get the init options that we should use for new submanifests that
3508 # are added when syncing an existing workspace.
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003509 git_event_log = git_event_log or EventLog()
3510 spec = submanifest.ToSubmanifestSpec()
3511 # Use the init options from the existing manifestProject, or the parent if
3512 # it doesn't exist.
3513 #
3514 # Today, we only support changing manifest_groups on the sub-manifest, with
3515 # no supported-for-the-user way to change the other arguments from those
3516 # specified by the outermost manifest.
3517 #
3518 # TODO(lamontjones): determine which of these should come from the outermost
3519 # manifest and which should come from the parent manifest.
3520 mp = self if self.Exists else submanifest.parent.manifestProject
3521 return self.Sync(
3522 manifest_url=spec.manifestUrl,
3523 manifest_branch=spec.revision,
3524 standalone_manifest=mp.standalone_manifest_url,
3525 groups=mp.manifest_groups,
3526 platform=mp.manifest_platform,
3527 mirror=mp.mirror,
3528 dissociate=mp.dissociate,
3529 reference=mp.reference,
3530 worktree=mp.use_worktree,
3531 submodules=mp.submodules,
3532 archive=mp.archive,
3533 partial_clone=mp.partial_clone,
3534 clone_filter=mp.clone_filter,
3535 partial_clone_exclude=mp.partial_clone_exclude,
3536 clone_bundle=mp.clone_bundle,
3537 git_lfs=mp.git_lfs,
3538 use_superproject=mp.use_superproject,
3539 verbose=verbose,
3540 current_branch_only=current_branch_only,
3541 tags=tags,
3542 depth=mp.depth,
3543 git_event_log=git_event_log,
3544 manifest_name=spec.manifestName,
3545 this_manifest_only=True,
3546 outer_manifest=False,
3547 )
3548
LaMont Jones9b03f152022-03-29 23:01:18 +00003549 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003550 standalone_manifest=False, groups='', mirror=False, reference='',
3551 dissociate=False, worktree=False, submodules=False, archive=False,
3552 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003553 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3554 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jones55ee3042022-04-06 17:10:21 +00003555 git_event_log=None, platform='', manifest_name='default.xml',
3556 tags='', this_manifest_only=False, outer_manifest=True):
LaMont Jones9b03f152022-03-29 23:01:18 +00003557 """Sync the manifest and all submanifests.
3558
3559 Args:
3560 manifest_url: a string, the URL of the manifest project.
3561 manifest_branch: a string, the manifest branch to use.
3562 standalone_manifest: a boolean, whether to store the manifest as a static
3563 file.
3564 groups: a string, restricts the checkout to projects with the specified
3565 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003566 mirror: a boolean, whether to create a mirror of the remote repository.
3567 reference: a string, location of a repo instance to use as a reference.
3568 dissociate: a boolean, whether to dissociate from reference mirrors after
3569 clone.
3570 worktree: a boolean, whether to use git-worktree to manage projects.
3571 submodules: a boolean, whether sync submodules associated with the
3572 manifest project.
3573 archive: a boolean, whether to checkout each project as an archive. See
3574 git-archive.
3575 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003576 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003577 clone_filter: a string, filter to use with partial_clone.
3578 partial_clone_exclude : a string, comma-delimeted list of project namess
3579 to exclude from partial clone.
3580 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3581 git_lfs: a boolean, whether to enable git LFS support.
3582 use_superproject: a boolean, whether to use the manifest superproject to
3583 sync projects.
3584 verbose: a boolean, whether to show all output, rather than only errors.
3585 current_branch_only: a boolean, whether to only fetch the current manifest
3586 branch from the server.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003587 platform: a string, restrict the checkout to projects with the specified
3588 platform group.
LaMont Jones55ee3042022-04-06 17:10:21 +00003589 git_event_log: an EventLog, for git tracing.
LaMont Jones4ada0432022-04-14 15:10:43 +00003590 tags: a boolean, whether to fetch tags.
LaMont Jones409407a2022-04-05 21:21:56 +00003591 manifest_name: a string, the name of the manifest file to use.
3592 this_manifest_only: a boolean, whether to only operate on the current sub
3593 manifest.
3594 outer_manifest: a boolean, whether to start at the outermost manifest.
LaMont Jones9b03f152022-03-29 23:01:18 +00003595
3596 Returns:
3597 a boolean, whether the sync was successful.
3598 """
3599 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3600
LaMont Jones501733c2022-04-20 16:42:32 +00003601 groups = groups or self.manifest.GetDefaultGroupsStr(with_platform=False)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003602 platform = platform or 'auto'
LaMont Jones55ee3042022-04-06 17:10:21 +00003603 git_event_log = git_event_log or EventLog()
LaMont Jones409407a2022-04-05 21:21:56 +00003604 if outer_manifest and self.manifest.is_submanifest:
3605 # In a multi-manifest checkout, use the outer manifest unless we are told
3606 # not to.
3607 return self.client.outer_manifest.manifestProject.Sync(
3608 manifest_url=manifest_url,
3609 manifest_branch=manifest_branch,
3610 standalone_manifest=standalone_manifest,
3611 groups=groups,
3612 platform=platform,
3613 mirror=mirror,
3614 dissociate=dissociate,
3615 reference=reference,
3616 worktree=worktree,
3617 submodules=submodules,
3618 archive=archive,
3619 partial_clone=partial_clone,
3620 clone_filter=clone_filter,
3621 partial_clone_exclude=partial_clone_exclude,
3622 clone_bundle=clone_bundle,
3623 git_lfs=git_lfs,
3624 use_superproject=use_superproject,
3625 verbose=verbose,
3626 current_branch_only=current_branch_only,
3627 tags=tags,
3628 depth=depth,
LaMont Jones55ee3042022-04-06 17:10:21 +00003629 git_event_log=git_event_log,
LaMont Jones409407a2022-04-05 21:21:56 +00003630 manifest_name=manifest_name,
3631 this_manifest_only=this_manifest_only,
3632 outer_manifest=False)
3633
LaMont Jones9b03f152022-03-29 23:01:18 +00003634 # If repo has already been initialized, we take -u with the absence of
3635 # --standalone-manifest to mean "transition to a standard repo set up",
3636 # which necessitates starting fresh.
3637 # If --standalone-manifest is set, we always tear everything down and start
3638 # anew.
3639 if self.Exists:
3640 was_standalone_manifest = self.config.GetString('manifest.standalone')
3641 if was_standalone_manifest and not manifest_url:
3642 print('fatal: repo was initialized with a standlone manifest, '
3643 'cannot be re-initialized without --manifest-url/-u')
3644 return False
3645
3646 if standalone_manifest or (was_standalone_manifest and manifest_url):
3647 self.config.ClearCache()
3648 if self.gitdir and os.path.exists(self.gitdir):
3649 platform_utils.rmtree(self.gitdir)
3650 if self.worktree and os.path.exists(self.worktree):
3651 platform_utils.rmtree(self.worktree)
3652
3653 is_new = not self.Exists
3654 if is_new:
3655 if not manifest_url:
3656 print('fatal: manifest url is required.', file=sys.stderr)
3657 return False
3658
LaMont Jones409407a2022-04-05 21:21:56 +00003659 if verbose:
LaMont Jones9b03f152022-03-29 23:01:18 +00003660 print('Downloading manifest from %s' %
3661 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3662 file=sys.stderr)
3663
3664 # The manifest project object doesn't keep track of the path on the
3665 # server where this git is located, so let's save that here.
3666 mirrored_manifest_git = None
3667 if reference:
3668 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3669 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3670 if not mirrored_manifest_git.endswith(".git"):
3671 mirrored_manifest_git += ".git"
3672 if not os.path.exists(mirrored_manifest_git):
3673 mirrored_manifest_git = os.path.join(reference,
3674 '.repo/manifests.git')
3675
3676 self._InitGitDir(mirror_git=mirrored_manifest_git)
3677
3678 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3679 # still do much of the manifests.git set up, but will avoid actual syncs to
3680 # a remote.
3681 if standalone_manifest:
3682 self.config.SetString('manifest.standalone', manifest_url)
3683 elif not manifest_url and not manifest_branch:
3684 # If -u is set and --standalone-manifest is not, then we're not in
3685 # standalone mode. Otherwise, use config to infer what we were in the last
3686 # init.
3687 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3688 if not standalone_manifest:
3689 self.config.SetString('manifest.standalone', None)
3690
3691 self._ConfigureDepth(depth)
3692
3693 # Set the remote URL before the remote branch as we might need it below.
3694 if manifest_url:
3695 r = self.GetRemote(self.remote.name)
3696 r.url = manifest_url
3697 r.ResetFetch()
3698 r.Save()
3699
3700 if not standalone_manifest:
3701 if manifest_branch:
3702 if manifest_branch == 'HEAD':
3703 manifest_branch = self.ResolveRemoteHead()
3704 if manifest_branch is None:
3705 print('fatal: unable to resolve HEAD', file=sys.stderr)
3706 return False
3707 self.revisionExpr = manifest_branch
3708 else:
3709 if is_new:
3710 default_branch = self.ResolveRemoteHead()
3711 if default_branch is None:
3712 # If the remote doesn't have HEAD configured, default to master.
3713 default_branch = 'refs/heads/master'
3714 self.revisionExpr = default_branch
3715 else:
3716 self.PreSync()
3717
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003718 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003719 all_platforms = ['linux', 'darwin', 'windows']
3720 platformize = lambda x: 'platform-' + x
3721 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003722 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003723 groups.append(platformize(self._platform_name))
3724 elif platform == 'all':
3725 groups.extend(map(platformize, all_platforms))
3726 elif platform in all_platforms:
3727 groups.append(platformize(platform))
3728 elif platform != 'none':
3729 print('fatal: invalid platform flag', file=sys.stderr)
3730 return False
LaMont Jones4ada0432022-04-14 15:10:43 +00003731 self.config.SetString('manifest.platform', platform)
LaMont Jones9b03f152022-03-29 23:01:18 +00003732
3733 groups = [x for x in groups if x]
3734 groupstr = ','.join(groups)
3735 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3736 groupstr = None
3737 self.config.SetString('manifest.groups', groupstr)
3738
3739 if reference:
3740 self.config.SetString('repo.reference', reference)
3741
3742 if dissociate:
3743 self.config.SetBoolean('repo.dissociate', dissociate)
3744
3745 if worktree:
3746 if mirror:
3747 print('fatal: --mirror and --worktree are incompatible',
3748 file=sys.stderr)
3749 return False
3750 if submodules:
3751 print('fatal: --submodules and --worktree are incompatible',
3752 file=sys.stderr)
3753 return False
3754 self.config.SetBoolean('repo.worktree', worktree)
3755 if is_new:
3756 self.use_git_worktrees = True
3757 print('warning: --worktree is experimental!', file=sys.stderr)
3758
3759 if archive:
3760 if is_new:
3761 self.config.SetBoolean('repo.archive', archive)
3762 else:
3763 print('fatal: --archive is only supported when initializing a new '
3764 'workspace.', file=sys.stderr)
3765 print('Either delete the .repo folder in this workspace, or initialize '
3766 'in another location.', file=sys.stderr)
3767 return False
3768
3769 if mirror:
3770 if is_new:
3771 self.config.SetBoolean('repo.mirror', mirror)
3772 else:
3773 print('fatal: --mirror is only supported when initializing a new '
3774 'workspace.', file=sys.stderr)
3775 print('Either delete the .repo folder in this workspace, or initialize '
3776 'in another location.', file=sys.stderr)
3777 return False
3778
3779 if partial_clone is not None:
3780 if mirror:
3781 print('fatal: --mirror and --partial-clone are mutually exclusive',
3782 file=sys.stderr)
3783 return False
3784 self.config.SetBoolean('repo.partialclone', partial_clone)
3785 if clone_filter:
3786 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jones55ee3042022-04-06 17:10:21 +00003787 elif self.partial_clone:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003788 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003789 else:
3790 clone_filter = None
3791
3792 if partial_clone_exclude is not None:
3793 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3794
3795 if clone_bundle is None:
3796 clone_bundle = False if partial_clone else True
3797 else:
3798 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3799
3800 if submodules:
3801 self.config.SetBoolean('repo.submodules', submodules)
3802
3803 if git_lfs is not None:
3804 if git_lfs:
3805 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3806
3807 self.config.SetBoolean('repo.git-lfs', git_lfs)
3808 if not is_new:
3809 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3810 ' Existing projects will require manual updates.\n', file=sys.stderr)
3811
3812 if use_superproject is not None:
3813 self.config.SetBoolean('repo.superproject', use_superproject)
3814
LaMont Jones0165e202022-04-27 17:34:42 +00003815 if not standalone_manifest:
3816 if not self.Sync_NetworkHalf(
3817 is_new=is_new, quiet=not verbose, verbose=verbose,
3818 clone_bundle=clone_bundle, current_branch_only=current_branch_only,
3819 tags=tags, submodules=submodules, clone_filter=clone_filter,
3820 partial_clone_exclude=self.manifest.PartialCloneExclude):
3821 r = self.GetRemote(self.remote.name)
3822 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
LaMont Jones9b03f152022-03-29 23:01:18 +00003823
LaMont Jones0165e202022-04-27 17:34:42 +00003824 # Better delete the manifest git dir if we created it; otherwise next
3825 # time (when user fixes problems) we won't go through the "is_new" logic.
3826 if is_new:
3827 platform_utils.rmtree(self.gitdir)
LaMont Jones9b03f152022-03-29 23:01:18 +00003828 return False
3829
LaMont Jones0165e202022-04-27 17:34:42 +00003830 if manifest_branch:
3831 self.MetaBranchSwitch(submodules=submodules)
3832
3833 syncbuf = SyncBuffer(self.config)
3834 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3835 syncbuf.Finish()
3836
3837 if is_new or self.CurrentBranch is None:
3838 if not self.StartBranch('default'):
3839 print('fatal: cannot create default in manifest', file=sys.stderr)
3840 return False
3841
3842 if not manifest_name:
3843 print('fatal: manifest name (-m) is required.', file=sys.stderr)
3844 return False
3845
3846 elif is_new:
3847 # This is a new standalone manifest.
3848 manifest_name = 'default.xml'
3849 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3850 dest = os.path.join(self.worktree, manifest_name)
3851 os.makedirs(os.path.dirname(dest), exist_ok=True)
3852 with open(dest, 'wb') as f:
3853 f.write(manifest_data)
LaMont Jones409407a2022-04-05 21:21:56 +00003854
3855 try:
3856 self.manifest.Link(manifest_name)
3857 except ManifestParseError as e:
3858 print("fatal: manifest '%s' not available" % manifest_name,
3859 file=sys.stderr)
3860 print('fatal: %s' % str(e), file=sys.stderr)
3861 return False
3862
LaMont Jones55ee3042022-04-06 17:10:21 +00003863 if not this_manifest_only:
3864 for submanifest in self.manifest.submanifests.values():
LaMont Jonesb90a4222022-04-14 15:00:09 +00003865 spec = submanifest.ToSubmanifestSpec()
LaMont Jones55ee3042022-04-06 17:10:21 +00003866 submanifest.repo_client.manifestProject.Sync(
3867 manifest_url=spec.manifestUrl,
3868 manifest_branch=spec.revision,
3869 standalone_manifest=standalone_manifest,
3870 groups=self.manifest_groups,
3871 platform=platform,
3872 mirror=mirror,
3873 dissociate=dissociate,
3874 reference=reference,
3875 worktree=worktree,
3876 submodules=submodules,
3877 archive=archive,
3878 partial_clone=partial_clone,
3879 clone_filter=clone_filter,
3880 partial_clone_exclude=partial_clone_exclude,
3881 clone_bundle=clone_bundle,
3882 git_lfs=git_lfs,
3883 use_superproject=use_superproject,
3884 verbose=verbose,
3885 current_branch_only=current_branch_only,
3886 tags=tags,
3887 depth=depth,
3888 git_event_log=git_event_log,
3889 manifest_name=spec.manifestName,
3890 this_manifest_only=False,
3891 outer_manifest=False,
3892 )
LaMont Jones409407a2022-04-05 21:21:56 +00003893
LaMont Jones0ddb6772022-05-20 09:11:54 +00003894 # Lastly, if the manifest has a <superproject> then have the superproject
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003895 # sync it (if it will be used).
3896 if git_superproject.UseSuperproject(use_superproject, self.manifest):
LaMont Jones0ddb6772022-05-20 09:11:54 +00003897 sync_result = self.manifest.superproject.Sync(git_event_log)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003898 if not sync_result.success:
3899 print('warning: git update of superproject for '
3900 f'{self.manifest.path_prefix} failed, repo sync will not use '
3901 'superproject to fetch source; while this error is not fatal, '
3902 'and you can continue to run repo sync, please run repo init '
3903 'with the --no-use-superproject option to stop seeing this '
3904 'warning', file=sys.stderr)
3905 if sync_result.fatal and use_superproject is not None:
3906 return False
LaMont Jones409407a2022-04-05 21:21:56 +00003907
LaMont Jones9b03f152022-03-29 23:01:18 +00003908 return True
3909
3910 def _ConfigureDepth(self, depth):
3911 """Configure the depth we'll sync down.
3912
3913 Args:
3914 depth: an int, how deep of a partial clone to create.
3915 """
3916 # Opt.depth will be non-None if user actually passed --depth to repo init.
3917 if depth is not None:
3918 if depth > 0:
3919 # Positive values will set the depth.
3920 depth = str(depth)
3921 else:
3922 # Negative numbers will clear the depth; passing None to SetString
3923 # will do that.
3924 depth = None
3925
3926 # We store the depth in the main manifest project.
3927 self.config.SetString('repo.depth', depth)