blob: 8274f0221c0a90cf7d52db49474c7cb3498929bd [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 """
795 if self.UncommitedFiles(get_all=False):
796 return True
797 else:
798 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500799
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600800 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700801 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200802
803 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200804 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600805 quiet: If True then only print the project name. Do not print
806 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700808 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700809 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200810 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700811 print(file=output_redir)
812 print('project %s/' % self.relpath, file=output_redir)
813 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700814 return
815
816 self.work_git.update_index('-q',
817 '--unmerged',
818 '--ignore-missing',
819 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700820 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700821 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
822 df = self.work_git.DiffZ('diff-files')
823 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100824 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700825 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700826
827 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700828 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200829 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700830 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700831
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600832 if quiet:
833 out.nl()
834 return 'DIRTY'
835
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 branch = self.CurrentBranch
837 if branch is None:
838 out.nobranch('(*** NO BRANCH ***)')
839 else:
840 out.branch('branch %s', branch)
841 out.nl()
842
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700843 if rb:
844 out.important('prior sync failed; rebase still in progress')
845 out.nl()
846
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700847 paths = list()
848 paths.extend(di.keys())
849 paths.extend(df.keys())
850 paths.extend(do)
851
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530852 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900853 try:
854 i = di[p]
855 except KeyError:
856 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900858 try:
859 f = df[p]
860 except KeyError:
861 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200862
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900863 if i:
864 i_status = i.status.upper()
865 else:
866 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900868 if f:
869 f_status = f.status.lower()
870 else:
871 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872
873 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800874 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700875 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700876 else:
877 line = ' %s%s\t%s' % (i_status, f_status, p)
878
879 if i and not f:
880 out.added('%s', line)
881 elif (i and f) or (not i and f):
882 out.changed('%s', line)
883 elif not i and not f:
884 out.untracked('%s', line)
885 else:
886 out.write('%s', line)
887 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200888
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700889 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500891 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892 """Prints the status of the repository to stdout.
893 """
894 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500895 if output_redir:
896 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700897 cmd = ['diff']
898 if out.is_on:
899 cmd.append('--color')
900 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300901 if absolute_paths:
902 cmd.append('--src-prefix=a/%s/' % self.relpath)
903 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400905 try:
906 p = GitCommand(self,
907 cmd,
908 capture_stdout=True,
909 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500910 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400911 except GitError as e:
912 out.nl()
913 out.project('project %s/' % self.relpath)
914 out.nl()
915 out.fail('%s', str(e))
916 out.nl()
917 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500918 if p.stdout:
919 out.nl()
920 out.project('project %s/' % self.relpath)
921 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500922 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400923 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700925# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900926 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 """Was the branch published (uploaded) for code review?
928 If so, returns the SHA-1 hash of the last published
929 state for the branch.
930 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700931 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900932 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700933 try:
934 return self.bare_git.rev_parse(key)
935 except GitError:
936 return None
937 else:
938 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900939 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700940 except KeyError:
941 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942
David Pursehouse8a68ff92012-09-24 12:15:13 +0900943 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944 """Prunes any stale published refs.
945 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900946 if all_refs is None:
947 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 heads = set()
949 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530950 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951 if name.startswith(R_HEADS):
952 heads.add(name)
953 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900954 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700955
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530956 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957 n = name[len(R_PUB):]
958 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900959 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700960
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700961 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 """List any branches which can be uploaded for review.
963 """
964 heads = {}
965 pubed = {}
966
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530967 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900969 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900971 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972
973 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530974 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900975 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700976 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700977 if selected_branch and branch != selected_branch:
978 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800980 rb = self.GetUploadableBranch(branch)
981 if rb:
982 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 return ready
984
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800985 def GetUploadableBranch(self, branch_name):
986 """Get a single uploadable branch, or None.
987 """
988 branch = self.GetBranch(branch_name)
989 base = branch.LocalMerge
990 if branch.LocalMerge:
991 rb = ReviewableBranch(self, branch, base)
992 if rb.commits:
993 return rb
994 return None
995
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700996 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100997 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500998 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700999 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001000 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001001 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +02001002 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001003 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001004 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001005 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001006 validate_certs=True,
1007 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008 """Uploads the named branch for code review.
1009 """
1010 if branch is None:
1011 branch = self.CurrentBranch
1012 if branch is None:
1013 raise GitError('not currently on a branch')
1014
1015 branch = self.GetBranch(branch)
1016 if not branch.LocalMerge:
1017 raise GitError('branch %s does not track a remote' % branch.name)
1018 if not branch.remote.review:
1019 raise GitError('remote %s has no review url' % branch.remote.name)
1020
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001021 # Basic validity check on label syntax.
1022 for label in labels:
1023 if not re.match(r'^.+[+-][0-9]+$', label):
1024 raise UploadError(
1025 f'invalid label syntax "{label}": labels use forms like '
1026 'CodeReview+1 or Verified-1')
1027
Bryan Jacobsf609f912013-05-06 13:36:24 -04001028 if dest_branch is None:
1029 dest_branch = self.dest_branch
1030 if dest_branch is None:
1031 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001032 if not dest_branch.startswith(R_HEADS):
1033 dest_branch = R_HEADS + dest_branch
1034
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001035 if not branch.remote.projectname:
1036 branch.remote.projectname = self.name
1037 branch.remote.Save()
1038
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001039 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001040 if url is None:
1041 raise UploadError('review not configured')
1042 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001043 if dryrun:
1044 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001045
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001046 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001047 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001048
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001049 for push_option in (push_options or []):
1050 cmd.append('-o')
1051 cmd.append(push_option)
1052
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001053 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001054
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001055 if dest_branch.startswith(R_HEADS):
1056 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001057
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001058 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001059 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001060 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001061 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001062 opts += ['t=%s' % p for p in hashtags]
Mike Frysinger3a0a1452022-05-20 12:52:33 -04001063 # NB: No need to encode labels as they've been validated above.
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001064 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001065
David Pursehousef25a3702018-11-14 19:01:22 -08001066 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001067 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001068 if notify:
1069 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001070 if private:
1071 opts += ['private']
1072 if wip:
1073 opts += ['wip']
1074 if opts:
1075 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001076 cmd.append(ref_spec)
1077
Anthony King7bdac712014-07-16 12:56:40 +01001078 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001079 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080
Mike Frysingerd7f86832020-11-19 19:18:46 -05001081 if not dryrun:
1082 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1083 self.bare_git.UpdateRef(R_PUB + branch.name,
1084 R_HEADS + branch.name,
1085 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001087# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001088 def _ExtractArchive(self, tarpath, path=None):
1089 """Extract the given tar on its current location
1090
1091 Args:
1092 - tarpath: The path to the actual tar file
1093
1094 """
1095 try:
1096 with tarfile.open(tarpath, 'r') as tar:
1097 tar.extractall(path=path)
1098 return True
1099 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001100 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001101 return False
1102
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001103 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001104 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001105 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001106 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001107 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001108 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001109 force_sync=False,
1110 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001111 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001112 archive=False,
1113 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001114 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001115 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001116 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001117 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001118 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001119 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001120 """Perform only the network IO portion of the sync process.
1121 Local working directory/branch state is not affected.
1122 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001123 if archive and not isinstance(self, MetaProject):
1124 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001125 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001126 return False
1127
1128 name = self.relpath.replace('\\', '/')
1129 name = name.replace('/', '_')
1130 tarpath = '%s.tar' % name
1131 topdir = self.manifest.topdir
1132
1133 try:
1134 self._FetchArchive(tarpath, cwd=topdir)
1135 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001136 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001137 return False
1138
1139 # From now on, we only need absolute tarpath
1140 tarpath = os.path.join(topdir, tarpath)
1141
1142 if not self._ExtractArchive(tarpath, path=topdir):
1143 return False
1144 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001145 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001146 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001147 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001148 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001149 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001150
1151 # If the shared object dir already exists, don't try to rebootstrap with a
1152 # clone bundle download. We should have the majority of objects already.
1153 if clone_bundle and os.path.exists(self.objdir):
1154 clone_bundle = False
1155
Raman Tennetif32f2432021-04-12 20:57:25 -07001156 if self.name in partial_clone_exclude:
1157 clone_bundle = True
1158 clone_filter = None
1159
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001160 if is_new is None:
1161 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001162 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001163 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001164 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001165 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001166 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001167
LaMont Jones68d69632022-06-07 18:24:20 +00001168 if self.UseAlternates:
Mike Frysinger1d00a7e2021-12-21 00:40:31 -05001169 # If gitdir/objects is a symlink, migrate it from the old layout.
1170 gitdir_objects = os.path.join(self.gitdir, 'objects')
1171 if platform_utils.islink(gitdir_objects):
1172 platform_utils.remove(gitdir_objects, missing_ok=True)
1173 gitdir_alt = os.path.join(self.gitdir, 'objects/info/alternates')
1174 if not os.path.exists(gitdir_alt):
1175 os.makedirs(os.path.dirname(gitdir_alt), exist_ok=True)
1176 _lwrite(gitdir_alt, os.path.join(
1177 os.path.relpath(self.objdir, gitdir_objects), 'objects') + '\n')
1178
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001179 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001180 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001181 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001182 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001183 # This works for both absolute and relative alternate directories.
1184 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001185 except IOError:
1186 alt_dir = None
1187 else:
1188 alt_dir = None
1189
Mike Frysingere50b6a72020-02-19 01:45:48 -05001190 if (clone_bundle
1191 and alt_dir is None
1192 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001193 is_new = False
1194
Mike Frysinger73561142021-05-03 01:10:09 -04001195 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001196 if self.sync_c:
1197 current_branch_only = True
1198 elif not self.manifest._loaded:
1199 # Manifest cannot check defaults until it syncs.
1200 current_branch_only = False
1201 elif self.manifest.default.sync_c:
1202 current_branch_only = True
1203
Mike Frysingerd68ed632021-05-03 01:21:35 -04001204 if tags is None:
1205 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001206
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001207 if self.clone_depth:
1208 depth = self.clone_depth
1209 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001210 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001211
Mike Frysinger521d01b2020-02-17 01:51:49 -05001212 # See if we can skip the network fetch entirely.
1213 if not (optimized_fetch and
1214 (ID_RE.match(self.revisionExpr) and
1215 self._CheckForImmutableRevision())):
1216 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001217 initial=is_new,
1218 quiet=quiet, verbose=verbose, output_redir=output_redir,
1219 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001220 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001221 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001222 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001223 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001224 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001225
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001226 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001227 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001228 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001229 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001230 if os.path.exists(alternates_file):
1231 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001232 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1233 merge_output=bool(output_redir))
1234 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001235 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001236 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001237 return False
1238 platform_utils.remove(alternates_file)
1239
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001240 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001241 self._InitMRef()
1242 else:
1243 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001244 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1245 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001246 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001247
1248 def PostRepoUpgrade(self):
1249 self._InitHooks()
1250
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001251 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001252 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001253 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001254 for copyfile in self.copyfiles:
1255 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001256 for linkfile in self.linkfiles:
1257 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258
Julien Camperguedd654222014-01-09 16:21:37 +01001259 def GetCommitRevisionId(self):
1260 """Get revisionId of a commit.
1261
1262 Use this method instead of GetRevisionId to get the id of the commit rather
1263 than the id of the current git object (for example, a tag)
1264
1265 """
1266 if not self.revisionExpr.startswith(R_TAGS):
1267 return self.GetRevisionId(self._allrefs)
1268
1269 try:
1270 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1271 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001272 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1273 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001274
David Pursehouse8a68ff92012-09-24 12:15:13 +09001275 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001276 if self.revisionId:
1277 return self.revisionId
1278
1279 rem = self.GetRemote(self.remote.name)
1280 rev = rem.ToLocal(self.revisionExpr)
1281
David Pursehouse8a68ff92012-09-24 12:15:13 +09001282 if all_refs is not None and rev in all_refs:
1283 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001284
1285 try:
1286 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1287 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001288 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1289 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001290
Raman Tenneti6a872c92021-01-14 19:17:50 -08001291 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001292 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001293 self.upstream = self.revisionExpr
1294
Raman Tenneti6a872c92021-01-14 19:17:50 -08001295 self.revisionId = revisionId
1296
Martin Kellye4e94d22017-03-21 16:05:12 -07001297 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298 """Perform only the local IO portion of the sync process.
1299 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001301 if not os.path.exists(self.gitdir):
1302 syncbuf.fail(self,
1303 'Cannot checkout %s due to missing network sync; Run '
1304 '`repo sync -n %s` first.' %
1305 (self.name, self.name))
1306 return
1307
Martin Kellye4e94d22017-03-21 16:05:12 -07001308 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001309 all_refs = self.bare_ref.all
1310 self.CleanPublishedCache(all_refs)
1311 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001312
Mike Frysinger0458faa2021-03-10 23:35:44 -05001313 # Special case the root of the repo client checkout. Make sure it doesn't
1314 # contain files being checked out to dirs we don't allow.
1315 if self.relpath == '.':
1316 PROTECTED_PATHS = {'.repo'}
1317 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1318 bad_paths = paths & PROTECTED_PATHS
1319 if bad_paths:
1320 syncbuf.fail(self,
1321 'Refusing to checkout project that writes to protected '
1322 'paths: %s' % (', '.join(bad_paths),))
1323 return
1324
David Pursehouse1d947b32012-10-25 12:23:11 +09001325 def _doff():
1326 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001327 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001328
Martin Kellye4e94d22017-03-21 16:05:12 -07001329 def _dosubmodules():
1330 self._SyncSubmodules(quiet=True)
1331
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001332 head = self.work_git.GetHead()
1333 if head.startswith(R_HEADS):
1334 branch = head[len(R_HEADS):]
1335 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001336 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001337 except KeyError:
1338 head = None
1339 else:
1340 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001341
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001342 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001343 # Currently on a detached HEAD. The user is assumed to
1344 # not have any local modifications worth worrying about.
1345 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001346 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001347 syncbuf.fail(self, _PriorSyncFailedError())
1348 return
1349
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001350 if head == revid:
1351 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001352 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001353 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001354 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001355 # The copy/linkfile config may have changed.
1356 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001357 return
1358 else:
1359 lost = self._revlist(not_rev(revid), HEAD)
1360 if lost:
1361 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001362
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001364 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001365 if submodules:
1366 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001367 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001368 syncbuf.fail(self, e)
1369 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001370 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001371 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001372
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001373 if head == revid:
1374 # No changes; don't do anything further.
1375 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001376 # The copy/linkfile config may have changed.
1377 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001378 return
1379
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001380 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001382 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001383 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001384 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001386 syncbuf.info(self,
1387 "leaving %s; does not track upstream",
1388 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001390 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001391 if submodules:
1392 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001393 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001394 syncbuf.fail(self, e)
1395 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001396 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001397 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001399 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001400
1401 # See if we can perform a fast forward merge. This can happen if our
1402 # branch isn't in the exact same state as we last published.
1403 try:
1404 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1405 # Skip the published logic.
1406 pub = False
1407 except GitError:
1408 pub = self.WasPublished(branch.name, all_refs)
1409
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001410 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001411 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001412 if not_merged:
1413 if upstream_gain:
1414 # The user has published this branch and some of those
1415 # commits are not yet merged upstream. We do not want
1416 # to rewrite the published commits so we punt.
1417 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001418 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001419 "branch %s is published (but not merged) and is now "
1420 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001421 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001422 elif pub == head:
1423 # All published commits are merged, and thus we are a
1424 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001425 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001426 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001427 if submodules:
1428 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001429 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001430
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001431 # Examine the local commits not in the remote. Find the
1432 # last one attributed to this user, if any.
1433 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001434 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001435 last_mine = None
1436 cnt_mine = 0
1437 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001438 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001439 if committer_email == self.UserEmail:
1440 last_mine = commit_id
1441 cnt_mine += 1
1442
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001443 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001444 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001445
1446 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001447 syncbuf.fail(self, _DirtyError())
1448 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001449
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001450 # If the upstream switched on us, warn the user.
1451 #
1452 if branch.merge != self.revisionExpr:
1453 if branch.merge and self.revisionExpr:
1454 syncbuf.info(self,
1455 'manifest switched %s...%s',
1456 branch.merge,
1457 self.revisionExpr)
1458 elif branch.merge:
1459 syncbuf.info(self,
1460 'manifest no longer tracks %s',
1461 branch.merge)
1462
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001463 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001464 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001465 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001466 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001467 syncbuf.info(self,
1468 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001469 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001470
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001471 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001472 if not ID_RE.match(self.revisionExpr):
1473 # in case of manifest sync the revisionExpr might be a SHA1
1474 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001475 if not branch.merge.startswith('refs/'):
1476 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001477 branch.Save()
1478
Mike Pontillod3153822012-02-28 11:53:24 -08001479 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001480 def _docopyandlink():
1481 self._CopyAndLinkFiles()
1482
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001483 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001484 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001485 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001486 if submodules:
1487 syncbuf.later2(self, _dosubmodules)
1488 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001489 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001490 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001491 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001492 if submodules:
1493 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001494 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001495 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001496 syncbuf.fail(self, e)
1497 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001498 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001499 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001500 if submodules:
1501 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001502
Mike Frysingere6a202f2019-08-02 15:57:57 -04001503 def AddCopyFile(self, src, dest, topdir):
1504 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001505
Mike Frysingere6a202f2019-08-02 15:57:57 -04001506 No filesystem changes occur here. Actual copying happens later on.
1507
1508 Paths should have basic validation run on them before being queued.
1509 Further checking will be handled when the actual copy happens.
1510 """
1511 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1512
1513 def AddLinkFile(self, src, dest, topdir):
1514 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1515
1516 No filesystem changes occur here. Actual linking happens later on.
1517
1518 Paths should have basic validation run on them before being queued.
1519 Further checking will be handled when the actual link happens.
1520 """
1521 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001522
James W. Mills24c13082012-04-12 15:04:13 -05001523 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001524 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001525
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001526 def DownloadPatchSet(self, change_id, patch_id):
1527 """Download a single patch set of a single change to FETCH_HEAD.
1528 """
1529 remote = self.GetRemote(self.remote.name)
1530
1531 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001532 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001533 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001534 if GitCommand(self, cmd, bare=True).Wait() != 0:
1535 return None
1536 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001537 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001538 change_id,
1539 patch_id,
1540 self.bare_git.rev_parse('FETCH_HEAD'))
1541
Mike Frysingerc0d18662020-02-19 19:19:18 -05001542 def DeleteWorktree(self, quiet=False, force=False):
1543 """Delete the source checkout and any other housekeeping tasks.
1544
1545 This currently leaves behind the internal .repo/ cache state. This helps
1546 when switching branches or manifest changes get reverted as we don't have
1547 to redownload all the git objects. But we should do some GC at some point.
1548
1549 Args:
1550 quiet: Whether to hide normal messages.
1551 force: Always delete tree even if dirty.
1552
1553 Returns:
1554 True if the worktree was completely cleaned out.
1555 """
1556 if self.IsDirty():
1557 if force:
1558 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1559 (self.relpath,), file=sys.stderr)
1560 else:
1561 print('error: %s: Cannot remove project: uncommitted changes are '
1562 'present.\n' % (self.relpath,), file=sys.stderr)
1563 return False
1564
1565 if not quiet:
1566 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1567
1568 # Unlock and delink from the main worktree. We don't use git's worktree
1569 # remove because it will recursively delete projects -- we handle that
1570 # ourselves below. https://crbug.com/git/48
1571 if self.use_git_worktrees:
1572 needle = platform_utils.realpath(self.gitdir)
1573 # Find the git worktree commondir under .repo/worktrees/.
1574 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1575 assert output.startswith('worktree '), output
1576 commondir = output[9:]
1577 # Walk each of the git worktrees to see where they point.
1578 configs = os.path.join(commondir, 'worktrees')
1579 for name in os.listdir(configs):
1580 gitdir = os.path.join(configs, name, 'gitdir')
1581 with open(gitdir) as fp:
1582 relpath = fp.read().strip()
1583 # Resolve the checkout path and see if it matches this project.
1584 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1585 if fullpath == needle:
1586 platform_utils.rmtree(os.path.join(configs, name))
1587
1588 # Delete the .git directory first, so we're less likely to have a partially
1589 # working git repository around. There shouldn't be any git projects here,
1590 # so rmtree works.
1591
1592 # Try to remove plain files first in case of git worktrees. If this fails
1593 # for any reason, we'll fall back to rmtree, and that'll display errors if
1594 # it can't remove things either.
1595 try:
1596 platform_utils.remove(self.gitdir)
1597 except OSError:
1598 pass
1599 try:
1600 platform_utils.rmtree(self.gitdir)
1601 except OSError as e:
1602 if e.errno != errno.ENOENT:
1603 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1604 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1605 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1606 return False
1607
1608 # Delete everything under the worktree, except for directories that contain
1609 # another git project.
1610 dirs_to_remove = []
1611 failed = False
1612 for root, dirs, files in platform_utils.walk(self.worktree):
1613 for f in files:
1614 path = os.path.join(root, f)
1615 try:
1616 platform_utils.remove(path)
1617 except OSError as e:
1618 if e.errno != errno.ENOENT:
1619 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1620 failed = True
1621 dirs[:] = [d for d in dirs
1622 if not os.path.lexists(os.path.join(root, d, '.git'))]
1623 dirs_to_remove += [os.path.join(root, d) for d in dirs
1624 if os.path.join(root, d) not in dirs_to_remove]
1625 for d in reversed(dirs_to_remove):
1626 if platform_utils.islink(d):
1627 try:
1628 platform_utils.remove(d)
1629 except OSError as e:
1630 if e.errno != errno.ENOENT:
1631 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1632 failed = True
1633 elif not platform_utils.listdir(d):
1634 try:
1635 platform_utils.rmdir(d)
1636 except OSError as e:
1637 if e.errno != errno.ENOENT:
1638 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1639 failed = True
1640 if failed:
1641 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1642 file=sys.stderr)
1643 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1644 return False
1645
1646 # Try deleting parent dirs if they are empty.
1647 path = self.worktree
1648 while path != self.manifest.topdir:
1649 try:
1650 platform_utils.rmdir(path)
1651 except OSError as e:
1652 if e.errno != errno.ENOENT:
1653 break
1654 path = os.path.dirname(path)
1655
1656 return True
1657
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001658# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001659 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001660 """Create a new branch off the manifest's revision.
1661 """
Simran Basib9a1b732015-08-20 12:19:28 -07001662 if not branch_merge:
1663 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001664 head = self.work_git.GetHead()
1665 if head == (R_HEADS + name):
1666 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001667
David Pursehouse8a68ff92012-09-24 12:15:13 +09001668 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001669 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001670 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001671 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001672 capture_stdout=True,
1673 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001674
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001675 branch = self.GetBranch(name)
1676 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001677 branch.merge = branch_merge
1678 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1679 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001680
1681 if revision is None:
1682 revid = self.GetRevisionId(all_refs)
1683 else:
1684 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001685
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001686 if head.startswith(R_HEADS):
1687 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001688 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001689 except KeyError:
1690 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001691 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001692 ref = R_HEADS + name
1693 self.work_git.update_ref(ref, revid)
1694 self.work_git.symbolic_ref(HEAD, ref)
1695 branch.Save()
1696 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001697
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001698 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001699 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001700 capture_stdout=True,
1701 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001702 branch.Save()
1703 return True
1704 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705
Wink Saville02d79452009-04-10 13:01:24 -07001706 def CheckoutBranch(self, name):
1707 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001708
1709 Args:
1710 name: The name of the branch to checkout.
1711
1712 Returns:
1713 True if the checkout succeeded; False if it didn't; None if the branch
1714 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001715 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001716 rev = R_HEADS + name
1717 head = self.work_git.GetHead()
1718 if head == rev:
1719 # Already on the branch
1720 #
1721 return True
Wink Saville02d79452009-04-10 13:01:24 -07001722
David Pursehouse8a68ff92012-09-24 12:15:13 +09001723 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001724 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001725 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001726 except KeyError:
1727 # Branch does not exist in this project
1728 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001729 return None
Wink Saville02d79452009-04-10 13:01:24 -07001730
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001731 if head.startswith(R_HEADS):
1732 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001733 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001734 except KeyError:
1735 head = None
1736
1737 if head == revid:
1738 # Same revision; just update HEAD to point to the new
1739 # target branch, but otherwise take no other action.
1740 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001741 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1742 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001743 return True
1744
1745 return GitCommand(self,
1746 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001747 capture_stdout=True,
1748 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001749
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001750 def AbandonBranch(self, name):
1751 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001752
1753 Args:
1754 name: The name of the branch to abandon.
1755
1756 Returns:
1757 True if the abandon succeeded; False if it didn't; None if the branch
1758 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001759 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001760 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001761 all_refs = self.bare_ref.all
1762 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001763 # Doesn't exist
1764 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001765
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001766 head = self.work_git.GetHead()
1767 if head == rev:
1768 # We can't destroy the branch while we are sitting
1769 # on it. Switch to a detached HEAD.
1770 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001771 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001772
David Pursehouse8a68ff92012-09-24 12:15:13 +09001773 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001774 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001775 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001776 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001777 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001778
1779 return GitCommand(self,
1780 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001781 capture_stdout=True,
1782 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001783
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001784 def PruneHeads(self):
1785 """Prune any topic branches already merged into upstream.
1786 """
1787 cb = self.CurrentBranch
1788 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001789 left = self._allrefs
1790 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001791 if name.startswith(R_HEADS):
1792 name = name[len(R_HEADS):]
1793 if cb is None or name != cb:
1794 kill.append(name)
1795
Mike Frysingera3794e92021-03-11 23:24:01 -05001796 # Minor optimization: If there's nothing to prune, then don't try to read
1797 # any project state.
1798 if not kill and not cb:
1799 return []
1800
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001801 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001802 if cb is not None \
1803 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001804 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001805 self.work_git.DetachHead(HEAD)
1806 kill.append(cb)
1807
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001808 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001809 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001810
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001811 try:
1812 self.bare_git.DetachHead(rev)
1813
1814 b = ['branch', '-d']
1815 b.extend(kill)
1816 b = GitCommand(self, b, bare=True,
1817 capture_stdout=True,
1818 capture_stderr=True)
1819 b.Wait()
1820 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001821 if ID_RE.match(old):
1822 self.bare_git.DetachHead(old)
1823 else:
1824 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001825 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001826
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001827 for branch in kill:
1828 if (R_HEADS + branch) not in left:
1829 self.CleanPublishedCache()
1830 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001831
1832 if cb and cb not in kill:
1833 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001834 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001835
1836 kept = []
1837 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001838 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001839 branch = self.GetBranch(branch)
1840 base = branch.LocalMerge
1841 if not base:
1842 base = rev
1843 kept.append(ReviewableBranch(self, branch, base))
1844 return kept
1845
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001846# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001847 def GetRegisteredSubprojects(self):
1848 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001849
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001850 def rec(subprojects):
1851 if not subprojects:
1852 return
1853 result.extend(subprojects)
1854 for p in subprojects:
1855 rec(p.subprojects)
1856 rec(self.subprojects)
1857 return result
1858
1859 def _GetSubmodules(self):
1860 # Unfortunately we cannot call `git submodule status --recursive` here
1861 # because the working tree might not exist yet, and it cannot be used
1862 # without a working tree in its current implementation.
1863
1864 def get_submodules(gitdir, rev):
1865 # Parse .gitmodules for submodule sub_paths and sub_urls
1866 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1867 if not sub_paths:
1868 return []
1869 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1870 # revision of submodule repository
1871 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1872 submodules = []
1873 for sub_path, sub_url in zip(sub_paths, sub_urls):
1874 try:
1875 sub_rev = sub_revs[sub_path]
1876 except KeyError:
1877 # Ignore non-exist submodules
1878 continue
1879 submodules.append((sub_rev, sub_path, sub_url))
1880 return submodules
1881
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001882 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1883 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001884
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001885 def parse_gitmodules(gitdir, rev):
1886 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1887 try:
Anthony King7bdac712014-07-16 12:56:40 +01001888 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1889 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001890 except GitError:
1891 return [], []
1892 if p.Wait() != 0:
1893 return [], []
1894
1895 gitmodules_lines = []
1896 fd, temp_gitmodules_path = tempfile.mkstemp()
1897 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001898 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001899 os.close(fd)
1900 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001901 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1902 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001903 if p.Wait() != 0:
1904 return [], []
1905 gitmodules_lines = p.stdout.split('\n')
1906 except GitError:
1907 return [], []
1908 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001909 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001910
1911 names = set()
1912 paths = {}
1913 urls = {}
1914 for line in gitmodules_lines:
1915 if not line:
1916 continue
1917 m = re_path.match(line)
1918 if m:
1919 names.add(m.group(1))
1920 paths[m.group(1)] = m.group(2)
1921 continue
1922 m = re_url.match(line)
1923 if m:
1924 names.add(m.group(1))
1925 urls[m.group(1)] = m.group(2)
1926 continue
1927 names = sorted(names)
1928 return ([paths.get(name, '') for name in names],
1929 [urls.get(name, '') for name in names])
1930
1931 def git_ls_tree(gitdir, rev, paths):
1932 cmd = ['ls-tree', rev, '--']
1933 cmd.extend(paths)
1934 try:
Anthony King7bdac712014-07-16 12:56:40 +01001935 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1936 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001937 except GitError:
1938 return []
1939 if p.Wait() != 0:
1940 return []
1941 objects = {}
1942 for line in p.stdout.split('\n'):
1943 if not line.strip():
1944 continue
1945 object_rev, object_path = line.split()[2:4]
1946 objects[object_path] = object_rev
1947 return objects
1948
1949 try:
1950 rev = self.GetRevisionId()
1951 except GitError:
1952 return []
1953 return get_submodules(self.gitdir, rev)
1954
1955 def GetDerivedSubprojects(self):
1956 result = []
1957 if not self.Exists:
1958 # If git repo does not exist yet, querying its submodules will
1959 # mess up its states; so return here.
1960 return result
1961 for rev, path, url in self._GetSubmodules():
1962 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001963 relpath, worktree, gitdir, objdir = \
1964 self.manifest.GetSubprojectPaths(self, name, path)
1965 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001966 if project:
1967 result.extend(project.GetDerivedSubprojects())
1968 continue
David James8d201162013-10-11 17:03:19 -07001969
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001970 if url.startswith('..'):
1971 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001972 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001973 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001974 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001975 review=self.remote.review,
1976 revision=self.remote.revision)
1977 subproject = Project(manifest=self.manifest,
1978 name=name,
1979 remote=remote,
1980 gitdir=gitdir,
1981 objdir=objdir,
1982 worktree=worktree,
1983 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001984 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001985 revisionId=rev,
1986 rebase=self.rebase,
1987 groups=self.groups,
1988 sync_c=self.sync_c,
1989 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001990 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001991 parent=self,
1992 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001993 result.append(subproject)
1994 result.extend(subproject.GetDerivedSubprojects())
1995 return result
1996
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001997# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001998 def EnableRepositoryExtension(self, key, value='true', version=1):
1999 """Enable git repository extension |key| with |value|.
2000
2001 Args:
2002 key: The extension to enabled. Omit the "extensions." prefix.
2003 value: The value to use for the extension.
2004 version: The minimum git repository version needed.
2005 """
2006 # Make sure the git repo version is new enough already.
2007 found_version = self.config.GetInt('core.repositoryFormatVersion')
2008 if found_version is None:
2009 found_version = 0
2010 if found_version < version:
2011 self.config.SetString('core.repositoryFormatVersion', str(version))
2012
2013 # Enable the extension!
2014 self.config.SetString('extensions.%s' % (key,), value)
2015
Mike Frysinger50a81de2020-09-06 15:51:21 -04002016 def ResolveRemoteHead(self, name=None):
2017 """Find out what the default branch (HEAD) points to.
2018
2019 Normally this points to refs/heads/master, but projects are moving to main.
2020 Support whatever the server uses rather than hardcoding "master" ourselves.
2021 """
2022 if name is None:
2023 name = self.remote.name
2024
2025 # The output will look like (NB: tabs are separators):
2026 # ref: refs/heads/master HEAD
2027 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2028 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2029
2030 for line in output.splitlines():
2031 lhs, rhs = line.split('\t', 1)
2032 if rhs == 'HEAD' and lhs.startswith('ref:'):
2033 return lhs[4:].strip()
2034
2035 return None
2036
Zac Livingstone4332262017-06-16 08:56:09 -06002037 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002038 try:
2039 # if revision (sha or tag) is not present then following function
2040 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002041 self.bare_git.rev_list('-1', '--missing=allow-any',
2042 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00002043 if self.upstream:
2044 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
2045 self.bare_git.rev_list('-1', '--missing=allow-any',
2046 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002047 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002048 return True
2049 except GitError:
2050 # There is no such persistent revision. We have to fetch it.
2051 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002052
Julien Campergue335f5ef2013-10-16 11:02:35 +02002053 def _FetchArchive(self, tarpath, cwd=None):
2054 cmd = ['archive', '-v', '-o', tarpath]
2055 cmd.append('--remote=%s' % self.remote.url)
2056 cmd.append('--prefix=%s/' % self.relpath)
2057 cmd.append(self.revisionExpr)
2058
2059 command = GitCommand(self, cmd, cwd=cwd,
2060 capture_stdout=True,
2061 capture_stderr=True)
2062
2063 if command.Wait() != 0:
2064 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2065
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002066 def _RemoteFetch(self, name=None,
2067 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002068 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002069 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002070 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002071 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002072 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002073 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002074 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002075 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002076 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002077 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002078 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002079 clone_filter=None,
2080 retry_fetches=2,
2081 retry_sleep_initial_sec=4.0,
2082 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002083 is_sha1 = False
2084 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002085 # The depth should not be used when fetching to a mirror because
2086 # it will result in a shallow repository that cannot be cloned or
2087 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002088 # The repo project should also never be synced with partial depth.
2089 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2090 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002091
Shawn Pearce69e04d82014-01-29 12:48:54 -08002092 if depth:
2093 current_branch_only = True
2094
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002095 if ID_RE.match(self.revisionExpr) is not None:
2096 is_sha1 = True
2097
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002098 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002099 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002100 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002101 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002102 elif self.upstream and self.upstream.startswith(R_TAGS):
2103 # This is a tag and its commit id should never change.
2104 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002105
2106 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002107 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002108 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002109 print('Skipped fetching project %s (already have persistent ref)'
2110 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002111 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002112 if is_sha1 and not depth:
2113 # When syncing a specific commit and --depth is not set:
2114 # * if upstream is explicitly specified and is not a sha1, fetch only
2115 # upstream as users expect only upstream to be fetch.
2116 # Note: The commit might not be in upstream in which case the sync
2117 # will fail.
2118 # * otherwise, fetch all branches to make sure we end up with the
2119 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002120 if self.upstream:
2121 current_branch_only = not ID_RE.match(self.upstream)
2122 else:
2123 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002124
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002125 if not name:
2126 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002127
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002128 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002129 if not remote.PreConnectFetch(ssh_proxy):
2130 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002131
Shawn O. Pearce88443382010-10-08 10:02:09 +02002132 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002133 if alt_dir and 'objects' == os.path.basename(alt_dir):
2134 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002135 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002136
David Pursehouse8a68ff92012-09-24 12:15:13 +09002137 all_refs = self.bare_ref.all
2138 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002139 tmp = set()
2140
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302141 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002142 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002143 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002144 all_refs[r] = ref_id
2145 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002146 continue
2147
David Pursehouse8a68ff92012-09-24 12:15:13 +09002148 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002149 continue
2150
David Pursehouse8a68ff92012-09-24 12:15:13 +09002151 r = 'refs/_alt/%s' % ref_id
2152 all_refs[r] = ref_id
2153 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002154 tmp.add(r)
2155
heping3d7bbc92017-04-12 19:51:47 +08002156 tmp_packed_lines = []
2157 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002158
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302159 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002160 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002161 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002162 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002163 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002164
heping3d7bbc92017-04-12 19:51:47 +08002165 tmp_packed = ''.join(tmp_packed_lines)
2166 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002167 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002168 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002169 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002170
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002171 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002172
Xin Li745be2e2019-06-03 11:24:30 -07002173 if clone_filter:
2174 git_require((2, 19, 0), fail=True, msg='partial clones')
2175 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002176 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002177
Conley Owensf97e8382015-01-21 11:12:46 -08002178 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002179 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002180 else:
2181 # If this repo has shallow objects, then we don't know which refs have
2182 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2183 # do this with projects that don't have shallow objects, since it is less
2184 # efficient.
2185 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2186 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002187
Mike Frysinger4847e052020-02-22 00:07:35 -05002188 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002189 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002190 if not quiet and sys.stdout.isatty():
2191 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002192 if not self.worktree:
2193 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002194 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002195
Mike Frysingere57f1142019-03-18 21:27:54 -04002196 if force_sync:
2197 cmd.append('--force')
2198
David Pursehouse74cfd272015-10-14 10:50:15 +09002199 if prune:
2200 cmd.append('--prune')
2201
LaMont Jonesff6b1da2022-06-01 21:03:34 +00002202 # Always pass something for --recurse-submodules, git with GIT_DIR behaves
2203 # incorrectly when not given `--recurse-submodules=no`. (b/218891912)
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002204 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002205
Kuang-che Wu6856f982019-11-25 12:37:55 +08002206 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002207 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002208 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002209 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002210 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002211 spec.append('tag')
2212 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002213
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302214 if self.manifest.IsMirror and not current_branch_only:
2215 branch = None
2216 else:
2217 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002218 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002219 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002220 # Shallow checkout of a specific commit, fetch from that commit and not
2221 # the heads only as the commit might be deeper in the history.
2222 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002223 if self.upstream:
2224 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002225 else:
2226 if is_sha1:
2227 branch = self.upstream
2228 if branch is not None and branch.strip():
2229 if not branch.startswith('refs/'):
2230 branch = R_HEADS + branch
2231 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2232
2233 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2234 # whole repo.
2235 if self.manifest.IsMirror and not spec:
2236 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2237
2238 # If using depth then we should not get all the tags since they may
2239 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002240 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002241 cmd.append('--no-tags')
2242 else:
2243 cmd.append('--tags')
2244 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2245
Conley Owens80b87fe2014-05-09 17:13:44 -07002246 cmd.extend(spec)
2247
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002248 # At least one retry minimum due to git remote prune.
2249 retry_fetches = max(retry_fetches, 2)
2250 retry_cur_sleep = retry_sleep_initial_sec
2251 ok = prune_tried = False
2252 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002253 gitcmd = GitCommand(
2254 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2255 ssh_proxy=ssh_proxy,
2256 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002257 if gitcmd.stdout and not quiet and output_redir:
2258 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002259 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002260 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002261 ok = True
2262 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002263
2264 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002265 elif (gitcmd.stdout and
2266 'error:' in gitcmd.stdout and
2267 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002268 # Fallthru to sleep+retry logic at the bottom.
2269 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002270
Mike Frysinger6823bc22021-04-15 02:06:28 -04002271 # Try to prune remote branches once in case there are conflicts.
2272 # For example, if the remote had refs/heads/upstream, but deleted that and
2273 # now has refs/heads/upstream/foo.
2274 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002275 'error:' in gitcmd.stdout and
2276 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002277 not prune_tried):
2278 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002279 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002280 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002281 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002282 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002283 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002284 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002285 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002286 continue
Brian Harring14a66742012-09-28 20:21:57 -07002287 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002288 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2289 # in sha1 mode, we just tried sync'ing from the upstream field; it
2290 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002291 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002292 elif ret < 0:
2293 # Git died with a signal, exit immediately
2294 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002295
2296 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002297 if not verbose and gitcmd.stdout:
2298 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002299 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002300 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2301 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002302 time.sleep(retry_cur_sleep)
2303 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2304 MAXIMUM_RETRY_SLEEP_SEC)
2305 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2306 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002307
2308 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002309 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002310 if old_packed != '':
2311 _lwrite(packed_refs, old_packed)
2312 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002313 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002314 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002315
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002316 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002317 # We just synced the upstream given branch; verify we
2318 # got what we wanted, else trigger a second run of all
2319 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002320 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002321 # Sync the current branch only with depth set to None.
2322 # We always pass depth=None down to avoid infinite recursion.
2323 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002324 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002325 current_branch_only=current_branch_only and depth,
2326 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002327 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002328
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002329 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002330
Mike Frysingere50b6a72020-02-19 01:45:48 -05002331 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002332 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002333 return False
2334
2335 remote = self.GetRemote(self.remote.name)
2336 bundle_url = remote.url + '/clone.bundle'
2337 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002338 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2339 'persistent-http',
2340 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002341 return False
2342
2343 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2344 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2345
2346 exist_dst = os.path.exists(bundle_dst)
2347 exist_tmp = os.path.exists(bundle_tmp)
2348
2349 if not initial and not exist_dst and not exist_tmp:
2350 return False
2351
2352 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002353 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2354 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002355 if not exist_dst:
2356 return False
2357
2358 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002359 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002360 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002361 if not quiet and sys.stdout.isatty():
2362 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002363 if not self.worktree:
2364 cmd.append('--update-head-ok')
2365 cmd.append(bundle_dst)
2366 for f in remote.fetch:
2367 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002368 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002369
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002370 ok = GitCommand(
2371 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002372 platform_utils.remove(bundle_dst, missing_ok=True)
2373 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002374 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002375
Mike Frysingere50b6a72020-02-19 01:45:48 -05002376 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002377 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002378
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002379 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002380 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002381 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002382 if os.path.exists(tmpPath):
2383 size = os.stat(tmpPath).st_size
2384 if size >= 1024:
2385 cmd += ['--continue-at', '%d' % (size,)]
2386 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002387 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002388 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002389 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002390 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002391 if proxy:
2392 cmd += ['--proxy', proxy]
2393 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2394 cmd += ['--proxy', os.environ['http_proxy']]
2395 if srcUrl.startswith('persistent-https'):
2396 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2397 elif srcUrl.startswith('persistent-http'):
2398 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002399 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002400
Dave Borowitz137d0132015-01-02 11:12:54 -08002401 if IsTrace():
2402 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002403 if verbose:
2404 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2405 stdout = None if verbose else subprocess.PIPE
2406 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002407 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002408 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002409 except OSError:
2410 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002411
Mike Frysingere50b6a72020-02-19 01:45:48 -05002412 (output, _) = proc.communicate()
2413 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002414
Dave Borowitz137d0132015-01-02 11:12:54 -08002415 if curlret == 22:
2416 # From curl man page:
2417 # 22: HTTP page not retrieved. The requested url was not found or
2418 # returned another error with the HTTP error code being 400 or above.
2419 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002420 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002421 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2422 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002423 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002424 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002425 elif curlret and not verbose and output:
2426 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002427
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002428 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002429 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002430 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002431 return True
2432 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002433 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002434 return False
2435 else:
2436 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002437
Kris Giesingc8d882a2014-12-23 13:02:32 -08002438 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002439 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002440 with open(path, 'rb') as f:
2441 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002442 return True
2443 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002444 if not quiet:
2445 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002446 return False
2447 except OSError:
2448 return False
2449
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002450 def _Checkout(self, rev, quiet=False):
2451 cmd = ['checkout']
2452 if quiet:
2453 cmd.append('-q')
2454 cmd.append(rev)
2455 cmd.append('--')
2456 if GitCommand(self, cmd).Wait() != 0:
2457 if self._allrefs:
2458 raise GitError('%s checkout %s ' % (self.name, rev))
2459
Mike Frysinger915fda12020-03-22 12:15:20 -04002460 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002461 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002462 if ffonly:
2463 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002464 if record_origin:
2465 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002466 cmd.append(rev)
2467 cmd.append('--')
2468 if GitCommand(self, cmd).Wait() != 0:
2469 if self._allrefs:
2470 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2471
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302472 def _LsRemote(self, refs):
2473 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302474 p = GitCommand(self, cmd, capture_stdout=True)
2475 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002476 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302477 return None
2478
Anthony King7bdac712014-07-16 12:56:40 +01002479 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002480 cmd = ['revert']
2481 cmd.append('--no-edit')
2482 cmd.append(rev)
2483 cmd.append('--')
2484 if GitCommand(self, cmd).Wait() != 0:
2485 if self._allrefs:
2486 raise GitError('%s revert %s ' % (self.name, rev))
2487
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002488 def _ResetHard(self, rev, quiet=True):
2489 cmd = ['reset', '--hard']
2490 if quiet:
2491 cmd.append('-q')
2492 cmd.append(rev)
2493 if GitCommand(self, cmd).Wait() != 0:
2494 raise GitError('%s reset --hard %s ' % (self.name, rev))
2495
Martin Kellye4e94d22017-03-21 16:05:12 -07002496 def _SyncSubmodules(self, quiet=True):
2497 cmd = ['submodule', 'update', '--init', '--recursive']
2498 if quiet:
2499 cmd.append('-q')
2500 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002501 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002502
Anthony King7bdac712014-07-16 12:56:40 +01002503 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002504 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002505 if onto is not None:
2506 cmd.extend(['--onto', onto])
2507 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002508 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002509 raise GitError('%s rebase %s ' % (self.name, upstream))
2510
Pierre Tardy3d125942012-05-04 12:18:12 +02002511 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002512 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002513 if ffonly:
2514 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002515 if GitCommand(self, cmd).Wait() != 0:
2516 raise GitError('%s merge %s ' % (self.name, head))
2517
David Pursehousee8ace262020-02-13 12:41:15 +09002518 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002519 init_git_dir = not os.path.exists(self.gitdir)
2520 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002521 try:
2522 # Initialize the bare repository, which contains all of the objects.
2523 if init_obj_dir:
2524 os.makedirs(self.objdir)
2525 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002526
Mike Frysinger333c0a42021-11-15 12:39:00 -05002527 self._UpdateHooks(quiet=quiet)
2528
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002529 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002530 # Enable per-worktree config file support if possible. This is more a
2531 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002532 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002533 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002534
Kevin Degib1a07b82015-07-27 13:33:43 -06002535 # If we have a separate directory to hold refs, initialize it as well.
2536 if self.objdir != self.gitdir:
2537 if init_git_dir:
2538 os.makedirs(self.gitdir)
2539
2540 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002541 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002542 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002543 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002544 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002545 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002546 print("Retrying clone after deleting %s" %
2547 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002548 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002549 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2550 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002551 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002552 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002553 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2554 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002555 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002556 raise e
2557 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002558
Kevin Degi384b3c52014-10-16 16:02:58 -06002559 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002560 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002561 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002562
LaMont Jonescc879a92021-11-18 22:40:18 +00002563 def _expanded_ref_dirs():
2564 """Iterate through the possible git reference directory paths."""
2565 name = self.name + '.git'
2566 yield mirror_git or os.path.join(ref_dir, name)
2567 for prefix in '', self.remote.name:
2568 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2569 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002570
LaMont Jonescc879a92021-11-18 22:40:18 +00002571 if ref_dir or mirror_git:
2572 found_ref_dir = None
2573 for path in _expanded_ref_dirs():
2574 if os.path.exists(path):
2575 found_ref_dir = path
2576 break
2577 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002578
Kevin Degib1a07b82015-07-27 13:33:43 -06002579 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002580 if not os.path.isabs(ref_dir):
2581 # The alternate directory is relative to the object database.
2582 ref_dir = os.path.relpath(ref_dir,
2583 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002584 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002585 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002586
Kevin Degib1a07b82015-07-27 13:33:43 -06002587 m = self.manifest.manifestProject.config
2588 for key in ['user.name', 'user.email']:
2589 if m.Has(key, include_defaults=False):
2590 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002591 if not self.manifest.EnableGitLfs:
2592 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2593 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002594 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002595 except Exception:
2596 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002597 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002598 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002599 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002600 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002601
David Pursehousee8ace262020-02-13 12:41:15 +09002602 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002603 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002604 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002605
David Pursehousee8ace262020-02-13 12:41:15 +09002606 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002607 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002608 if not os.path.exists(hooks):
2609 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002610
2611 # Delete sample hooks. They're noise.
2612 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002613 try:
2614 platform_utils.remove(hook, missing_ok=True)
2615 except PermissionError:
2616 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002617
Jonathan Nieder93719792015-03-17 11:29:58 -07002618 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002619 name = os.path.basename(stock_hook)
2620
Victor Boivie65e0f352011-04-18 11:23:29 +02002621 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002622 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002623 # Don't install a Gerrit Code Review hook if this
2624 # project does not appear to use it for reviews.
2625 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002626 # Since the manifest project is one of those, but also
2627 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002628 continue
2629
2630 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002631 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002632 continue
2633 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002634 # If the files are the same, we'll leave it alone. We create symlinks
2635 # below by default but fallback to hardlinks if the OS blocks them.
2636 # So if we're here, it's probably because we made a hardlink below.
2637 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002638 if not quiet:
2639 _warn("%s: Not replacing locally modified %s hook",
2640 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002641 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002642 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002643 platform_utils.symlink(
2644 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002645 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002646 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002647 try:
2648 os.link(stock_hook, dst)
2649 except OSError:
2650 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002651 else:
2652 raise
2653
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002654 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002655 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002656 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002657 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002658 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002659 remote.review = self.remote.review
2660 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002661
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002662 if self.worktree:
2663 remote.ResetFetch(mirror=False)
2664 else:
2665 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002666 remote.Save()
2667
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002668 def _InitMRef(self):
2669 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002670 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002671 # Set up the m/ space to point to the worktree-specific ref space.
2672 # We'll update the worktree-specific ref space on each checkout.
2673 ref = R_M + self.manifest.branch
2674 if not self.bare_ref.symref(ref):
2675 self.bare_git.symbolic_ref(
2676 '-m', 'redirecting to worktree scope',
2677 ref, R_WORKTREE_M + self.manifest.branch)
2678
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002679 # We can't update this ref with git worktrees until it exists.
2680 # We'll wait until the initial checkout to set it.
2681 if not os.path.exists(self.worktree):
2682 return
2683
2684 base = R_WORKTREE_M
2685 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002686
2687 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002688 else:
2689 base = R_M
2690 active_git = self.bare_git
2691
2692 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002693
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002694 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002695 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002696
Remy Böhmer1469c282020-12-15 18:49:02 +01002697 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002698 cur = self.bare_ref.symref(ref)
2699
2700 if self.revisionId:
2701 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2702 msg = 'manifest set to %s' % self.revisionId
2703 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002704 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002705 else:
2706 remote = self.GetRemote(self.remote.name)
2707 dst = remote.ToLocal(self.revisionExpr)
2708 if cur != dst:
2709 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002710 if detach:
2711 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2712 else:
2713 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002714
Mike Frysingerc72bd842021-11-14 03:58:00 -05002715 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002716 # Git worktrees don't use symlinks to share at all.
2717 if self.use_git_worktrees:
2718 return
2719
Mike Frysingerd33dce02021-12-20 18:16:33 -05002720 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002721 # Try to self-heal a bit in simple cases.
2722 dst_path = os.path.join(destdir, name)
2723 src_path = os.path.join(srcdir, name)
2724
Mike Frysingered4f2112020-02-11 23:06:29 -05002725 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002726 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002727 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002728 # Fail if the links are pointing to the wrong place
2729 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002730 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002731 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002732 'work tree. If you\'re comfortable with the '
2733 'possibility of losing the work tree\'s git metadata,'
2734 ' use `repo sync --force-sync {0}` to '
2735 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002736
Mike Frysingerc72bd842021-11-14 03:58:00 -05002737 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002738 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2739
2740 Args:
2741 gitdir: The bare git repository. Must already be initialized.
2742 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002743 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2744 This saves you the effort of initializing |dotgit| yourself.
2745 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002746 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002747 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002748
2749 to_copy = []
2750 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002751 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002752
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002753 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002754 for name in set(to_copy).union(to_symlink):
2755 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002756 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002757 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002758
Kevin Degi384b3c52014-10-16 16:02:58 -06002759 if os.path.lexists(dst):
2760 continue
David James8d201162013-10-11 17:03:19 -07002761
2762 # If the source dir doesn't exist, create an empty dir.
2763 if name in symlink_dirs and not os.path.lexists(src):
2764 os.makedirs(src)
2765
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002766 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002767 platform_utils.symlink(
2768 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002769 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002770 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002771 shutil.copytree(src, dst)
2772 elif os.path.isfile(src):
2773 shutil.copy(src, dst)
2774
David James8d201162013-10-11 17:03:19 -07002775 except OSError as e:
2776 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002777 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002778 else:
2779 raise
2780
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002781 def _InitGitWorktree(self):
2782 """Init the project using git worktrees."""
2783 self.bare_git.worktree('prune')
2784 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2785 self.worktree, self.GetRevisionId())
2786
2787 # Rewrite the internal state files to use relative paths between the
2788 # checkouts & worktrees.
2789 dotgit = os.path.join(self.worktree, '.git')
2790 with open(dotgit, 'r') as fp:
2791 # Figure out the checkout->worktree path.
2792 setting = fp.read()
2793 assert setting.startswith('gitdir:')
2794 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002795 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2796 # of file permissions. Delete it and recreate it from scratch to avoid.
2797 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002798 # Use relative path from checkout->worktree & maintain Unix line endings
2799 # on all OS's to match git behavior.
2800 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002801 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2802 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002803 # Use relative path from worktree->checkout & maintain Unix line endings
2804 # on all OS's to match git behavior.
2805 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002806 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2807
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002808 self._InitMRef()
2809
Martin Kellye4e94d22017-03-21 16:05:12 -07002810 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002811 """Setup the worktree .git path.
2812
2813 This is the user-visible path like src/foo/.git/.
2814
2815 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2816 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2817
2818 Older checkouts had .git/ directories. If we see that, migrate it.
2819
2820 This also handles changes in the manifest. Maybe this project was backed
2821 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2822 the path we point to under .repo/projects/ to match.
2823 """
2824 dotgit = os.path.join(self.worktree, '.git')
2825
2826 # If using an old layout style (a directory), migrate it.
2827 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2828 self._MigrateOldWorkTreeGitDir(dotgit)
2829
2830 init_dotgit = not os.path.exists(dotgit)
2831 if self.use_git_worktrees:
2832 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002833 self._InitGitWorktree()
2834 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002835 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002836 if not init_dotgit:
2837 # See if the project has changed.
2838 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2839 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002840
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002841 if init_dotgit or not os.path.exists(dotgit):
2842 os.makedirs(self.worktree, exist_ok=True)
2843 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002844
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002845 if init_dotgit:
2846 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002847
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002848 # Finish checking out the worktree.
2849 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2850 if GitCommand(self, cmd).Wait() != 0:
2851 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002852
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002853 if submodules:
2854 self._SyncSubmodules(quiet=True)
2855 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002856
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002857 @classmethod
2858 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2859 """Migrate the old worktree .git/ dir style to a symlink.
2860
2861 This logic specifically only uses state from |dotgit| to figure out where to
2862 move content and not |self|. This way if the backing project also changed
2863 places, we only do the .git/ dir to .git symlink migration here. The path
2864 updates will happen independently.
2865 """
2866 # Figure out where in .repo/projects/ it's pointing to.
2867 if not os.path.islink(os.path.join(dotgit, 'refs')):
2868 raise GitError(f'{dotgit}: unsupported checkout state')
2869 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2870
2871 # Remove known symlink paths that exist in .repo/projects/.
2872 KNOWN_LINKS = {
2873 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2874 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2875 }
2876 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2877 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002878 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2879 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002880 }
2881
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002882 # First see if we'd succeed before starting the migration.
2883 unknown_paths = []
2884 for name in platform_utils.listdir(dotgit):
2885 # Ignore all temporary/backup names. These are common with vim & emacs.
2886 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2887 continue
2888
2889 dotgit_path = os.path.join(dotgit, name)
2890 if name in KNOWN_LINKS:
2891 if not platform_utils.islink(dotgit_path):
2892 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2893 else:
2894 gitdir_path = os.path.join(gitdir, name)
2895 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2896 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2897 if unknown_paths:
2898 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2899
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002900 # Now walk the paths and sync the .git/ to .repo/projects/.
2901 for name in platform_utils.listdir(dotgit):
2902 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002903
2904 # Ignore all temporary/backup names. These are common with vim & emacs.
2905 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2906 platform_utils.remove(dotgit_path)
2907 elif name in KNOWN_LINKS:
2908 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002909 else:
2910 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002911 platform_utils.remove(gitdir_path, missing_ok=True)
2912 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002913
2914 # Now that the dir should be empty, clear it out, and symlink it over.
2915 platform_utils.rmdir(dotgit)
2916 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002917
Renaud Paquay788e9622017-01-27 11:41:12 -08002918 def _get_symlink_error_message(self):
2919 if platform_utils.isWindows():
2920 return ('Unable to create symbolic link. Please re-run the command as '
2921 'Administrator, or see '
2922 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2923 'for other options.')
2924 return 'filesystem must support symlinks'
2925
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002926 def _revlist(self, *args, **kw):
2927 a = []
2928 a.extend(args)
2929 a.append('--')
2930 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002931
2932 @property
2933 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002934 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002935
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002936 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002937 """Get logs between two revisions of this project."""
2938 comp = '..'
2939 if rev1:
2940 revs = [rev1]
2941 if rev2:
2942 revs.extend([comp, rev2])
2943 cmd = ['log', ''.join(revs)]
2944 out = DiffColoring(self.config)
2945 if out.is_on and color:
2946 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002947 if pretty_format is not None:
2948 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002949 if oneline:
2950 cmd.append('--oneline')
2951
2952 try:
2953 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2954 if log.Wait() == 0:
2955 return log.stdout
2956 except GitError:
2957 # worktree may not exist if groups changed for example. In that case,
2958 # try in gitdir instead.
2959 if not os.path.exists(self.worktree):
2960 return self.bare_git.log(*cmd[1:])
2961 else:
2962 raise
2963 return None
2964
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002965 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2966 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002967 """Get the list of logs from this revision to given revisionId"""
2968 logs = {}
2969 selfId = self.GetRevisionId(self._allrefs)
2970 toId = toProject.GetRevisionId(toProject._allrefs)
2971
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002972 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2973 pretty_format=pretty_format)
2974 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2975 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002976 return logs
2977
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002978 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002979
David James8d201162013-10-11 17:03:19 -07002980 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002981 self._project = project
2982 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002983 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002984
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002985 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2986 def __getstate__(self):
2987 return (self._project, self._bare, self._gitdir)
2988
2989 def __setstate__(self, state):
2990 self._project, self._bare, self._gitdir = state
2991
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002992 def LsOthers(self):
2993 p = GitCommand(self._project,
2994 ['ls-files',
2995 '-z',
2996 '--others',
2997 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002998 bare=False,
David James8d201162013-10-11 17:03:19 -07002999 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003000 capture_stdout=True,
3001 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003002 if p.Wait() == 0:
3003 out = p.stdout
3004 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003005 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003006 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003007 return []
3008
3009 def DiffZ(self, name, *args):
3010 cmd = [name]
3011 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003012 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003013 cmd.extend(args)
3014 p = GitCommand(self._project,
3015 cmd,
David James8d201162013-10-11 17:03:19 -07003016 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003017 bare=False,
3018 capture_stdout=True,
3019 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05003020 p.Wait()
3021 r = {}
3022 out = p.stdout
3023 if out:
3024 out = iter(out[:-1].split('\0'))
3025 while out:
3026 try:
3027 info = next(out)
3028 path = next(out)
3029 except StopIteration:
3030 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003031
Mike Frysinger84230002021-02-16 17:08:35 -05003032 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003033
Mike Frysinger84230002021-02-16 17:08:35 -05003034 def __init__(self, path, omode, nmode, oid, nid, state):
3035 self.path = path
3036 self.src_path = None
3037 self.old_mode = omode
3038 self.new_mode = nmode
3039 self.old_id = oid
3040 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003041
Mike Frysinger84230002021-02-16 17:08:35 -05003042 if len(state) == 1:
3043 self.status = state
3044 self.level = None
3045 else:
3046 self.status = state[:1]
3047 self.level = state[1:]
3048 while self.level.startswith('0'):
3049 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003050
Mike Frysinger84230002021-02-16 17:08:35 -05003051 info = info[1:].split(' ')
3052 info = _Info(path, *info)
3053 if info.status in ('R', 'C'):
3054 info.src_path = info.path
3055 info.path = next(out)
3056 r[info.path] = info
3057 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003058
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003059 def GetDotgitPath(self, subpath=None):
3060 """Return the full path to the .git dir.
3061
3062 As a convenience, append |subpath| if provided.
3063 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003064 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003065 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003066 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003067 dotgit = os.path.join(self._project.worktree, '.git')
3068 if os.path.isfile(dotgit):
3069 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3070 with open(dotgit) as fp:
3071 setting = fp.read()
3072 assert setting.startswith('gitdir:')
3073 gitdir = setting.split(':', 1)[1].strip()
3074 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3075
3076 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3077
3078 def GetHead(self):
3079 """Return the ref that HEAD points to."""
3080 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003081 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003082 with open(path) as fd:
3083 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003084 except IOError as e:
3085 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003086 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303087 line = line.decode()
3088 except AttributeError:
3089 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003090 if line.startswith('ref: '):
3091 return line[5:-1]
3092 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003093
3094 def SetHead(self, ref, message=None):
3095 cmdv = []
3096 if message is not None:
3097 cmdv.extend(['-m', message])
3098 cmdv.append(HEAD)
3099 cmdv.append(ref)
3100 self.symbolic_ref(*cmdv)
3101
3102 def DetachHead(self, new, message=None):
3103 cmdv = ['--no-deref']
3104 if message is not None:
3105 cmdv.extend(['-m', message])
3106 cmdv.append(HEAD)
3107 cmdv.append(new)
3108 self.update_ref(*cmdv)
3109
3110 def UpdateRef(self, name, new, old=None,
3111 message=None,
3112 detach=False):
3113 cmdv = []
3114 if message is not None:
3115 cmdv.extend(['-m', message])
3116 if detach:
3117 cmdv.append('--no-deref')
3118 cmdv.append(name)
3119 cmdv.append(new)
3120 if old is not None:
3121 cmdv.append(old)
3122 self.update_ref(*cmdv)
3123
3124 def DeleteRef(self, name, old=None):
3125 if not old:
3126 old = self.rev_parse(name)
3127 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003128 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003129
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003130 def rev_list(self, *args, **kw):
3131 if 'format' in kw:
3132 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3133 else:
3134 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003135 cmdv.extend(args)
3136 p = GitCommand(self._project,
3137 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003138 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003139 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003140 capture_stdout=True,
3141 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003142 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003143 raise GitError('%s rev-list %s: %s' %
3144 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003145 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003146
3147 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003148 """Allow arbitrary git commands using pythonic syntax.
3149
3150 This allows you to do things like:
3151 git_obj.rev_parse('HEAD')
3152
3153 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3154 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003155 Any other positional arguments will be passed to the git command, and the
3156 following keyword arguments are supported:
3157 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003158
3159 Args:
3160 name: The name of the git command to call. Any '_' characters will
3161 be replaced with '-'.
3162
3163 Returns:
3164 A callable object that will try to call git with the named command.
3165 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003166 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003167
Dave Borowitz091f8932012-10-23 17:01:04 -07003168 def runner(*args, **kwargs):
3169 cmdv = []
3170 config = kwargs.pop('config', None)
3171 for k in kwargs:
3172 raise TypeError('%s() got an unexpected keyword argument %r'
3173 % (name, k))
3174 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303175 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003176 cmdv.append('-c')
3177 cmdv.append('%s=%s' % (k, v))
3178 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003179 cmdv.extend(args)
3180 p = GitCommand(self._project,
3181 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003182 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003183 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003184 capture_stdout=True,
3185 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003186 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003187 raise GitError('%s %s: %s' %
3188 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003189 r = p.stdout
3190 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3191 return r[:-1]
3192 return r
3193 return runner
3194
3195
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003196class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003197
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003198 def __str__(self):
3199 return 'prior sync failed; rebase still in progress'
3200
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003201
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003202class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003203
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003204 def __str__(self):
3205 return 'contains uncommitted changes'
3206
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003207
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003208class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003209
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003210 def __init__(self, project, text):
3211 self.project = project
3212 self.text = text
3213
3214 def Print(self, syncbuf):
3215 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3216 syncbuf.out.nl()
3217
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003218
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003219class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003220
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003221 def __init__(self, project, why):
3222 self.project = project
3223 self.why = why
3224
3225 def Print(self, syncbuf):
3226 syncbuf.out.fail('error: %s/: %s',
3227 self.project.relpath,
3228 str(self.why))
3229 syncbuf.out.nl()
3230
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003231
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003232class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003233
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003234 def __init__(self, project, action):
3235 self.project = project
3236 self.action = action
3237
3238 def Run(self, syncbuf):
3239 out = syncbuf.out
3240 out.project('project %s/', self.project.relpath)
3241 out.nl()
3242 try:
3243 self.action()
3244 out.nl()
3245 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003246 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003247 out.nl()
3248 return False
3249
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003250
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003251class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003252
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003253 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003254 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003255 self.project = self.printer('header', attr='bold')
3256 self.info = self.printer('info')
3257 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003258
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003259
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003260class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003261
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003262 def __init__(self, config, detach_head=False):
3263 self._messages = []
3264 self._failures = []
3265 self._later_queue1 = []
3266 self._later_queue2 = []
3267
3268 self.out = _SyncColoring(config)
3269 self.out.redirect(sys.stderr)
3270
3271 self.detach_head = detach_head
3272 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003273 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003274
3275 def info(self, project, fmt, *args):
3276 self._messages.append(_InfoMessage(project, fmt % args))
3277
3278 def fail(self, project, err=None):
3279 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003280 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003281
3282 def later1(self, project, what):
3283 self._later_queue1.append(_Later(project, what))
3284
3285 def later2(self, project, what):
3286 self._later_queue2.append(_Later(project, what))
3287
3288 def Finish(self):
3289 self._PrintMessages()
3290 self._RunLater()
3291 self._PrintMessages()
3292 return self.clean
3293
David Rileye0684ad2017-04-05 00:02:59 -07003294 def Recently(self):
3295 recent_clean = self.recent_clean
3296 self.recent_clean = True
3297 return recent_clean
3298
3299 def _MarkUnclean(self):
3300 self.clean = False
3301 self.recent_clean = False
3302
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003303 def _RunLater(self):
3304 for q in ['_later_queue1', '_later_queue2']:
3305 if not self._RunQueue(q):
3306 return
3307
3308 def _RunQueue(self, queue):
3309 for m in getattr(self, queue):
3310 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003311 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003312 return False
3313 setattr(self, queue, [])
3314 return True
3315
3316 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003317 if self._messages or self._failures:
3318 if os.isatty(2):
3319 self.out.write(progress.CSI_ERASE_LINE)
3320 self.out.write('\r')
3321
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003322 for m in self._messages:
3323 m.Print(self)
3324 for m in self._failures:
3325 m.Print(self)
3326
3327 self._messages = []
3328 self._failures = []
3329
3330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003331class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003332 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003333
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003334 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003335 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003336 manifest=manifest,
3337 name=name,
3338 gitdir=gitdir,
3339 objdir=gitdir,
3340 worktree=worktree,
3341 remote=RemoteSpec('origin'),
3342 relpath='.repo/%s' % name,
3343 revisionExpr='refs/heads/master',
3344 revisionId=None,
3345 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003346
3347 def PreSync(self):
3348 if self.Exists:
3349 cb = self.CurrentBranch
3350 if cb:
3351 base = self.GetBranch(cb).merge
3352 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003353 self.revisionExpr = base
3354 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003355
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003356 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003357 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003358 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003359 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003360 return False
3361
David Pursehouse8a68ff92012-09-24 12:15:13 +09003362 all_refs = self.bare_ref.all
3363 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003364 head = self.work_git.GetHead()
3365 if head.startswith(R_HEADS):
3366 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003367 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003368 except KeyError:
3369 head = None
3370
3371 if revid == head:
3372 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003373 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003374 return True
3375 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003376
3377
3378class RepoProject(MetaProject):
3379 """The MetaProject for repo itself."""
3380
3381 @property
3382 def LastFetch(self):
3383 try:
3384 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3385 return os.path.getmtime(fh)
3386 except OSError:
3387 return 0
3388
3389class ManifestProject(MetaProject):
3390 """The MetaProject for manifests."""
3391
3392 def MetaBranchSwitch(self, submodules=False):
3393 """Prepare for manifest branch switch."""
3394
3395 # detach and delete manifest branch, allowing a new
3396 # branch to take over
3397 syncbuf = SyncBuffer(self.config, detach_head=True)
3398 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3399 syncbuf.Finish()
3400
3401 return GitCommand(self,
3402 ['update-ref', '-d', 'refs/heads/default'],
3403 capture_stdout=True,
3404 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003405
3406 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003407 def standalone_manifest_url(self):
3408 """The URL of the standalone manifest, or None."""
LaMont Jones55ee3042022-04-06 17:10:21 +00003409 return self.config.GetString('manifest.standalone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003410
3411 @property
3412 def manifest_groups(self):
3413 """The manifest groups string."""
3414 return self.config.GetString('manifest.groups')
3415
3416 @property
3417 def reference(self):
3418 """The --reference for this manifest."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003419 return self.config.GetString('repo.reference')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003420
3421 @property
3422 def dissociate(self):
3423 """Whether to dissociate."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003424 return self.config.GetBoolean('repo.dissociate')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003425
3426 @property
3427 def archive(self):
3428 """Whether we use archive."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003429 return self.config.GetBoolean('repo.archive')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003430
3431 @property
3432 def mirror(self):
3433 """Whether we use mirror."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003434 return self.config.GetBoolean('repo.mirror')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003435
3436 @property
3437 def use_worktree(self):
3438 """Whether we use worktree."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003439 return self.config.GetBoolean('repo.worktree')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003440
3441 @property
3442 def clone_bundle(self):
3443 """Whether we use clone_bundle."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003444 return self.config.GetBoolean('repo.clonebundle')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003445
3446 @property
3447 def submodules(self):
3448 """Whether we use submodules."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003449 return self.config.GetBoolean('repo.submodules')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003450
3451 @property
3452 def git_lfs(self):
3453 """Whether we use git_lfs."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003454 return self.config.GetBoolean('repo.git-lfs')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003455
3456 @property
3457 def use_superproject(self):
3458 """Whether we use superproject."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003459 return self.config.GetBoolean('repo.superproject')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003460
3461 @property
3462 def partial_clone(self):
3463 """Whether this is a partial clone."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003464 return self.config.GetBoolean('repo.partialclone')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003465
3466 @property
3467 def depth(self):
3468 """Partial clone depth."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003469 return self.config.GetString('repo.depth')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003470
3471 @property
3472 def clone_filter(self):
3473 """The clone filter."""
LaMont Jones4ada0432022-04-14 15:10:43 +00003474 return self.config.GetString('repo.clonefilter')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003475
3476 @property
3477 def partial_clone_exclude(self):
3478 """Partial clone exclude string"""
LaMont Jones4ada0432022-04-14 15:10:43 +00003479 return self.config.GetBoolean('repo.partialcloneexclude')
3480
3481 @property
3482 def manifest_platform(self):
3483 """The --platform argument from `repo init`."""
3484 return self.config.GetString('manifest.platform')
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003485
3486 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003487 def _platform_name(self):
3488 """Return the name of the platform."""
3489 return platform.system().lower()
3490
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003491 def SyncWithPossibleInit(self, submanifest, verbose=False,
3492 current_branch_only=False, tags='', git_event_log=None):
3493 """Sync a manifestProject, possibly for the first time.
3494
3495 Call Sync() with arguments from the most recent `repo init`. If this is a
3496 new sub manifest, then inherit options from the parent's manifestProject.
3497
3498 This is used by subcmds.Sync() to do an initial download of new sub
3499 manifests.
3500
3501 Args:
3502 submanifest: an XmlSubmanifest, the submanifest to re-sync.
3503 verbose: a boolean, whether to show all output, rather than only errors.
3504 current_branch_only: a boolean, whether to only fetch the current manifest
3505 branch from the server.
3506 tags: a boolean, whether to fetch tags.
3507 git_event_log: an EventLog, for git tracing.
3508 """
3509 # TODO(lamontjones): when refactoring sync (and init?) consider how to
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003510 # better get the init options that we should use for new submanifests that
3511 # are added when syncing an existing workspace.
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00003512 git_event_log = git_event_log or EventLog()
3513 spec = submanifest.ToSubmanifestSpec()
3514 # Use the init options from the existing manifestProject, or the parent if
3515 # it doesn't exist.
3516 #
3517 # Today, we only support changing manifest_groups on the sub-manifest, with
3518 # no supported-for-the-user way to change the other arguments from those
3519 # specified by the outermost manifest.
3520 #
3521 # TODO(lamontjones): determine which of these should come from the outermost
3522 # manifest and which should come from the parent manifest.
3523 mp = self if self.Exists else submanifest.parent.manifestProject
3524 return self.Sync(
3525 manifest_url=spec.manifestUrl,
3526 manifest_branch=spec.revision,
3527 standalone_manifest=mp.standalone_manifest_url,
3528 groups=mp.manifest_groups,
3529 platform=mp.manifest_platform,
3530 mirror=mp.mirror,
3531 dissociate=mp.dissociate,
3532 reference=mp.reference,
3533 worktree=mp.use_worktree,
3534 submodules=mp.submodules,
3535 archive=mp.archive,
3536 partial_clone=mp.partial_clone,
3537 clone_filter=mp.clone_filter,
3538 partial_clone_exclude=mp.partial_clone_exclude,
3539 clone_bundle=mp.clone_bundle,
3540 git_lfs=mp.git_lfs,
3541 use_superproject=mp.use_superproject,
3542 verbose=verbose,
3543 current_branch_only=current_branch_only,
3544 tags=tags,
3545 depth=mp.depth,
3546 git_event_log=git_event_log,
3547 manifest_name=spec.manifestName,
3548 this_manifest_only=True,
3549 outer_manifest=False,
3550 )
3551
LaMont Jones9b03f152022-03-29 23:01:18 +00003552 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003553 standalone_manifest=False, groups='', mirror=False, reference='',
3554 dissociate=False, worktree=False, submodules=False, archive=False,
3555 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003556 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3557 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jones55ee3042022-04-06 17:10:21 +00003558 git_event_log=None, platform='', manifest_name='default.xml',
3559 tags='', this_manifest_only=False, outer_manifest=True):
LaMont Jones9b03f152022-03-29 23:01:18 +00003560 """Sync the manifest and all submanifests.
3561
3562 Args:
3563 manifest_url: a string, the URL of the manifest project.
3564 manifest_branch: a string, the manifest branch to use.
3565 standalone_manifest: a boolean, whether to store the manifest as a static
3566 file.
3567 groups: a string, restricts the checkout to projects with the specified
3568 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003569 mirror: a boolean, whether to create a mirror of the remote repository.
3570 reference: a string, location of a repo instance to use as a reference.
3571 dissociate: a boolean, whether to dissociate from reference mirrors after
3572 clone.
3573 worktree: a boolean, whether to use git-worktree to manage projects.
3574 submodules: a boolean, whether sync submodules associated with the
3575 manifest project.
3576 archive: a boolean, whether to checkout each project as an archive. See
3577 git-archive.
3578 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003579 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003580 clone_filter: a string, filter to use with partial_clone.
3581 partial_clone_exclude : a string, comma-delimeted list of project namess
3582 to exclude from partial clone.
3583 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3584 git_lfs: a boolean, whether to enable git LFS support.
3585 use_superproject: a boolean, whether to use the manifest superproject to
3586 sync projects.
3587 verbose: a boolean, whether to show all output, rather than only errors.
3588 current_branch_only: a boolean, whether to only fetch the current manifest
3589 branch from the server.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003590 platform: a string, restrict the checkout to projects with the specified
3591 platform group.
LaMont Jones55ee3042022-04-06 17:10:21 +00003592 git_event_log: an EventLog, for git tracing.
LaMont Jones4ada0432022-04-14 15:10:43 +00003593 tags: a boolean, whether to fetch tags.
LaMont Jones409407a2022-04-05 21:21:56 +00003594 manifest_name: a string, the name of the manifest file to use.
3595 this_manifest_only: a boolean, whether to only operate on the current sub
3596 manifest.
3597 outer_manifest: a boolean, whether to start at the outermost manifest.
LaMont Jones9b03f152022-03-29 23:01:18 +00003598
3599 Returns:
3600 a boolean, whether the sync was successful.
3601 """
3602 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3603
LaMont Jones501733c2022-04-20 16:42:32 +00003604 groups = groups or self.manifest.GetDefaultGroupsStr(with_platform=False)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003605 platform = platform or 'auto'
LaMont Jones55ee3042022-04-06 17:10:21 +00003606 git_event_log = git_event_log or EventLog()
LaMont Jones409407a2022-04-05 21:21:56 +00003607 if outer_manifest and self.manifest.is_submanifest:
3608 # In a multi-manifest checkout, use the outer manifest unless we are told
3609 # not to.
3610 return self.client.outer_manifest.manifestProject.Sync(
3611 manifest_url=manifest_url,
3612 manifest_branch=manifest_branch,
3613 standalone_manifest=standalone_manifest,
3614 groups=groups,
3615 platform=platform,
3616 mirror=mirror,
3617 dissociate=dissociate,
3618 reference=reference,
3619 worktree=worktree,
3620 submodules=submodules,
3621 archive=archive,
3622 partial_clone=partial_clone,
3623 clone_filter=clone_filter,
3624 partial_clone_exclude=partial_clone_exclude,
3625 clone_bundle=clone_bundle,
3626 git_lfs=git_lfs,
3627 use_superproject=use_superproject,
3628 verbose=verbose,
3629 current_branch_only=current_branch_only,
3630 tags=tags,
3631 depth=depth,
LaMont Jones55ee3042022-04-06 17:10:21 +00003632 git_event_log=git_event_log,
LaMont Jones409407a2022-04-05 21:21:56 +00003633 manifest_name=manifest_name,
3634 this_manifest_only=this_manifest_only,
3635 outer_manifest=False)
3636
LaMont Jones9b03f152022-03-29 23:01:18 +00003637 # If repo has already been initialized, we take -u with the absence of
3638 # --standalone-manifest to mean "transition to a standard repo set up",
3639 # which necessitates starting fresh.
3640 # If --standalone-manifest is set, we always tear everything down and start
3641 # anew.
3642 if self.Exists:
3643 was_standalone_manifest = self.config.GetString('manifest.standalone')
3644 if was_standalone_manifest and not manifest_url:
3645 print('fatal: repo was initialized with a standlone manifest, '
3646 'cannot be re-initialized without --manifest-url/-u')
3647 return False
3648
3649 if standalone_manifest or (was_standalone_manifest and manifest_url):
3650 self.config.ClearCache()
3651 if self.gitdir and os.path.exists(self.gitdir):
3652 platform_utils.rmtree(self.gitdir)
3653 if self.worktree and os.path.exists(self.worktree):
3654 platform_utils.rmtree(self.worktree)
3655
3656 is_new = not self.Exists
3657 if is_new:
3658 if not manifest_url:
3659 print('fatal: manifest url is required.', file=sys.stderr)
3660 return False
3661
LaMont Jones409407a2022-04-05 21:21:56 +00003662 if verbose:
LaMont Jones9b03f152022-03-29 23:01:18 +00003663 print('Downloading manifest from %s' %
3664 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3665 file=sys.stderr)
3666
3667 # The manifest project object doesn't keep track of the path on the
3668 # server where this git is located, so let's save that here.
3669 mirrored_manifest_git = None
3670 if reference:
3671 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3672 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3673 if not mirrored_manifest_git.endswith(".git"):
3674 mirrored_manifest_git += ".git"
3675 if not os.path.exists(mirrored_manifest_git):
3676 mirrored_manifest_git = os.path.join(reference,
3677 '.repo/manifests.git')
3678
3679 self._InitGitDir(mirror_git=mirrored_manifest_git)
3680
3681 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3682 # still do much of the manifests.git set up, but will avoid actual syncs to
3683 # a remote.
3684 if standalone_manifest:
3685 self.config.SetString('manifest.standalone', manifest_url)
3686 elif not manifest_url and not manifest_branch:
3687 # If -u is set and --standalone-manifest is not, then we're not in
3688 # standalone mode. Otherwise, use config to infer what we were in the last
3689 # init.
3690 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3691 if not standalone_manifest:
3692 self.config.SetString('manifest.standalone', None)
3693
3694 self._ConfigureDepth(depth)
3695
3696 # Set the remote URL before the remote branch as we might need it below.
3697 if manifest_url:
3698 r = self.GetRemote(self.remote.name)
3699 r.url = manifest_url
3700 r.ResetFetch()
3701 r.Save()
3702
3703 if not standalone_manifest:
3704 if manifest_branch:
3705 if manifest_branch == 'HEAD':
3706 manifest_branch = self.ResolveRemoteHead()
3707 if manifest_branch is None:
3708 print('fatal: unable to resolve HEAD', file=sys.stderr)
3709 return False
3710 self.revisionExpr = manifest_branch
3711 else:
3712 if is_new:
3713 default_branch = self.ResolveRemoteHead()
3714 if default_branch is None:
3715 # If the remote doesn't have HEAD configured, default to master.
3716 default_branch = 'refs/heads/master'
3717 self.revisionExpr = default_branch
3718 else:
3719 self.PreSync()
3720
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003721 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003722 all_platforms = ['linux', 'darwin', 'windows']
3723 platformize = lambda x: 'platform-' + x
3724 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003725 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003726 groups.append(platformize(self._platform_name))
3727 elif platform == 'all':
3728 groups.extend(map(platformize, all_platforms))
3729 elif platform in all_platforms:
3730 groups.append(platformize(platform))
3731 elif platform != 'none':
3732 print('fatal: invalid platform flag', file=sys.stderr)
3733 return False
LaMont Jones4ada0432022-04-14 15:10:43 +00003734 self.config.SetString('manifest.platform', platform)
LaMont Jones9b03f152022-03-29 23:01:18 +00003735
3736 groups = [x for x in groups if x]
3737 groupstr = ','.join(groups)
3738 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3739 groupstr = None
3740 self.config.SetString('manifest.groups', groupstr)
3741
3742 if reference:
3743 self.config.SetString('repo.reference', reference)
3744
3745 if dissociate:
3746 self.config.SetBoolean('repo.dissociate', dissociate)
3747
3748 if worktree:
3749 if mirror:
3750 print('fatal: --mirror and --worktree are incompatible',
3751 file=sys.stderr)
3752 return False
3753 if submodules:
3754 print('fatal: --submodules and --worktree are incompatible',
3755 file=sys.stderr)
3756 return False
3757 self.config.SetBoolean('repo.worktree', worktree)
3758 if is_new:
3759 self.use_git_worktrees = True
3760 print('warning: --worktree is experimental!', file=sys.stderr)
3761
3762 if archive:
3763 if is_new:
3764 self.config.SetBoolean('repo.archive', archive)
3765 else:
3766 print('fatal: --archive is only supported when initializing a new '
3767 'workspace.', file=sys.stderr)
3768 print('Either delete the .repo folder in this workspace, or initialize '
3769 'in another location.', file=sys.stderr)
3770 return False
3771
3772 if mirror:
3773 if is_new:
3774 self.config.SetBoolean('repo.mirror', mirror)
3775 else:
3776 print('fatal: --mirror is only supported when initializing a new '
3777 'workspace.', file=sys.stderr)
3778 print('Either delete the .repo folder in this workspace, or initialize '
3779 'in another location.', file=sys.stderr)
3780 return False
3781
3782 if partial_clone is not None:
3783 if mirror:
3784 print('fatal: --mirror and --partial-clone are mutually exclusive',
3785 file=sys.stderr)
3786 return False
3787 self.config.SetBoolean('repo.partialclone', partial_clone)
3788 if clone_filter:
3789 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jones55ee3042022-04-06 17:10:21 +00003790 elif self.partial_clone:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003791 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003792 else:
3793 clone_filter = None
3794
3795 if partial_clone_exclude is not None:
3796 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3797
3798 if clone_bundle is None:
3799 clone_bundle = False if partial_clone else True
3800 else:
3801 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3802
3803 if submodules:
3804 self.config.SetBoolean('repo.submodules', submodules)
3805
3806 if git_lfs is not None:
3807 if git_lfs:
3808 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3809
3810 self.config.SetBoolean('repo.git-lfs', git_lfs)
3811 if not is_new:
3812 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3813 ' Existing projects will require manual updates.\n', file=sys.stderr)
3814
3815 if use_superproject is not None:
3816 self.config.SetBoolean('repo.superproject', use_superproject)
3817
LaMont Jones0165e202022-04-27 17:34:42 +00003818 if not standalone_manifest:
3819 if not self.Sync_NetworkHalf(
3820 is_new=is_new, quiet=not verbose, verbose=verbose,
3821 clone_bundle=clone_bundle, current_branch_only=current_branch_only,
3822 tags=tags, submodules=submodules, clone_filter=clone_filter,
3823 partial_clone_exclude=self.manifest.PartialCloneExclude):
3824 r = self.GetRemote(self.remote.name)
3825 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
LaMont Jones9b03f152022-03-29 23:01:18 +00003826
LaMont Jones0165e202022-04-27 17:34:42 +00003827 # Better delete the manifest git dir if we created it; otherwise next
3828 # time (when user fixes problems) we won't go through the "is_new" logic.
3829 if is_new:
3830 platform_utils.rmtree(self.gitdir)
LaMont Jones9b03f152022-03-29 23:01:18 +00003831 return False
3832
LaMont Jones0165e202022-04-27 17:34:42 +00003833 if manifest_branch:
3834 self.MetaBranchSwitch(submodules=submodules)
3835
3836 syncbuf = SyncBuffer(self.config)
3837 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3838 syncbuf.Finish()
3839
3840 if is_new or self.CurrentBranch is None:
3841 if not self.StartBranch('default'):
3842 print('fatal: cannot create default in manifest', file=sys.stderr)
3843 return False
3844
3845 if not manifest_name:
3846 print('fatal: manifest name (-m) is required.', file=sys.stderr)
3847 return False
3848
3849 elif is_new:
3850 # This is a new standalone manifest.
3851 manifest_name = 'default.xml'
3852 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3853 dest = os.path.join(self.worktree, manifest_name)
3854 os.makedirs(os.path.dirname(dest), exist_ok=True)
3855 with open(dest, 'wb') as f:
3856 f.write(manifest_data)
LaMont Jones409407a2022-04-05 21:21:56 +00003857
3858 try:
3859 self.manifest.Link(manifest_name)
3860 except ManifestParseError as e:
3861 print("fatal: manifest '%s' not available" % manifest_name,
3862 file=sys.stderr)
3863 print('fatal: %s' % str(e), file=sys.stderr)
3864 return False
3865
LaMont Jones55ee3042022-04-06 17:10:21 +00003866 if not this_manifest_only:
3867 for submanifest in self.manifest.submanifests.values():
LaMont Jonesb90a4222022-04-14 15:00:09 +00003868 spec = submanifest.ToSubmanifestSpec()
LaMont Jones55ee3042022-04-06 17:10:21 +00003869 submanifest.repo_client.manifestProject.Sync(
3870 manifest_url=spec.manifestUrl,
3871 manifest_branch=spec.revision,
3872 standalone_manifest=standalone_manifest,
3873 groups=self.manifest_groups,
3874 platform=platform,
3875 mirror=mirror,
3876 dissociate=dissociate,
3877 reference=reference,
3878 worktree=worktree,
3879 submodules=submodules,
3880 archive=archive,
3881 partial_clone=partial_clone,
3882 clone_filter=clone_filter,
3883 partial_clone_exclude=partial_clone_exclude,
3884 clone_bundle=clone_bundle,
3885 git_lfs=git_lfs,
3886 use_superproject=use_superproject,
3887 verbose=verbose,
3888 current_branch_only=current_branch_only,
3889 tags=tags,
3890 depth=depth,
3891 git_event_log=git_event_log,
3892 manifest_name=spec.manifestName,
3893 this_manifest_only=False,
3894 outer_manifest=False,
3895 )
LaMont Jones409407a2022-04-05 21:21:56 +00003896
LaMont Jones0ddb6772022-05-20 09:11:54 +00003897 # Lastly, if the manifest has a <superproject> then have the superproject
LaMont Jonesff6b1da2022-06-01 21:03:34 +00003898 # sync it (if it will be used).
3899 if git_superproject.UseSuperproject(use_superproject, self.manifest):
LaMont Jones0ddb6772022-05-20 09:11:54 +00003900 sync_result = self.manifest.superproject.Sync(git_event_log)
LaMont Jonesa2ff20d2022-04-07 16:49:06 +00003901 if not sync_result.success:
3902 print('warning: git update of superproject for '
3903 f'{self.manifest.path_prefix} failed, repo sync will not use '
3904 'superproject to fetch source; while this error is not fatal, '
3905 'and you can continue to run repo sync, please run repo init '
3906 'with the --no-use-superproject option to stop seeing this '
3907 'warning', file=sys.stderr)
3908 if sync_result.fatal and use_superproject is not None:
3909 return False
LaMont Jones409407a2022-04-05 21:21:56 +00003910
LaMont Jones9b03f152022-03-29 23:01:18 +00003911 return True
3912
3913 def _ConfigureDepth(self, depth):
3914 """Configure the depth we'll sync down.
3915
3916 Args:
3917 depth: an int, how deep of a partial clone to create.
3918 """
3919 # Opt.depth will be non-None if user actually passed --depth to repo init.
3920 if depth is not None:
3921 if depth > 0:
3922 # Positive values will set the depth.
3923 depth = str(depth)
3924 else:
3925 # Negative numbers will clear the depth; passing None to SetString
3926 # will do that.
3927 depth = None
3928
3929 # We store the depth in the main manifest project.
3930 self.config.SetString('repo.depth', depth)