blob: 2989e09d20b11c7f80951de8a1189545d458823e [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
Dave Borowitzb42b4742012-10-31 12:27:27 -070032from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070033from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
34 ID_RE
Remy Bohmer16c13282020-09-10 10:38:04 +020035from error import GitError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040036from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080037from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070038import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040039import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040040from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050042from 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 -070043
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070044
George Engelbrecht9bc283e2020-04-02 12:36:09 -060045# Maximum sleep time allowed during retries.
46MAXIMUM_RETRY_SLEEP_SEC = 3600.0
47# +-10% random jitter is added to each Fetches retry sleep duration.
48RETRY_JITTER_PERCENT = 0.1
49
50
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070051def _lwrite(path, content):
52 lock = '%s.lock' % path
53
Remy Bohmer169b0212020-11-21 10:57:52 +010054 # Maintain Unix line endings on all OS's to match git behavior.
55 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070056 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070057
58 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070059 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070060 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080061 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 raise
63
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070064
Shawn O. Pearce48244782009-04-16 08:25:57 -070065def _error(fmt, *args):
66 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070067 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070068
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070069
David Pursehousef33929d2015-08-24 14:39:14 +090070def _warn(fmt, *args):
71 msg = fmt % args
72 print('warn: %s' % msg, file=sys.stderr)
73
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070074
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070075def not_rev(r):
76 return '^' + r
77
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070078
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080079def sq(r):
80 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080081
David Pursehouse819827a2020-02-12 15:20:19 +090082
Jonathan Nieder93719792015-03-17 11:29:58 -070083_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
85
Jonathan Nieder93719792015-03-17 11:29:58 -070086def _ProjectHooks():
87 """List the hooks present in the 'hooks' directory.
88
89 These hooks are project hooks and are copied to the '.git/hooks' directory
90 of all subprojects.
91
92 This function caches the list of hooks (based on the contents of the
93 'repo/hooks' directory) on the first call.
94
95 Returns:
96 A list of absolute paths to all of the files in the hooks directory.
97 """
98 global _project_hook_list
99 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700100 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700101 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700102 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700103 return _project_hook_list
104
105
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700106class DownloadedChange(object):
107 _commit_cache = None
108
109 def __init__(self, project, base, change_id, ps_id, commit):
110 self.project = project
111 self.base = base
112 self.change_id = change_id
113 self.ps_id = ps_id
114 self.commit = commit
115
116 @property
117 def commits(self):
118 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700119 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
120 '--abbrev-commit',
121 '--pretty=oneline',
122 '--reverse',
123 '--date-order',
124 not_rev(self.base),
125 self.commit,
126 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700127 return self._commit_cache
128
129
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700130class ReviewableBranch(object):
131 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400132 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700133
134 def __init__(self, project, branch, base):
135 self.project = project
136 self.branch = branch
137 self.base = base
138
139 @property
140 def name(self):
141 return self.branch.name
142
143 @property
144 def commits(self):
145 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400146 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
147 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
148 try:
149 self._commit_cache = self.project.bare_git.rev_list(*args)
150 except GitError:
151 # We weren't able to probe the commits for this branch. Was it tracking
152 # a branch that no longer exists? If so, return no commits. Otherwise,
153 # rethrow the error as we don't know what's going on.
154 if self.base_exists:
155 raise
156
157 self._commit_cache = []
158
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159 return self._commit_cache
160
161 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 def unabbrev_commits(self):
163 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700164 for commit in self.project.bare_git.rev_list(not_rev(self.base),
165 R_HEADS + self.name,
166 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800167 r[commit[0:8]] = commit
168 return r
169
170 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700172 return self.project.bare_git.log('--pretty=format:%cd',
173 '-n', '1',
174 R_HEADS + self.name,
175 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176
Mike Frysinger6da17752019-09-11 18:43:17 -0400177 @property
178 def base_exists(self):
179 """Whether the branch we're tracking exists.
180
181 Normally it should, but sometimes branches we track can get deleted.
182 """
183 if self._base_exists is None:
184 try:
185 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
186 # If we're still here, the base branch exists.
187 self._base_exists = True
188 except GitError:
189 # If we failed to verify, the base branch doesn't exist.
190 self._base_exists = False
191
192 return self._base_exists
193
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700194 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500195 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700196 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500197 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500198 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200199 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700200 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200201 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200202 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800203 validate_certs=True,
204 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500205 self.project.UploadForReview(branch=self.name,
206 people=people,
207 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700208 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500209 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500210 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200211 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700212 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200213 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200214 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800215 validate_certs=validate_certs,
216 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700218 def GetPublishedRefs(self):
219 refs = {}
220 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700221 self.branch.remote.SshReviewUrl(self.project.UserEmail),
222 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700223 for line in output.split('\n'):
224 try:
225 (sha, ref) = line.split()
226 refs[sha] = ref
227 except ValueError:
228 pass
229
230 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700232
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700234
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500236 super().__init__(config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100237 self.project = self.printer('header', attr='bold')
238 self.branch = self.printer('header', attr='bold')
239 self.nobranch = self.printer('nobranch', fg='red')
240 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241
Anthony King7bdac712014-07-16 12:56:40 +0100242 self.added = self.printer('added', fg='green')
243 self.changed = self.printer('changed', fg='red')
244 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245
246
247class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700248
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700249 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500250 super().__init__(config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100251 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400252 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700254
Jack Neus6ea0cae2021-07-20 20:52:33 +0000255class Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700256
James W. Mills24c13082012-04-12 15:04:13 -0500257 def __init__(self, name, value, keep):
258 self.name = name
259 self.value = value
260 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700261
Jack Neus6ea0cae2021-07-20 20:52:33 +0000262 def __eq__(self, other):
263 if not isinstance(other, Annotation):
264 return False
265 return self.__dict__ == other.__dict__
266
267 def __lt__(self, other):
268 # This exists just so that lists of Annotation objects can be sorted, for
269 # use in comparisons.
270 if not isinstance(other, Annotation):
271 raise ValueError('comparison is not between two Annotation objects')
272 if self.name == other.name:
273 if self.value == other.value:
274 return self.keep < other.keep
275 return self.value < other.value
276 return self.name < other.name
277
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700278
Mike Frysingere6a202f2019-08-02 15:57:57 -0400279def _SafeExpandPath(base, subpath, skipfinal=False):
280 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700281
Mike Frysingere6a202f2019-08-02 15:57:57 -0400282 We make sure no intermediate symlinks are traversed, and that the final path
283 is not a special file (e.g. not a socket or fifo).
284
285 NB: We rely on a number of paths already being filtered out while parsing the
286 manifest. See the validation logic in manifest_xml.py for more details.
287 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500288 # Split up the path by its components. We can't use os.path.sep exclusively
289 # as some platforms (like Windows) will convert / to \ and that bypasses all
290 # our constructed logic here. Especially since manifest authors only use
291 # / in their paths.
292 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
293 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400294 if skipfinal:
295 # Whether the caller handles the final component itself.
296 finalpart = components.pop()
297
298 path = base
299 for part in components:
300 if part in {'.', '..'}:
301 raise ManifestInvalidPathError(
302 '%s: "%s" not allowed in paths' % (subpath, part))
303
304 path = os.path.join(path, part)
305 if platform_utils.islink(path):
306 raise ManifestInvalidPathError(
307 '%s: traversing symlinks not allow' % (path,))
308
309 if os.path.exists(path):
310 if not os.path.isfile(path) and not platform_utils.isdir(path):
311 raise ManifestInvalidPathError(
312 '%s: only regular files & directories allowed' % (path,))
313
314 if skipfinal:
315 path = os.path.join(path, finalpart)
316
317 return path
318
319
320class _CopyFile(object):
321 """Container for <copyfile> manifest element."""
322
323 def __init__(self, git_worktree, src, topdir, dest):
324 """Register a <copyfile> request.
325
326 Args:
327 git_worktree: Absolute path to the git project checkout.
328 src: Relative path under |git_worktree| of file to read.
329 topdir: Absolute path to the top of the repo client checkout.
330 dest: Relative path under |topdir| of file to write.
331 """
332 self.git_worktree = git_worktree
333 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700334 self.src = src
335 self.dest = dest
336
337 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400338 src = _SafeExpandPath(self.git_worktree, self.src)
339 dest = _SafeExpandPath(self.topdir, self.dest)
340
341 if platform_utils.isdir(src):
342 raise ManifestInvalidPathError(
343 '%s: copying from directory not supported' % (self.src,))
344 if platform_utils.isdir(dest):
345 raise ManifestInvalidPathError(
346 '%s: copying to directory not allowed' % (self.dest,))
347
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348 # copy file if it does not exist or is out of date
349 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
350 try:
351 # remove existing file first, since it might be read-only
352 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800353 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400354 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200355 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700356 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200357 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700358 shutil.copy(src, dest)
359 # make the file read-only
360 mode = os.stat(dest)[stat.ST_MODE]
361 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
362 os.chmod(dest, mode)
363 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700364 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700365
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700366
Anthony King7bdac712014-07-16 12:56:40 +0100367class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400368 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700369
Mike Frysingere6a202f2019-08-02 15:57:57 -0400370 def __init__(self, git_worktree, src, topdir, dest):
371 """Register a <linkfile> request.
372
373 Args:
374 git_worktree: Absolute path to the git project checkout.
375 src: Target of symlink relative to path under |git_worktree|.
376 topdir: Absolute path to the top of the repo client checkout.
377 dest: Relative path under |topdir| of symlink to create.
378 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700379 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400380 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500381 self.src = src
382 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500383
Wink Saville4c426ef2015-06-03 08:05:17 -0700384 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500385 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700386 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500387 try:
388 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800389 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800390 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500391 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700392 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700393 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500394 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700395 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500396 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700397 _error('Cannot link file %s to %s', relSrc, absDest)
398
399 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400400 """Link the self.src & self.dest paths.
401
402 Handles wild cards on the src linking all of the files in the source in to
403 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700404 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500405 # Some people use src="." to create stable links to projects. Lets allow
406 # that but reject all other uses of "." to keep things simple.
407 if self.src == '.':
408 src = self.git_worktree
409 else:
410 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400411
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300412 if not glob.has_magic(src):
413 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400414 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
415 # dest & src are absolute paths at this point. Make sure the target of
416 # the symlink is relative in the context of the repo client checkout.
417 relpath = os.path.relpath(src, os.path.dirname(dest))
418 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700419 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400420 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300421 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400422 if os.path.exists(dest) and not platform_utils.isdir(dest):
423 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700424 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400425 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700426 # Create a releative path from source dir to destination dir
427 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400428 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700429
430 # Get the source file name
431 srcFile = os.path.basename(absSrcFile)
432
433 # Now form the final full paths to srcFile. They will be
434 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400435 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700436 relSrc = os.path.join(relSrcDir, srcFile)
437 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500438
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700439
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700440class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700441
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700442 def __init__(self,
443 name,
Anthony King7bdac712014-07-16 12:56:40 +0100444 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700445 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100446 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700447 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700448 orig_name=None,
449 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700450 self.name = name
451 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700452 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700453 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100454 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700455 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700456 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700457
Ian Kasprzak0286e312021-02-05 10:06:18 -0800458
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700459class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600460 # These objects can be shared between several working trees.
Mike Frysinger41289c62021-12-20 17:30:33 -0500461 shareable_dirs = ['hooks', 'objects', 'rr-cache']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700462
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700463 def __init__(self,
464 manifest,
465 name,
466 remote,
467 gitdir,
David James8d201162013-10-11 17:03:19 -0700468 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700469 worktree,
470 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700471 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800472 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100473 rebase=True,
474 groups=None,
475 sync_c=False,
476 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900477 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100478 clone_depth=None,
479 upstream=None,
480 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500481 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100482 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900483 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700484 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600485 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700486 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800487 """Init a Project object.
488
489 Args:
490 manifest: The XmlManifest object.
491 name: The `name` attribute of manifest.xml's project element.
492 remote: RemoteSpec object specifying its remote's properties.
493 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700494 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800495 worktree: Absolute path of git working tree.
496 relpath: Relative path of git working tree to repo's top directory.
497 revisionExpr: The `revision` attribute of manifest.xml's project element.
498 revisionId: git commit id for checking out.
499 rebase: The `rebase` attribute of manifest.xml's project element.
500 groups: The `groups` attribute of manifest.xml's project element.
501 sync_c: The `sync-c` attribute of manifest.xml's project element.
502 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900503 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800504 upstream: The `upstream` attribute of manifest.xml's project element.
505 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500506 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800507 is_derived: False if the project was explicitly defined in the manifest;
508 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400509 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900510 optimized_fetch: If True, when a project is set to a sha1 revision, only
511 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600512 retry_fetches: Retry remote fetches n times upon receiving transient error
513 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700514 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800515 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400516 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700517 self.name = name
518 self.remote = remote
Michael Kelly37c21c22020-06-13 02:10:40 -0700519 self.UpdatePaths(relpath, worktree, gitdir, objdir)
Michael Kelly2f3c3312020-07-21 19:40:38 -0700520 self.SetRevision(revisionExpr, revisionId=revisionId)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700521
Mike Pontillod3153822012-02-28 11:53:24 -0800522 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700523 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700524 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800525 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900526 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900527 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700528 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800529 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500530 # NB: Do not use this setting in __init__ to change behavior so that the
531 # manifest.git checkout can inspect & change it after instantiating. See
532 # the XmlManifest init code for more info.
533 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800534 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900535 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600536 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800537 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800538
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700539 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500541 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500542 self.annotations = []
Bryan Jacobsf609f912013-05-06 13:36:24 -0400543 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700544 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700545
Doug Anderson37282b42011-03-04 11:54:18 -0800546 # This will be filled in if a project is later identified to be the
547 # project containing repo hooks.
548 self.enabled_repo_hooks = []
549
LaMont Jonescc879a92021-11-18 22:40:18 +0000550 def RelPath(self, local=True):
551 """Return the path for the project relative to a manifest.
552
553 Args:
554 local: a boolean, if True, the path is relative to the local
555 (sub)manifest. If false, the path is relative to the
556 outermost manifest.
557 """
558 if local:
559 return self.relpath
560 return os.path.join(self.manifest.path_prefix, self.relpath)
561
Michael Kelly2f3c3312020-07-21 19:40:38 -0700562 def SetRevision(self, revisionExpr, revisionId=None):
563 """Set revisionId based on revision expression and id"""
564 self.revisionExpr = revisionExpr
565 if revisionId is None and revisionExpr and IsId(revisionExpr):
566 self.revisionId = self.revisionExpr
567 else:
568 self.revisionId = revisionId
569
Michael Kelly37c21c22020-06-13 02:10:40 -0700570 def UpdatePaths(self, relpath, worktree, gitdir, objdir):
571 """Update paths used by this project"""
572 self.gitdir = gitdir.replace('\\', '/')
573 self.objdir = objdir.replace('\\', '/')
574 if worktree:
575 self.worktree = os.path.normpath(worktree).replace('\\', '/')
576 else:
577 self.worktree = None
578 self.relpath = relpath
579
580 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
581 defaults=self.manifest.globalConfig)
582
583 if self.worktree:
584 self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
585 else:
586 self.work_git = None
587 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
588 self.bare_ref = GitRefs(self.gitdir)
589 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
590
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700591 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800592 def Derived(self):
593 return self.is_derived
594
595 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700596 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700597 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700598
599 @property
600 def CurrentBranch(self):
601 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400602
603 The branch name omits the 'refs/heads/' prefix.
604 None is returned if the project is on a detached HEAD, or if the work_git is
605 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700606 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400607 try:
608 b = self.work_git.GetHead()
609 except NoManifestException:
610 # If the local checkout is in a bad state, don't barf. Let the callers
611 # process this like the head is unreadable.
612 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700613 if b.startswith(R_HEADS):
614 return b[len(R_HEADS):]
615 return None
616
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700617 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500618 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
619 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
620 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200621
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700622 def IsDirty(self, consider_untracked=True):
623 """Is the working directory modified in some way?
624 """
625 self.work_git.update_index('-q',
626 '--unmerged',
627 '--ignore-missing',
628 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900629 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700630 return True
631 if self.work_git.DiffZ('diff-files'):
632 return True
633 if consider_untracked and self.work_git.LsOthers():
634 return True
635 return False
636
637 _userident_name = None
638 _userident_email = None
639
640 @property
641 def UserName(self):
642 """Obtain the user's personal name.
643 """
644 if self._userident_name is None:
645 self._LoadUserIdentity()
646 return self._userident_name
647
648 @property
649 def UserEmail(self):
650 """Obtain the user's email address. This is very likely
651 to be their Gerrit login.
652 """
653 if self._userident_email is None:
654 self._LoadUserIdentity()
655 return self._userident_email
656
657 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900658 u = self.bare_git.var('GIT_COMMITTER_IDENT')
659 m = re.compile("^(.*) <([^>]*)> ").match(u)
660 if m:
661 self._userident_name = m.group(1)
662 self._userident_email = m.group(2)
663 else:
664 self._userident_name = ''
665 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700666
667 def GetRemote(self, name):
668 """Get the configuration for a single remote.
669 """
670 return self.config.GetRemote(name)
671
672 def GetBranch(self, name):
673 """Get the configuration for a single branch.
674 """
675 return self.config.GetBranch(name)
676
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700677 def GetBranches(self):
678 """Get all existing local branches.
679 """
680 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900681 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700682 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700683
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530684 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700685 if name.startswith(R_HEADS):
686 name = name[len(R_HEADS):]
687 b = self.GetBranch(name)
688 b.current = name == current
689 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900690 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700691 heads[name] = b
692
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530693 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700694 if name.startswith(R_PUB):
695 name = name[len(R_PUB):]
696 b = heads.get(name)
697 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900698 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700699
700 return heads
701
Colin Cross5acde752012-03-28 20:15:45 -0700702 def MatchesGroups(self, manifest_groups):
703 """Returns true if the manifest groups specified at init should cause
704 this project to be synced.
705 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700706 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700707
708 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700709 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700710 manifest_groups: "-group1,group2"
711 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500712
713 The special manifest group "default" will match any project that
714 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700715 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500716 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700717 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700718 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500719 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700720
Conley Owens971de8e2012-04-16 10:36:08 -0700721 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700722 for group in expanded_manifest_groups:
723 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700724 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700725 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700726 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700727
Conley Owens971de8e2012-04-16 10:36:08 -0700728 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700729
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700730# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700731 def UncommitedFiles(self, get_all=True):
732 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700734 Args:
735 get_all: a boolean, if True - get information about all different
736 uncommitted files. If False - return as soon as any kind of
737 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500738 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700739 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500740 self.work_git.update_index('-q',
741 '--unmerged',
742 '--ignore-missing',
743 '--refresh')
744 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700745 details.append("rebase in progress")
746 if not get_all:
747 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500748
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700749 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
750 if changes:
751 details.extend(changes)
752 if not get_all:
753 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500754
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700755 changes = self.work_git.DiffZ('diff-files').keys()
756 if changes:
757 details.extend(changes)
758 if not get_all:
759 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500760
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700761 changes = self.work_git.LsOthers()
762 if changes:
763 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500764
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700765 return details
766
767 def HasChanges(self):
768 """Returns true if there are uncommitted changes.
769 """
770 if self.UncommitedFiles(get_all=False):
771 return True
772 else:
773 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500774
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600775 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700776 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200777
778 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200779 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600780 quiet: If True then only print the project name. Do not print
781 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700783 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700784 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200785 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700786 print(file=output_redir)
787 print('project %s/' % self.relpath, file=output_redir)
788 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700789 return
790
791 self.work_git.update_index('-q',
792 '--unmerged',
793 '--ignore-missing',
794 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700795 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700796 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
797 df = self.work_git.DiffZ('diff-files')
798 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100799 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700800 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700801
802 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700803 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200804 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700805 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700806
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600807 if quiet:
808 out.nl()
809 return 'DIRTY'
810
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811 branch = self.CurrentBranch
812 if branch is None:
813 out.nobranch('(*** NO BRANCH ***)')
814 else:
815 out.branch('branch %s', branch)
816 out.nl()
817
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700818 if rb:
819 out.important('prior sync failed; rebase still in progress')
820 out.nl()
821
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700822 paths = list()
823 paths.extend(di.keys())
824 paths.extend(df.keys())
825 paths.extend(do)
826
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530827 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900828 try:
829 i = di[p]
830 except KeyError:
831 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900833 try:
834 f = df[p]
835 except KeyError:
836 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200837
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900838 if i:
839 i_status = i.status.upper()
840 else:
841 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700842
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900843 if f:
844 f_status = f.status.lower()
845 else:
846 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700847
848 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800849 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700850 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700851 else:
852 line = ' %s%s\t%s' % (i_status, f_status, p)
853
854 if i and not f:
855 out.added('%s', line)
856 elif (i and f) or (not i and f):
857 out.changed('%s', line)
858 elif not i and not f:
859 out.untracked('%s', line)
860 else:
861 out.write('%s', line)
862 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200863
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700864 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500866 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867 """Prints the status of the repository to stdout.
868 """
869 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500870 if output_redir:
871 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872 cmd = ['diff']
873 if out.is_on:
874 cmd.append('--color')
875 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300876 if absolute_paths:
877 cmd.append('--src-prefix=a/%s/' % self.relpath)
878 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700879 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400880 try:
881 p = GitCommand(self,
882 cmd,
883 capture_stdout=True,
884 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500885 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400886 except GitError as e:
887 out.nl()
888 out.project('project %s/' % self.relpath)
889 out.nl()
890 out.fail('%s', str(e))
891 out.nl()
892 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500893 if p.stdout:
894 out.nl()
895 out.project('project %s/' % self.relpath)
896 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500897 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400898 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700900# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900901 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902 """Was the branch published (uploaded) for code review?
903 If so, returns the SHA-1 hash of the last published
904 state for the branch.
905 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700906 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900907 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700908 try:
909 return self.bare_git.rev_parse(key)
910 except GitError:
911 return None
912 else:
913 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900914 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700915 except KeyError:
916 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917
David Pursehouse8a68ff92012-09-24 12:15:13 +0900918 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919 """Prunes any stale published refs.
920 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900921 if all_refs is None:
922 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923 heads = set()
924 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530925 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926 if name.startswith(R_HEADS):
927 heads.add(name)
928 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900929 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530931 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932 n = name[len(R_PUB):]
933 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900934 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700935
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700936 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937 """List any branches which can be uploaded for review.
938 """
939 heads = {}
940 pubed = {}
941
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530942 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900944 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900946 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947
948 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530949 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900950 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700952 if selected_branch and branch != selected_branch:
953 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800955 rb = self.GetUploadableBranch(branch)
956 if rb:
957 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958 return ready
959
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800960 def GetUploadableBranch(self, branch_name):
961 """Get a single uploadable branch, or None.
962 """
963 branch = self.GetBranch(branch_name)
964 base = branch.LocalMerge
965 if branch.LocalMerge:
966 rb = ReviewableBranch(self, branch, base)
967 if rb.commits:
968 return rb
969 return None
970
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700971 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100972 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500973 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700974 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500975 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500976 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200977 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700978 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200979 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200980 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800981 validate_certs=True,
982 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 """Uploads the named branch for code review.
984 """
985 if branch is None:
986 branch = self.CurrentBranch
987 if branch is None:
988 raise GitError('not currently on a branch')
989
990 branch = self.GetBranch(branch)
991 if not branch.LocalMerge:
992 raise GitError('branch %s does not track a remote' % branch.name)
993 if not branch.remote.review:
994 raise GitError('remote %s has no review url' % branch.remote.name)
995
Bryan Jacobsf609f912013-05-06 13:36:24 -0400996 if dest_branch is None:
997 dest_branch = self.dest_branch
998 if dest_branch is None:
999 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000 if not dest_branch.startswith(R_HEADS):
1001 dest_branch = R_HEADS + dest_branch
1002
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001003 if not branch.remote.projectname:
1004 branch.remote.projectname = self.name
1005 branch.remote.Save()
1006
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001007 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001008 if url is None:
1009 raise UploadError('review not configured')
1010 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001011 if dryrun:
1012 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001013
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001014 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001015 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001016
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001017 for push_option in (push_options or []):
1018 cmd.append('-o')
1019 cmd.append(push_option)
1020
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001021 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001022
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001023 if dest_branch.startswith(R_HEADS):
1024 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001025
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001026 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001027 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001028 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001029 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001030 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001031 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001032
David Pursehousef25a3702018-11-14 19:01:22 -08001033 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001034 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001035 if notify:
1036 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001037 if private:
1038 opts += ['private']
1039 if wip:
1040 opts += ['wip']
1041 if opts:
1042 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001043 cmd.append(ref_spec)
1044
Anthony King7bdac712014-07-16 12:56:40 +01001045 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001046 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001047
Mike Frysingerd7f86832020-11-19 19:18:46 -05001048 if not dryrun:
1049 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1050 self.bare_git.UpdateRef(R_PUB + branch.name,
1051 R_HEADS + branch.name,
1052 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001054# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001055 def _ExtractArchive(self, tarpath, path=None):
1056 """Extract the given tar on its current location
1057
1058 Args:
1059 - tarpath: The path to the actual tar file
1060
1061 """
1062 try:
1063 with tarfile.open(tarpath, 'r') as tar:
1064 tar.extractall(path=path)
1065 return True
1066 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001067 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001068 return False
1069
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001070 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001071 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001072 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001073 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001074 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001075 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001076 force_sync=False,
1077 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001078 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001079 archive=False,
1080 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001081 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001082 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001083 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001084 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001085 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001086 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001087 """Perform only the network IO portion of the sync process.
1088 Local working directory/branch state is not affected.
1089 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001090 if archive and not isinstance(self, MetaProject):
1091 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001092 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001093 return False
1094
1095 name = self.relpath.replace('\\', '/')
1096 name = name.replace('/', '_')
1097 tarpath = '%s.tar' % name
1098 topdir = self.manifest.topdir
1099
1100 try:
1101 self._FetchArchive(tarpath, cwd=topdir)
1102 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001103 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001104 return False
1105
1106 # From now on, we only need absolute tarpath
1107 tarpath = os.path.join(topdir, tarpath)
1108
1109 if not self._ExtractArchive(tarpath, path=topdir):
1110 return False
1111 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001112 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001113 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001114 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001115 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001116 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001117
1118 # If the shared object dir already exists, don't try to rebootstrap with a
1119 # clone bundle download. We should have the majority of objects already.
1120 if clone_bundle and os.path.exists(self.objdir):
1121 clone_bundle = False
1122
Raman Tennetif32f2432021-04-12 20:57:25 -07001123 if self.name in partial_clone_exclude:
1124 clone_bundle = True
1125 clone_filter = None
1126
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001127 if is_new is None:
1128 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001129 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001130 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001131 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001132 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001133 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001134
1135 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001136 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001137 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001138 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001139 # This works for both absolute and relative alternate directories.
1140 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001141 except IOError:
1142 alt_dir = None
1143 else:
1144 alt_dir = None
1145
Mike Frysingere50b6a72020-02-19 01:45:48 -05001146 if (clone_bundle
1147 and alt_dir is None
1148 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001149 is_new = False
1150
Mike Frysinger73561142021-05-03 01:10:09 -04001151 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001152 if self.sync_c:
1153 current_branch_only = True
1154 elif not self.manifest._loaded:
1155 # Manifest cannot check defaults until it syncs.
1156 current_branch_only = False
1157 elif self.manifest.default.sync_c:
1158 current_branch_only = True
1159
Mike Frysingerd68ed632021-05-03 01:21:35 -04001160 if tags is None:
1161 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001162
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001163 if self.clone_depth:
1164 depth = self.clone_depth
1165 else:
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001166 depth = self.manifest.manifestProject.depth
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001167
Mike Frysinger521d01b2020-02-17 01:51:49 -05001168 # See if we can skip the network fetch entirely.
1169 if not (optimized_fetch and
1170 (ID_RE.match(self.revisionExpr) and
1171 self._CheckForImmutableRevision())):
1172 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001173 initial=is_new,
1174 quiet=quiet, verbose=verbose, output_redir=output_redir,
1175 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001176 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001177 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001178 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001179 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001180 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001181
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001182 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00001183 dissociate = mp.dissociate
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001184 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001185 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001186 if os.path.exists(alternates_file):
1187 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001188 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1189 merge_output=bool(output_redir))
1190 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001191 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001192 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001193 return False
1194 platform_utils.remove(alternates_file)
1195
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001196 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001197 self._InitMRef()
1198 else:
1199 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001200 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1201 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001202 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001203
1204 def PostRepoUpgrade(self):
1205 self._InitHooks()
1206
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001207 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001208 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001209 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001210 for copyfile in self.copyfiles:
1211 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001212 for linkfile in self.linkfiles:
1213 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001214
Julien Camperguedd654222014-01-09 16:21:37 +01001215 def GetCommitRevisionId(self):
1216 """Get revisionId of a commit.
1217
1218 Use this method instead of GetRevisionId to get the id of the commit rather
1219 than the id of the current git object (for example, a tag)
1220
1221 """
1222 if not self.revisionExpr.startswith(R_TAGS):
1223 return self.GetRevisionId(self._allrefs)
1224
1225 try:
1226 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1227 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001228 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1229 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001230
David Pursehouse8a68ff92012-09-24 12:15:13 +09001231 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001232 if self.revisionId:
1233 return self.revisionId
1234
1235 rem = self.GetRemote(self.remote.name)
1236 rev = rem.ToLocal(self.revisionExpr)
1237
David Pursehouse8a68ff92012-09-24 12:15:13 +09001238 if all_refs is not None and rev in all_refs:
1239 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001240
1241 try:
1242 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1243 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001244 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1245 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001246
Raman Tenneti6a872c92021-01-14 19:17:50 -08001247 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001248 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001249 self.upstream = self.revisionExpr
1250
Raman Tenneti6a872c92021-01-14 19:17:50 -08001251 self.revisionId = revisionId
1252
Martin Kellye4e94d22017-03-21 16:05:12 -07001253 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001254 """Perform only the local IO portion of the sync process.
1255 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001256 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001257 if not os.path.exists(self.gitdir):
1258 syncbuf.fail(self,
1259 'Cannot checkout %s due to missing network sync; Run '
1260 '`repo sync -n %s` first.' %
1261 (self.name, self.name))
1262 return
1263
Martin Kellye4e94d22017-03-21 16:05:12 -07001264 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001265 all_refs = self.bare_ref.all
1266 self.CleanPublishedCache(all_refs)
1267 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001268
Mike Frysinger0458faa2021-03-10 23:35:44 -05001269 # Special case the root of the repo client checkout. Make sure it doesn't
1270 # contain files being checked out to dirs we don't allow.
1271 if self.relpath == '.':
1272 PROTECTED_PATHS = {'.repo'}
1273 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1274 bad_paths = paths & PROTECTED_PATHS
1275 if bad_paths:
1276 syncbuf.fail(self,
1277 'Refusing to checkout project that writes to protected '
1278 'paths: %s' % (', '.join(bad_paths),))
1279 return
1280
David Pursehouse1d947b32012-10-25 12:23:11 +09001281 def _doff():
1282 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001283 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001284
Martin Kellye4e94d22017-03-21 16:05:12 -07001285 def _dosubmodules():
1286 self._SyncSubmodules(quiet=True)
1287
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001288 head = self.work_git.GetHead()
1289 if head.startswith(R_HEADS):
1290 branch = head[len(R_HEADS):]
1291 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001292 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001293 except KeyError:
1294 head = None
1295 else:
1296 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001298 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299 # Currently on a detached HEAD. The user is assumed to
1300 # not have any local modifications worth worrying about.
1301 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001302 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001303 syncbuf.fail(self, _PriorSyncFailedError())
1304 return
1305
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001306 if head == revid:
1307 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001308 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001309 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001310 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001311 # The copy/linkfile config may have changed.
1312 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001313 return
1314 else:
1315 lost = self._revlist(not_rev(revid), HEAD)
1316 if lost:
1317 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001318
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001320 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001321 if submodules:
1322 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001323 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001324 syncbuf.fail(self, e)
1325 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001326 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001327 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001329 if head == revid:
1330 # No changes; don't do anything further.
1331 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001332 # The copy/linkfile config may have changed.
1333 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001334 return
1335
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001336 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001338 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001339 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001340 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001341 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001342 syncbuf.info(self,
1343 "leaving %s; does not track upstream",
1344 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001345 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001346 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001347 if submodules:
1348 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001349 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001350 syncbuf.fail(self, e)
1351 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001352 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001353 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001354
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001355 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001356
1357 # See if we can perform a fast forward merge. This can happen if our
1358 # branch isn't in the exact same state as we last published.
1359 try:
1360 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1361 # Skip the published logic.
1362 pub = False
1363 except GitError:
1364 pub = self.WasPublished(branch.name, all_refs)
1365
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001366 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001367 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368 if not_merged:
1369 if upstream_gain:
1370 # The user has published this branch and some of those
1371 # commits are not yet merged upstream. We do not want
1372 # to rewrite the published commits so we punt.
1373 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001374 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001375 "branch %s is published (but not merged) and is now "
1376 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001377 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001378 elif pub == head:
1379 # All published commits are merged, and thus we are a
1380 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001381 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001382 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001383 if submodules:
1384 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001385 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001387 # Examine the local commits not in the remote. Find the
1388 # last one attributed to this user, if any.
1389 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001390 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001391 last_mine = None
1392 cnt_mine = 0
1393 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001394 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001395 if committer_email == self.UserEmail:
1396 last_mine = commit_id
1397 cnt_mine += 1
1398
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001399 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001400 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001401
1402 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001403 syncbuf.fail(self, _DirtyError())
1404 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001405
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001406 # If the upstream switched on us, warn the user.
1407 #
1408 if branch.merge != self.revisionExpr:
1409 if branch.merge and self.revisionExpr:
1410 syncbuf.info(self,
1411 'manifest switched %s...%s',
1412 branch.merge,
1413 self.revisionExpr)
1414 elif branch.merge:
1415 syncbuf.info(self,
1416 'manifest no longer tracks %s',
1417 branch.merge)
1418
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001419 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001420 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001421 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001422 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001423 syncbuf.info(self,
1424 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001425 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001426
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001427 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001428 if not ID_RE.match(self.revisionExpr):
1429 # in case of manifest sync the revisionExpr might be a SHA1
1430 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001431 if not branch.merge.startswith('refs/'):
1432 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001433 branch.Save()
1434
Mike Pontillod3153822012-02-28 11:53:24 -08001435 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001436 def _docopyandlink():
1437 self._CopyAndLinkFiles()
1438
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001439 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001440 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001441 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001442 if submodules:
1443 syncbuf.later2(self, _dosubmodules)
1444 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001445 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001446 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001447 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001448 if submodules:
1449 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001450 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001451 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001452 syncbuf.fail(self, e)
1453 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001454 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001455 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001456 if submodules:
1457 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001458
Mike Frysingere6a202f2019-08-02 15:57:57 -04001459 def AddCopyFile(self, src, dest, topdir):
1460 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001461
Mike Frysingere6a202f2019-08-02 15:57:57 -04001462 No filesystem changes occur here. Actual copying happens later on.
1463
1464 Paths should have basic validation run on them before being queued.
1465 Further checking will be handled when the actual copy happens.
1466 """
1467 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1468
1469 def AddLinkFile(self, src, dest, topdir):
1470 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1471
1472 No filesystem changes occur here. Actual linking happens later on.
1473
1474 Paths should have basic validation run on them before being queued.
1475 Further checking will be handled when the actual link happens.
1476 """
1477 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001478
James W. Mills24c13082012-04-12 15:04:13 -05001479 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001480 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001481
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001482 def DownloadPatchSet(self, change_id, patch_id):
1483 """Download a single patch set of a single change to FETCH_HEAD.
1484 """
1485 remote = self.GetRemote(self.remote.name)
1486
1487 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001488 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001489 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001490 if GitCommand(self, cmd, bare=True).Wait() != 0:
1491 return None
1492 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001493 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001494 change_id,
1495 patch_id,
1496 self.bare_git.rev_parse('FETCH_HEAD'))
1497
Mike Frysingerc0d18662020-02-19 19:19:18 -05001498 def DeleteWorktree(self, quiet=False, force=False):
1499 """Delete the source checkout and any other housekeeping tasks.
1500
1501 This currently leaves behind the internal .repo/ cache state. This helps
1502 when switching branches or manifest changes get reverted as we don't have
1503 to redownload all the git objects. But we should do some GC at some point.
1504
1505 Args:
1506 quiet: Whether to hide normal messages.
1507 force: Always delete tree even if dirty.
1508
1509 Returns:
1510 True if the worktree was completely cleaned out.
1511 """
1512 if self.IsDirty():
1513 if force:
1514 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1515 (self.relpath,), file=sys.stderr)
1516 else:
1517 print('error: %s: Cannot remove project: uncommitted changes are '
1518 'present.\n' % (self.relpath,), file=sys.stderr)
1519 return False
1520
1521 if not quiet:
1522 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1523
1524 # Unlock and delink from the main worktree. We don't use git's worktree
1525 # remove because it will recursively delete projects -- we handle that
1526 # ourselves below. https://crbug.com/git/48
1527 if self.use_git_worktrees:
1528 needle = platform_utils.realpath(self.gitdir)
1529 # Find the git worktree commondir under .repo/worktrees/.
1530 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1531 assert output.startswith('worktree '), output
1532 commondir = output[9:]
1533 # Walk each of the git worktrees to see where they point.
1534 configs = os.path.join(commondir, 'worktrees')
1535 for name in os.listdir(configs):
1536 gitdir = os.path.join(configs, name, 'gitdir')
1537 with open(gitdir) as fp:
1538 relpath = fp.read().strip()
1539 # Resolve the checkout path and see if it matches this project.
1540 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1541 if fullpath == needle:
1542 platform_utils.rmtree(os.path.join(configs, name))
1543
1544 # Delete the .git directory first, so we're less likely to have a partially
1545 # working git repository around. There shouldn't be any git projects here,
1546 # so rmtree works.
1547
1548 # Try to remove plain files first in case of git worktrees. If this fails
1549 # for any reason, we'll fall back to rmtree, and that'll display errors if
1550 # it can't remove things either.
1551 try:
1552 platform_utils.remove(self.gitdir)
1553 except OSError:
1554 pass
1555 try:
1556 platform_utils.rmtree(self.gitdir)
1557 except OSError as e:
1558 if e.errno != errno.ENOENT:
1559 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1560 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1561 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1562 return False
1563
1564 # Delete everything under the worktree, except for directories that contain
1565 # another git project.
1566 dirs_to_remove = []
1567 failed = False
1568 for root, dirs, files in platform_utils.walk(self.worktree):
1569 for f in files:
1570 path = os.path.join(root, f)
1571 try:
1572 platform_utils.remove(path)
1573 except OSError as e:
1574 if e.errno != errno.ENOENT:
1575 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1576 failed = True
1577 dirs[:] = [d for d in dirs
1578 if not os.path.lexists(os.path.join(root, d, '.git'))]
1579 dirs_to_remove += [os.path.join(root, d) for d in dirs
1580 if os.path.join(root, d) not in dirs_to_remove]
1581 for d in reversed(dirs_to_remove):
1582 if platform_utils.islink(d):
1583 try:
1584 platform_utils.remove(d)
1585 except OSError as e:
1586 if e.errno != errno.ENOENT:
1587 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1588 failed = True
1589 elif not platform_utils.listdir(d):
1590 try:
1591 platform_utils.rmdir(d)
1592 except OSError as e:
1593 if e.errno != errno.ENOENT:
1594 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1595 failed = True
1596 if failed:
1597 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1598 file=sys.stderr)
1599 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1600 return False
1601
1602 # Try deleting parent dirs if they are empty.
1603 path = self.worktree
1604 while path != self.manifest.topdir:
1605 try:
1606 platform_utils.rmdir(path)
1607 except OSError as e:
1608 if e.errno != errno.ENOENT:
1609 break
1610 path = os.path.dirname(path)
1611
1612 return True
1613
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001614# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001615 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001616 """Create a new branch off the manifest's revision.
1617 """
Simran Basib9a1b732015-08-20 12:19:28 -07001618 if not branch_merge:
1619 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001620 head = self.work_git.GetHead()
1621 if head == (R_HEADS + name):
1622 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001623
David Pursehouse8a68ff92012-09-24 12:15:13 +09001624 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001625 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001626 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001627 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001628 capture_stdout=True,
1629 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001630
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001631 branch = self.GetBranch(name)
1632 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001633 branch.merge = branch_merge
1634 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1635 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001636
1637 if revision is None:
1638 revid = self.GetRevisionId(all_refs)
1639 else:
1640 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001641
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001642 if head.startswith(R_HEADS):
1643 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001644 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001645 except KeyError:
1646 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001647 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001648 ref = R_HEADS + name
1649 self.work_git.update_ref(ref, revid)
1650 self.work_git.symbolic_ref(HEAD, ref)
1651 branch.Save()
1652 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001653
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001654 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001655 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001656 capture_stdout=True,
1657 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001658 branch.Save()
1659 return True
1660 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001661
Wink Saville02d79452009-04-10 13:01:24 -07001662 def CheckoutBranch(self, name):
1663 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001664
1665 Args:
1666 name: The name of the branch to checkout.
1667
1668 Returns:
1669 True if the checkout succeeded; False if it didn't; None if the branch
1670 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001671 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001672 rev = R_HEADS + name
1673 head = self.work_git.GetHead()
1674 if head == rev:
1675 # Already on the branch
1676 #
1677 return True
Wink Saville02d79452009-04-10 13:01:24 -07001678
David Pursehouse8a68ff92012-09-24 12:15:13 +09001679 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001680 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001681 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001682 except KeyError:
1683 # Branch does not exist in this project
1684 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001685 return None
Wink Saville02d79452009-04-10 13:01:24 -07001686
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001687 if head.startswith(R_HEADS):
1688 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001689 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001690 except KeyError:
1691 head = None
1692
1693 if head == revid:
1694 # Same revision; just update HEAD to point to the new
1695 # target branch, but otherwise take no other action.
1696 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001697 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1698 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001699 return True
1700
1701 return GitCommand(self,
1702 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001703 capture_stdout=True,
1704 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001705
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001706 def AbandonBranch(self, name):
1707 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001708
1709 Args:
1710 name: The name of the branch to abandon.
1711
1712 Returns:
1713 True if the abandon succeeded; False if it didn't; None if the branch
1714 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001715 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001716 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001717 all_refs = self.bare_ref.all
1718 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001719 # Doesn't exist
1720 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001721
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001722 head = self.work_git.GetHead()
1723 if head == rev:
1724 # We can't destroy the branch while we are sitting
1725 # on it. Switch to a detached HEAD.
1726 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001727 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001728
David Pursehouse8a68ff92012-09-24 12:15:13 +09001729 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001730 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001731 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001732 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001733 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001734
1735 return GitCommand(self,
1736 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001737 capture_stdout=True,
1738 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001739
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001740 def PruneHeads(self):
1741 """Prune any topic branches already merged into upstream.
1742 """
1743 cb = self.CurrentBranch
1744 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001745 left = self._allrefs
1746 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001747 if name.startswith(R_HEADS):
1748 name = name[len(R_HEADS):]
1749 if cb is None or name != cb:
1750 kill.append(name)
1751
Mike Frysingera3794e92021-03-11 23:24:01 -05001752 # Minor optimization: If there's nothing to prune, then don't try to read
1753 # any project state.
1754 if not kill and not cb:
1755 return []
1756
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001757 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758 if cb is not None \
1759 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001760 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001761 self.work_git.DetachHead(HEAD)
1762 kill.append(cb)
1763
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001764 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001765 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001767 try:
1768 self.bare_git.DetachHead(rev)
1769
1770 b = ['branch', '-d']
1771 b.extend(kill)
1772 b = GitCommand(self, b, bare=True,
1773 capture_stdout=True,
1774 capture_stderr=True)
1775 b.Wait()
1776 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001777 if ID_RE.match(old):
1778 self.bare_git.DetachHead(old)
1779 else:
1780 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001781 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001782
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001783 for branch in kill:
1784 if (R_HEADS + branch) not in left:
1785 self.CleanPublishedCache()
1786 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001787
1788 if cb and cb not in kill:
1789 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001790 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001791
1792 kept = []
1793 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001794 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001795 branch = self.GetBranch(branch)
1796 base = branch.LocalMerge
1797 if not base:
1798 base = rev
1799 kept.append(ReviewableBranch(self, branch, base))
1800 return kept
1801
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001802# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001803 def GetRegisteredSubprojects(self):
1804 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001805
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001806 def rec(subprojects):
1807 if not subprojects:
1808 return
1809 result.extend(subprojects)
1810 for p in subprojects:
1811 rec(p.subprojects)
1812 rec(self.subprojects)
1813 return result
1814
1815 def _GetSubmodules(self):
1816 # Unfortunately we cannot call `git submodule status --recursive` here
1817 # because the working tree might not exist yet, and it cannot be used
1818 # without a working tree in its current implementation.
1819
1820 def get_submodules(gitdir, rev):
1821 # Parse .gitmodules for submodule sub_paths and sub_urls
1822 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1823 if not sub_paths:
1824 return []
1825 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1826 # revision of submodule repository
1827 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1828 submodules = []
1829 for sub_path, sub_url in zip(sub_paths, sub_urls):
1830 try:
1831 sub_rev = sub_revs[sub_path]
1832 except KeyError:
1833 # Ignore non-exist submodules
1834 continue
1835 submodules.append((sub_rev, sub_path, sub_url))
1836 return submodules
1837
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001838 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1839 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001840
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001841 def parse_gitmodules(gitdir, rev):
1842 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1843 try:
Anthony King7bdac712014-07-16 12:56:40 +01001844 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1845 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001846 except GitError:
1847 return [], []
1848 if p.Wait() != 0:
1849 return [], []
1850
1851 gitmodules_lines = []
1852 fd, temp_gitmodules_path = tempfile.mkstemp()
1853 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001854 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001855 os.close(fd)
1856 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001857 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1858 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001859 if p.Wait() != 0:
1860 return [], []
1861 gitmodules_lines = p.stdout.split('\n')
1862 except GitError:
1863 return [], []
1864 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001865 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001866
1867 names = set()
1868 paths = {}
1869 urls = {}
1870 for line in gitmodules_lines:
1871 if not line:
1872 continue
1873 m = re_path.match(line)
1874 if m:
1875 names.add(m.group(1))
1876 paths[m.group(1)] = m.group(2)
1877 continue
1878 m = re_url.match(line)
1879 if m:
1880 names.add(m.group(1))
1881 urls[m.group(1)] = m.group(2)
1882 continue
1883 names = sorted(names)
1884 return ([paths.get(name, '') for name in names],
1885 [urls.get(name, '') for name in names])
1886
1887 def git_ls_tree(gitdir, rev, paths):
1888 cmd = ['ls-tree', rev, '--']
1889 cmd.extend(paths)
1890 try:
Anthony King7bdac712014-07-16 12:56:40 +01001891 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1892 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001893 except GitError:
1894 return []
1895 if p.Wait() != 0:
1896 return []
1897 objects = {}
1898 for line in p.stdout.split('\n'):
1899 if not line.strip():
1900 continue
1901 object_rev, object_path = line.split()[2:4]
1902 objects[object_path] = object_rev
1903 return objects
1904
1905 try:
1906 rev = self.GetRevisionId()
1907 except GitError:
1908 return []
1909 return get_submodules(self.gitdir, rev)
1910
1911 def GetDerivedSubprojects(self):
1912 result = []
1913 if not self.Exists:
1914 # If git repo does not exist yet, querying its submodules will
1915 # mess up its states; so return here.
1916 return result
1917 for rev, path, url in self._GetSubmodules():
1918 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001919 relpath, worktree, gitdir, objdir = \
1920 self.manifest.GetSubprojectPaths(self, name, path)
1921 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001922 if project:
1923 result.extend(project.GetDerivedSubprojects())
1924 continue
David James8d201162013-10-11 17:03:19 -07001925
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001926 if url.startswith('..'):
1927 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001928 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001929 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001930 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001931 review=self.remote.review,
1932 revision=self.remote.revision)
1933 subproject = Project(manifest=self.manifest,
1934 name=name,
1935 remote=remote,
1936 gitdir=gitdir,
1937 objdir=objdir,
1938 worktree=worktree,
1939 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001940 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001941 revisionId=rev,
1942 rebase=self.rebase,
1943 groups=self.groups,
1944 sync_c=self.sync_c,
1945 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001946 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001947 parent=self,
1948 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001949 result.append(subproject)
1950 result.extend(subproject.GetDerivedSubprojects())
1951 return result
1952
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001953# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001954 def EnableRepositoryExtension(self, key, value='true', version=1):
1955 """Enable git repository extension |key| with |value|.
1956
1957 Args:
1958 key: The extension to enabled. Omit the "extensions." prefix.
1959 value: The value to use for the extension.
1960 version: The minimum git repository version needed.
1961 """
1962 # Make sure the git repo version is new enough already.
1963 found_version = self.config.GetInt('core.repositoryFormatVersion')
1964 if found_version is None:
1965 found_version = 0
1966 if found_version < version:
1967 self.config.SetString('core.repositoryFormatVersion', str(version))
1968
1969 # Enable the extension!
1970 self.config.SetString('extensions.%s' % (key,), value)
1971
Mike Frysinger50a81de2020-09-06 15:51:21 -04001972 def ResolveRemoteHead(self, name=None):
1973 """Find out what the default branch (HEAD) points to.
1974
1975 Normally this points to refs/heads/master, but projects are moving to main.
1976 Support whatever the server uses rather than hardcoding "master" ourselves.
1977 """
1978 if name is None:
1979 name = self.remote.name
1980
1981 # The output will look like (NB: tabs are separators):
1982 # ref: refs/heads/master HEAD
1983 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1984 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1985
1986 for line in output.splitlines():
1987 lhs, rhs = line.split('\t', 1)
1988 if rhs == 'HEAD' and lhs.startswith('ref:'):
1989 return lhs[4:].strip()
1990
1991 return None
1992
Zac Livingstone4332262017-06-16 08:56:09 -06001993 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001994 try:
1995 # if revision (sha or tag) is not present then following function
1996 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08001997 self.bare_git.rev_list('-1', '--missing=allow-any',
1998 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00001999 if self.upstream:
2000 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
2001 self.bare_git.rev_list('-1', '--missing=allow-any',
2002 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002003 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002004 return True
2005 except GitError:
2006 # There is no such persistent revision. We have to fetch it.
2007 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002008
Julien Campergue335f5ef2013-10-16 11:02:35 +02002009 def _FetchArchive(self, tarpath, cwd=None):
2010 cmd = ['archive', '-v', '-o', tarpath]
2011 cmd.append('--remote=%s' % self.remote.url)
2012 cmd.append('--prefix=%s/' % self.relpath)
2013 cmd.append(self.revisionExpr)
2014
2015 command = GitCommand(self, cmd, cwd=cwd,
2016 capture_stdout=True,
2017 capture_stderr=True)
2018
2019 if command.Wait() != 0:
2020 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2021
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002022 def _RemoteFetch(self, name=None,
2023 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002024 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002025 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002026 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002027 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002028 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002029 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002030 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002031 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002032 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002033 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002034 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002035 clone_filter=None,
2036 retry_fetches=2,
2037 retry_sleep_initial_sec=4.0,
2038 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002039 is_sha1 = False
2040 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002041 # The depth should not be used when fetching to a mirror because
2042 # it will result in a shallow repository that cannot be cloned or
2043 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002044 # The repo project should also never be synced with partial depth.
2045 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2046 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002047
Shawn Pearce69e04d82014-01-29 12:48:54 -08002048 if depth:
2049 current_branch_only = True
2050
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002051 if ID_RE.match(self.revisionExpr) is not None:
2052 is_sha1 = True
2053
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002054 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002055 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002056 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002057 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002058 elif self.upstream and self.upstream.startswith(R_TAGS):
2059 # This is a tag and its commit id should never change.
2060 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002061
2062 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002063 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002064 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002065 print('Skipped fetching project %s (already have persistent ref)'
2066 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002067 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002068 if is_sha1 and not depth:
2069 # When syncing a specific commit and --depth is not set:
2070 # * if upstream is explicitly specified and is not a sha1, fetch only
2071 # upstream as users expect only upstream to be fetch.
2072 # Note: The commit might not be in upstream in which case the sync
2073 # will fail.
2074 # * otherwise, fetch all branches to make sure we end up with the
2075 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002076 if self.upstream:
2077 current_branch_only = not ID_RE.match(self.upstream)
2078 else:
2079 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002081 if not name:
2082 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002083
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002084 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002085 if not remote.PreConnectFetch(ssh_proxy):
2086 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002087
Shawn O. Pearce88443382010-10-08 10:02:09 +02002088 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002089 if alt_dir and 'objects' == os.path.basename(alt_dir):
2090 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002091 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002092
David Pursehouse8a68ff92012-09-24 12:15:13 +09002093 all_refs = self.bare_ref.all
2094 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002095 tmp = set()
2096
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302097 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002098 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002099 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002100 all_refs[r] = ref_id
2101 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002102 continue
2103
David Pursehouse8a68ff92012-09-24 12:15:13 +09002104 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002105 continue
2106
David Pursehouse8a68ff92012-09-24 12:15:13 +09002107 r = 'refs/_alt/%s' % ref_id
2108 all_refs[r] = ref_id
2109 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002110 tmp.add(r)
2111
heping3d7bbc92017-04-12 19:51:47 +08002112 tmp_packed_lines = []
2113 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002114
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302115 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002116 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002117 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002118 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002119 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002120
heping3d7bbc92017-04-12 19:51:47 +08002121 tmp_packed = ''.join(tmp_packed_lines)
2122 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002123 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002124 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002125 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002126
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002127 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002128
Xin Li745be2e2019-06-03 11:24:30 -07002129 if clone_filter:
2130 git_require((2, 19, 0), fail=True, msg='partial clones')
2131 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002132 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002133
Conley Owensf97e8382015-01-21 11:12:46 -08002134 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002135 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002136 else:
2137 # If this repo has shallow objects, then we don't know which refs have
2138 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2139 # do this with projects that don't have shallow objects, since it is less
2140 # efficient.
2141 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2142 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002143
Mike Frysinger4847e052020-02-22 00:07:35 -05002144 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002145 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002146 if not quiet and sys.stdout.isatty():
2147 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002148 if not self.worktree:
2149 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002150 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002151
Mike Frysingere57f1142019-03-18 21:27:54 -04002152 if force_sync:
2153 cmd.append('--force')
2154
David Pursehouse74cfd272015-10-14 10:50:15 +09002155 if prune:
2156 cmd.append('--prune')
2157
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002158 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002159
Kuang-che Wu6856f982019-11-25 12:37:55 +08002160 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002161 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002162 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002163 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002164 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002165 spec.append('tag')
2166 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002167
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302168 if self.manifest.IsMirror and not current_branch_only:
2169 branch = None
2170 else:
2171 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002172 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002173 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002174 # Shallow checkout of a specific commit, fetch from that commit and not
2175 # the heads only as the commit might be deeper in the history.
2176 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002177 if self.upstream:
2178 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002179 else:
2180 if is_sha1:
2181 branch = self.upstream
2182 if branch is not None and branch.strip():
2183 if not branch.startswith('refs/'):
2184 branch = R_HEADS + branch
2185 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2186
2187 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2188 # whole repo.
2189 if self.manifest.IsMirror and not spec:
2190 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2191
2192 # If using depth then we should not get all the tags since they may
2193 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002194 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002195 cmd.append('--no-tags')
2196 else:
2197 cmd.append('--tags')
2198 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2199
Conley Owens80b87fe2014-05-09 17:13:44 -07002200 cmd.extend(spec)
2201
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002202 # At least one retry minimum due to git remote prune.
2203 retry_fetches = max(retry_fetches, 2)
2204 retry_cur_sleep = retry_sleep_initial_sec
2205 ok = prune_tried = False
2206 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002207 gitcmd = GitCommand(
2208 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2209 ssh_proxy=ssh_proxy,
2210 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002211 if gitcmd.stdout and not quiet and output_redir:
2212 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002213 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002214 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002215 ok = True
2216 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002217
2218 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002219 elif (gitcmd.stdout and
2220 'error:' in gitcmd.stdout and
2221 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002222 # Fallthru to sleep+retry logic at the bottom.
2223 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002224
Mike Frysinger6823bc22021-04-15 02:06:28 -04002225 # Try to prune remote branches once in case there are conflicts.
2226 # For example, if the remote had refs/heads/upstream, but deleted that and
2227 # now has refs/heads/upstream/foo.
2228 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002229 'error:' in gitcmd.stdout and
2230 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002231 not prune_tried):
2232 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002233 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002234 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002235 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002236 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002237 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002238 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002239 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002240 continue
Brian Harring14a66742012-09-28 20:21:57 -07002241 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002242 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2243 # in sha1 mode, we just tried sync'ing from the upstream field; it
2244 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002245 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002246 elif ret < 0:
2247 # Git died with a signal, exit immediately
2248 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002249
2250 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002251 if not verbose and gitcmd.stdout:
2252 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002253 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002254 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2255 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002256 time.sleep(retry_cur_sleep)
2257 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2258 MAXIMUM_RETRY_SLEEP_SEC)
2259 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2260 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002261
2262 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002263 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002264 if old_packed != '':
2265 _lwrite(packed_refs, old_packed)
2266 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002267 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002268 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002269
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002270 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002271 # We just synced the upstream given branch; verify we
2272 # got what we wanted, else trigger a second run of all
2273 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002274 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002275 # Sync the current branch only with depth set to None.
2276 # We always pass depth=None down to avoid infinite recursion.
2277 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002278 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002279 current_branch_only=current_branch_only and depth,
2280 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002281 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002282
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002283 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002284
Mike Frysingere50b6a72020-02-19 01:45:48 -05002285 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002286 if initial and (self.manifest.manifestProject.depth or self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002287 return False
2288
2289 remote = self.GetRemote(self.remote.name)
2290 bundle_url = remote.url + '/clone.bundle'
2291 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002292 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2293 'persistent-http',
2294 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002295 return False
2296
2297 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2298 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2299
2300 exist_dst = os.path.exists(bundle_dst)
2301 exist_tmp = os.path.exists(bundle_tmp)
2302
2303 if not initial and not exist_dst and not exist_tmp:
2304 return False
2305
2306 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002307 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2308 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002309 if not exist_dst:
2310 return False
2311
2312 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002313 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002314 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002315 if not quiet and sys.stdout.isatty():
2316 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002317 if not self.worktree:
2318 cmd.append('--update-head-ok')
2319 cmd.append(bundle_dst)
2320 for f in remote.fetch:
2321 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002322 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002323
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002324 ok = GitCommand(
2325 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002326 platform_utils.remove(bundle_dst, missing_ok=True)
2327 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002328 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002329
Mike Frysingere50b6a72020-02-19 01:45:48 -05002330 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002331 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002332
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002333 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002334 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002335 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002336 if os.path.exists(tmpPath):
2337 size = os.stat(tmpPath).st_size
2338 if size >= 1024:
2339 cmd += ['--continue-at', '%d' % (size,)]
2340 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002341 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002342 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002343 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002344 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002345 if proxy:
2346 cmd += ['--proxy', proxy]
2347 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2348 cmd += ['--proxy', os.environ['http_proxy']]
2349 if srcUrl.startswith('persistent-https'):
2350 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2351 elif srcUrl.startswith('persistent-http'):
2352 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002353 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002354
Dave Borowitz137d0132015-01-02 11:12:54 -08002355 if IsTrace():
2356 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002357 if verbose:
2358 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2359 stdout = None if verbose else subprocess.PIPE
2360 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002361 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002362 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002363 except OSError:
2364 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002365
Mike Frysingere50b6a72020-02-19 01:45:48 -05002366 (output, _) = proc.communicate()
2367 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002368
Dave Borowitz137d0132015-01-02 11:12:54 -08002369 if curlret == 22:
2370 # From curl man page:
2371 # 22: HTTP page not retrieved. The requested url was not found or
2372 # returned another error with the HTTP error code being 400 or above.
2373 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002374 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002375 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2376 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002377 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002378 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002379 elif curlret and not verbose and output:
2380 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002381
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002382 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002383 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002384 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002385 return True
2386 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002387 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002388 return False
2389 else:
2390 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002391
Kris Giesingc8d882a2014-12-23 13:02:32 -08002392 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002393 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002394 with open(path, 'rb') as f:
2395 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002396 return True
2397 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002398 if not quiet:
2399 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002400 return False
2401 except OSError:
2402 return False
2403
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002404 def _Checkout(self, rev, quiet=False):
2405 cmd = ['checkout']
2406 if quiet:
2407 cmd.append('-q')
2408 cmd.append(rev)
2409 cmd.append('--')
2410 if GitCommand(self, cmd).Wait() != 0:
2411 if self._allrefs:
2412 raise GitError('%s checkout %s ' % (self.name, rev))
2413
Mike Frysinger915fda12020-03-22 12:15:20 -04002414 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002415 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002416 if ffonly:
2417 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002418 if record_origin:
2419 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002420 cmd.append(rev)
2421 cmd.append('--')
2422 if GitCommand(self, cmd).Wait() != 0:
2423 if self._allrefs:
2424 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2425
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302426 def _LsRemote(self, refs):
2427 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302428 p = GitCommand(self, cmd, capture_stdout=True)
2429 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002430 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302431 return None
2432
Anthony King7bdac712014-07-16 12:56:40 +01002433 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002434 cmd = ['revert']
2435 cmd.append('--no-edit')
2436 cmd.append(rev)
2437 cmd.append('--')
2438 if GitCommand(self, cmd).Wait() != 0:
2439 if self._allrefs:
2440 raise GitError('%s revert %s ' % (self.name, rev))
2441
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002442 def _ResetHard(self, rev, quiet=True):
2443 cmd = ['reset', '--hard']
2444 if quiet:
2445 cmd.append('-q')
2446 cmd.append(rev)
2447 if GitCommand(self, cmd).Wait() != 0:
2448 raise GitError('%s reset --hard %s ' % (self.name, rev))
2449
Martin Kellye4e94d22017-03-21 16:05:12 -07002450 def _SyncSubmodules(self, quiet=True):
2451 cmd = ['submodule', 'update', '--init', '--recursive']
2452 if quiet:
2453 cmd.append('-q')
2454 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002455 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002456
Anthony King7bdac712014-07-16 12:56:40 +01002457 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002458 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002459 if onto is not None:
2460 cmd.extend(['--onto', onto])
2461 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002462 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002463 raise GitError('%s rebase %s ' % (self.name, upstream))
2464
Pierre Tardy3d125942012-05-04 12:18:12 +02002465 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002466 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002467 if ffonly:
2468 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002469 if GitCommand(self, cmd).Wait() != 0:
2470 raise GitError('%s merge %s ' % (self.name, head))
2471
David Pursehousee8ace262020-02-13 12:41:15 +09002472 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002473 init_git_dir = not os.path.exists(self.gitdir)
2474 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002475 try:
2476 # Initialize the bare repository, which contains all of the objects.
2477 if init_obj_dir:
2478 os.makedirs(self.objdir)
2479 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002480
Mike Frysinger333c0a42021-11-15 12:39:00 -05002481 self._UpdateHooks(quiet=quiet)
2482
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002483 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002484 # Enable per-worktree config file support if possible. This is more a
2485 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002486 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002487 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002488
Kevin Degib1a07b82015-07-27 13:33:43 -06002489 # If we have a separate directory to hold refs, initialize it as well.
2490 if self.objdir != self.gitdir:
2491 if init_git_dir:
2492 os.makedirs(self.gitdir)
2493
2494 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002495 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002496 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002497 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002498 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002499 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002500 print("Retrying clone after deleting %s" %
2501 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002502 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002503 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2504 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002505 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002506 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002507 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2508 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002509 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002510 raise e
2511 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002512
Kevin Degi384b3c52014-10-16 16:02:58 -06002513 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002514 mp = self.manifest.manifestProject
LaMont Jonesd82be3e2022-04-05 19:30:46 +00002515 ref_dir = mp.reference or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002516
LaMont Jonescc879a92021-11-18 22:40:18 +00002517 def _expanded_ref_dirs():
2518 """Iterate through the possible git reference directory paths."""
2519 name = self.name + '.git'
2520 yield mirror_git or os.path.join(ref_dir, name)
2521 for prefix in '', self.remote.name:
2522 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2523 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002524
LaMont Jonescc879a92021-11-18 22:40:18 +00002525 if ref_dir or mirror_git:
2526 found_ref_dir = None
2527 for path in _expanded_ref_dirs():
2528 if os.path.exists(path):
2529 found_ref_dir = path
2530 break
2531 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002532
Kevin Degib1a07b82015-07-27 13:33:43 -06002533 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002534 if not os.path.isabs(ref_dir):
2535 # The alternate directory is relative to the object database.
2536 ref_dir = os.path.relpath(ref_dir,
2537 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002538 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002539 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002540
Kevin Degib1a07b82015-07-27 13:33:43 -06002541 m = self.manifest.manifestProject.config
2542 for key in ['user.name', 'user.email']:
2543 if m.Has(key, include_defaults=False):
2544 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002545 if not self.manifest.EnableGitLfs:
2546 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2547 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002548 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002549 except Exception:
2550 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002551 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002552 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002553 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002554 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002555
David Pursehousee8ace262020-02-13 12:41:15 +09002556 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002557 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002558 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002559
David Pursehousee8ace262020-02-13 12:41:15 +09002560 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002561 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002562 if not os.path.exists(hooks):
2563 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002564
2565 # Delete sample hooks. They're noise.
2566 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002567 try:
2568 platform_utils.remove(hook, missing_ok=True)
2569 except PermissionError:
2570 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002571
Jonathan Nieder93719792015-03-17 11:29:58 -07002572 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002573 name = os.path.basename(stock_hook)
2574
Victor Boivie65e0f352011-04-18 11:23:29 +02002575 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002576 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002577 # Don't install a Gerrit Code Review hook if this
2578 # project does not appear to use it for reviews.
2579 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002580 # Since the manifest project is one of those, but also
2581 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002582 continue
2583
2584 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002585 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002586 continue
2587 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002588 # If the files are the same, we'll leave it alone. We create symlinks
2589 # below by default but fallback to hardlinks if the OS blocks them.
2590 # So if we're here, it's probably because we made a hardlink below.
2591 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002592 if not quiet:
2593 _warn("%s: Not replacing locally modified %s hook",
2594 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002595 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002596 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002597 platform_utils.symlink(
2598 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002599 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002600 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002601 try:
2602 os.link(stock_hook, dst)
2603 except OSError:
2604 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002605 else:
2606 raise
2607
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002609 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002610 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002611 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002612 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002613 remote.review = self.remote.review
2614 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002615
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002616 if self.worktree:
2617 remote.ResetFetch(mirror=False)
2618 else:
2619 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002620 remote.Save()
2621
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002622 def _InitMRef(self):
2623 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002624 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002625 # Set up the m/ space to point to the worktree-specific ref space.
2626 # We'll update the worktree-specific ref space on each checkout.
2627 ref = R_M + self.manifest.branch
2628 if not self.bare_ref.symref(ref):
2629 self.bare_git.symbolic_ref(
2630 '-m', 'redirecting to worktree scope',
2631 ref, R_WORKTREE_M + self.manifest.branch)
2632
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002633 # We can't update this ref with git worktrees until it exists.
2634 # We'll wait until the initial checkout to set it.
2635 if not os.path.exists(self.worktree):
2636 return
2637
2638 base = R_WORKTREE_M
2639 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002640
2641 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002642 else:
2643 base = R_M
2644 active_git = self.bare_git
2645
2646 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002647
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002648 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002649 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002650
Remy Böhmer1469c282020-12-15 18:49:02 +01002651 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002652 cur = self.bare_ref.symref(ref)
2653
2654 if self.revisionId:
2655 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2656 msg = 'manifest set to %s' % self.revisionId
2657 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002658 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002659 else:
2660 remote = self.GetRemote(self.remote.name)
2661 dst = remote.ToLocal(self.revisionExpr)
2662 if cur != dst:
2663 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002664 if detach:
2665 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2666 else:
2667 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002668
Mike Frysingerc72bd842021-11-14 03:58:00 -05002669 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002670 # Git worktrees don't use symlinks to share at all.
2671 if self.use_git_worktrees:
2672 return
2673
Mike Frysingerd33dce02021-12-20 18:16:33 -05002674 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002675 # Try to self-heal a bit in simple cases.
2676 dst_path = os.path.join(destdir, name)
2677 src_path = os.path.join(srcdir, name)
2678
Mike Frysingered4f2112020-02-11 23:06:29 -05002679 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002680 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002681 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002682 # Fail if the links are pointing to the wrong place
2683 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002684 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002685 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002686 'work tree. If you\'re comfortable with the '
2687 'possibility of losing the work tree\'s git metadata,'
2688 ' use `repo sync --force-sync {0}` to '
2689 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002690
Mike Frysingerc72bd842021-11-14 03:58:00 -05002691 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002692 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2693
2694 Args:
2695 gitdir: The bare git repository. Must already be initialized.
2696 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002697 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2698 This saves you the effort of initializing |dotgit| yourself.
2699 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002700 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002701 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002702
2703 to_copy = []
2704 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002705 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002706
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002707 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002708 for name in set(to_copy).union(to_symlink):
2709 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002710 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002711 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002712
Kevin Degi384b3c52014-10-16 16:02:58 -06002713 if os.path.lexists(dst):
2714 continue
David James8d201162013-10-11 17:03:19 -07002715
2716 # If the source dir doesn't exist, create an empty dir.
2717 if name in symlink_dirs and not os.path.lexists(src):
2718 os.makedirs(src)
2719
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002720 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002721 platform_utils.symlink(
2722 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002723 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002724 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002725 shutil.copytree(src, dst)
2726 elif os.path.isfile(src):
2727 shutil.copy(src, dst)
2728
David James8d201162013-10-11 17:03:19 -07002729 except OSError as e:
2730 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002731 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002732 else:
2733 raise
2734
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002735 def _InitGitWorktree(self):
2736 """Init the project using git worktrees."""
2737 self.bare_git.worktree('prune')
2738 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2739 self.worktree, self.GetRevisionId())
2740
2741 # Rewrite the internal state files to use relative paths between the
2742 # checkouts & worktrees.
2743 dotgit = os.path.join(self.worktree, '.git')
2744 with open(dotgit, 'r') as fp:
2745 # Figure out the checkout->worktree path.
2746 setting = fp.read()
2747 assert setting.startswith('gitdir:')
2748 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002749 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2750 # of file permissions. Delete it and recreate it from scratch to avoid.
2751 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002752 # Use relative path from checkout->worktree & maintain Unix line endings
2753 # on all OS's to match git behavior.
2754 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002755 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2756 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002757 # Use relative path from worktree->checkout & maintain Unix line endings
2758 # on all OS's to match git behavior.
2759 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002760 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2761
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002762 self._InitMRef()
2763
Martin Kellye4e94d22017-03-21 16:05:12 -07002764 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002765 """Setup the worktree .git path.
2766
2767 This is the user-visible path like src/foo/.git/.
2768
2769 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2770 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2771
2772 Older checkouts had .git/ directories. If we see that, migrate it.
2773
2774 This also handles changes in the manifest. Maybe this project was backed
2775 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2776 the path we point to under .repo/projects/ to match.
2777 """
2778 dotgit = os.path.join(self.worktree, '.git')
2779
2780 # If using an old layout style (a directory), migrate it.
2781 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2782 self._MigrateOldWorkTreeGitDir(dotgit)
2783
2784 init_dotgit = not os.path.exists(dotgit)
2785 if self.use_git_worktrees:
2786 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002787 self._InitGitWorktree()
2788 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002789 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002790 if not init_dotgit:
2791 # See if the project has changed.
2792 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2793 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002794
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002795 if init_dotgit or not os.path.exists(dotgit):
2796 os.makedirs(self.worktree, exist_ok=True)
2797 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002798
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002799 if init_dotgit:
2800 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002801
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002802 # Finish checking out the worktree.
2803 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2804 if GitCommand(self, cmd).Wait() != 0:
2805 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002806
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002807 if submodules:
2808 self._SyncSubmodules(quiet=True)
2809 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002810
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002811 @classmethod
2812 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2813 """Migrate the old worktree .git/ dir style to a symlink.
2814
2815 This logic specifically only uses state from |dotgit| to figure out where to
2816 move content and not |self|. This way if the backing project also changed
2817 places, we only do the .git/ dir to .git symlink migration here. The path
2818 updates will happen independently.
2819 """
2820 # Figure out where in .repo/projects/ it's pointing to.
2821 if not os.path.islink(os.path.join(dotgit, 'refs')):
2822 raise GitError(f'{dotgit}: unsupported checkout state')
2823 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2824
2825 # Remove known symlink paths that exist in .repo/projects/.
2826 KNOWN_LINKS = {
2827 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2828 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2829 }
2830 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2831 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002832 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2833 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002834 }
2835
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002836 # First see if we'd succeed before starting the migration.
2837 unknown_paths = []
2838 for name in platform_utils.listdir(dotgit):
2839 # Ignore all temporary/backup names. These are common with vim & emacs.
2840 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2841 continue
2842
2843 dotgit_path = os.path.join(dotgit, name)
2844 if name in KNOWN_LINKS:
2845 if not platform_utils.islink(dotgit_path):
2846 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2847 else:
2848 gitdir_path = os.path.join(gitdir, name)
2849 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2850 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2851 if unknown_paths:
2852 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2853
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002854 # Now walk the paths and sync the .git/ to .repo/projects/.
2855 for name in platform_utils.listdir(dotgit):
2856 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002857
2858 # Ignore all temporary/backup names. These are common with vim & emacs.
2859 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2860 platform_utils.remove(dotgit_path)
2861 elif name in KNOWN_LINKS:
2862 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002863 else:
2864 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002865 platform_utils.remove(gitdir_path, missing_ok=True)
2866 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002867
2868 # Now that the dir should be empty, clear it out, and symlink it over.
2869 platform_utils.rmdir(dotgit)
2870 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002871
Renaud Paquay788e9622017-01-27 11:41:12 -08002872 def _get_symlink_error_message(self):
2873 if platform_utils.isWindows():
2874 return ('Unable to create symbolic link. Please re-run the command as '
2875 'Administrator, or see '
2876 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2877 'for other options.')
2878 return 'filesystem must support symlinks'
2879
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002880 def _revlist(self, *args, **kw):
2881 a = []
2882 a.extend(args)
2883 a.append('--')
2884 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002885
2886 @property
2887 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002888 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002889
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002890 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002891 """Get logs between two revisions of this project."""
2892 comp = '..'
2893 if rev1:
2894 revs = [rev1]
2895 if rev2:
2896 revs.extend([comp, rev2])
2897 cmd = ['log', ''.join(revs)]
2898 out = DiffColoring(self.config)
2899 if out.is_on and color:
2900 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002901 if pretty_format is not None:
2902 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002903 if oneline:
2904 cmd.append('--oneline')
2905
2906 try:
2907 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2908 if log.Wait() == 0:
2909 return log.stdout
2910 except GitError:
2911 # worktree may not exist if groups changed for example. In that case,
2912 # try in gitdir instead.
2913 if not os.path.exists(self.worktree):
2914 return self.bare_git.log(*cmd[1:])
2915 else:
2916 raise
2917 return None
2918
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002919 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2920 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002921 """Get the list of logs from this revision to given revisionId"""
2922 logs = {}
2923 selfId = self.GetRevisionId(self._allrefs)
2924 toId = toProject.GetRevisionId(toProject._allrefs)
2925
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002926 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2927 pretty_format=pretty_format)
2928 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2929 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002930 return logs
2931
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002932 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002933
David James8d201162013-10-11 17:03:19 -07002934 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002935 self._project = project
2936 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002937 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002938
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002939 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2940 def __getstate__(self):
2941 return (self._project, self._bare, self._gitdir)
2942
2943 def __setstate__(self, state):
2944 self._project, self._bare, self._gitdir = state
2945
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002946 def LsOthers(self):
2947 p = GitCommand(self._project,
2948 ['ls-files',
2949 '-z',
2950 '--others',
2951 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002952 bare=False,
David James8d201162013-10-11 17:03:19 -07002953 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002954 capture_stdout=True,
2955 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002956 if p.Wait() == 0:
2957 out = p.stdout
2958 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002959 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002960 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002961 return []
2962
2963 def DiffZ(self, name, *args):
2964 cmd = [name]
2965 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002966 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002967 cmd.extend(args)
2968 p = GitCommand(self._project,
2969 cmd,
David James8d201162013-10-11 17:03:19 -07002970 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002971 bare=False,
2972 capture_stdout=True,
2973 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002974 p.Wait()
2975 r = {}
2976 out = p.stdout
2977 if out:
2978 out = iter(out[:-1].split('\0'))
2979 while out:
2980 try:
2981 info = next(out)
2982 path = next(out)
2983 except StopIteration:
2984 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002985
Mike Frysinger84230002021-02-16 17:08:35 -05002986 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002987
Mike Frysinger84230002021-02-16 17:08:35 -05002988 def __init__(self, path, omode, nmode, oid, nid, state):
2989 self.path = path
2990 self.src_path = None
2991 self.old_mode = omode
2992 self.new_mode = nmode
2993 self.old_id = oid
2994 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002995
Mike Frysinger84230002021-02-16 17:08:35 -05002996 if len(state) == 1:
2997 self.status = state
2998 self.level = None
2999 else:
3000 self.status = state[:1]
3001 self.level = state[1:]
3002 while self.level.startswith('0'):
3003 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003004
Mike Frysinger84230002021-02-16 17:08:35 -05003005 info = info[1:].split(' ')
3006 info = _Info(path, *info)
3007 if info.status in ('R', 'C'):
3008 info.src_path = info.path
3009 info.path = next(out)
3010 r[info.path] = info
3011 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003012
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003013 def GetDotgitPath(self, subpath=None):
3014 """Return the full path to the .git dir.
3015
3016 As a convenience, append |subpath| if provided.
3017 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003018 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003019 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003020 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003021 dotgit = os.path.join(self._project.worktree, '.git')
3022 if os.path.isfile(dotgit):
3023 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3024 with open(dotgit) as fp:
3025 setting = fp.read()
3026 assert setting.startswith('gitdir:')
3027 gitdir = setting.split(':', 1)[1].strip()
3028 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3029
3030 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3031
3032 def GetHead(self):
3033 """Return the ref that HEAD points to."""
3034 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003035 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003036 with open(path) as fd:
3037 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003038 except IOError as e:
3039 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003040 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303041 line = line.decode()
3042 except AttributeError:
3043 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003044 if line.startswith('ref: '):
3045 return line[5:-1]
3046 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003047
3048 def SetHead(self, ref, message=None):
3049 cmdv = []
3050 if message is not None:
3051 cmdv.extend(['-m', message])
3052 cmdv.append(HEAD)
3053 cmdv.append(ref)
3054 self.symbolic_ref(*cmdv)
3055
3056 def DetachHead(self, new, message=None):
3057 cmdv = ['--no-deref']
3058 if message is not None:
3059 cmdv.extend(['-m', message])
3060 cmdv.append(HEAD)
3061 cmdv.append(new)
3062 self.update_ref(*cmdv)
3063
3064 def UpdateRef(self, name, new, old=None,
3065 message=None,
3066 detach=False):
3067 cmdv = []
3068 if message is not None:
3069 cmdv.extend(['-m', message])
3070 if detach:
3071 cmdv.append('--no-deref')
3072 cmdv.append(name)
3073 cmdv.append(new)
3074 if old is not None:
3075 cmdv.append(old)
3076 self.update_ref(*cmdv)
3077
3078 def DeleteRef(self, name, old=None):
3079 if not old:
3080 old = self.rev_parse(name)
3081 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003082 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003083
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003084 def rev_list(self, *args, **kw):
3085 if 'format' in kw:
3086 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3087 else:
3088 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003089 cmdv.extend(args)
3090 p = GitCommand(self._project,
3091 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003092 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003093 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003094 capture_stdout=True,
3095 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003096 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003097 raise GitError('%s rev-list %s: %s' %
3098 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003099 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003100
3101 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003102 """Allow arbitrary git commands using pythonic syntax.
3103
3104 This allows you to do things like:
3105 git_obj.rev_parse('HEAD')
3106
3107 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3108 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003109 Any other positional arguments will be passed to the git command, and the
3110 following keyword arguments are supported:
3111 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003112
3113 Args:
3114 name: The name of the git command to call. Any '_' characters will
3115 be replaced with '-'.
3116
3117 Returns:
3118 A callable object that will try to call git with the named command.
3119 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003120 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003121
Dave Borowitz091f8932012-10-23 17:01:04 -07003122 def runner(*args, **kwargs):
3123 cmdv = []
3124 config = kwargs.pop('config', None)
3125 for k in kwargs:
3126 raise TypeError('%s() got an unexpected keyword argument %r'
3127 % (name, k))
3128 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303129 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003130 cmdv.append('-c')
3131 cmdv.append('%s=%s' % (k, v))
3132 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003133 cmdv.extend(args)
3134 p = GitCommand(self._project,
3135 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003136 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003137 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003138 capture_stdout=True,
3139 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003140 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003141 raise GitError('%s %s: %s' %
3142 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003143 r = p.stdout
3144 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3145 return r[:-1]
3146 return r
3147 return runner
3148
3149
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003150class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003151
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003152 def __str__(self):
3153 return 'prior sync failed; rebase still in progress'
3154
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003155
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003156class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003157
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003158 def __str__(self):
3159 return 'contains uncommitted changes'
3160
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003161
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003162class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003163
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003164 def __init__(self, project, text):
3165 self.project = project
3166 self.text = text
3167
3168 def Print(self, syncbuf):
3169 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3170 syncbuf.out.nl()
3171
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003172
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003173class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003174
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003175 def __init__(self, project, why):
3176 self.project = project
3177 self.why = why
3178
3179 def Print(self, syncbuf):
3180 syncbuf.out.fail('error: %s/: %s',
3181 self.project.relpath,
3182 str(self.why))
3183 syncbuf.out.nl()
3184
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003185
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003186class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003187
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003188 def __init__(self, project, action):
3189 self.project = project
3190 self.action = action
3191
3192 def Run(self, syncbuf):
3193 out = syncbuf.out
3194 out.project('project %s/', self.project.relpath)
3195 out.nl()
3196 try:
3197 self.action()
3198 out.nl()
3199 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003200 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003201 out.nl()
3202 return False
3203
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003204
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003205class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003206
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003207 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003208 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003209 self.project = self.printer('header', attr='bold')
3210 self.info = self.printer('info')
3211 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003212
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003213
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003214class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003215
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003216 def __init__(self, config, detach_head=False):
3217 self._messages = []
3218 self._failures = []
3219 self._later_queue1 = []
3220 self._later_queue2 = []
3221
3222 self.out = _SyncColoring(config)
3223 self.out.redirect(sys.stderr)
3224
3225 self.detach_head = detach_head
3226 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003227 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003228
3229 def info(self, project, fmt, *args):
3230 self._messages.append(_InfoMessage(project, fmt % args))
3231
3232 def fail(self, project, err=None):
3233 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003234 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003235
3236 def later1(self, project, what):
3237 self._later_queue1.append(_Later(project, what))
3238
3239 def later2(self, project, what):
3240 self._later_queue2.append(_Later(project, what))
3241
3242 def Finish(self):
3243 self._PrintMessages()
3244 self._RunLater()
3245 self._PrintMessages()
3246 return self.clean
3247
David Rileye0684ad2017-04-05 00:02:59 -07003248 def Recently(self):
3249 recent_clean = self.recent_clean
3250 self.recent_clean = True
3251 return recent_clean
3252
3253 def _MarkUnclean(self):
3254 self.clean = False
3255 self.recent_clean = False
3256
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003257 def _RunLater(self):
3258 for q in ['_later_queue1', '_later_queue2']:
3259 if not self._RunQueue(q):
3260 return
3261
3262 def _RunQueue(self, queue):
3263 for m in getattr(self, queue):
3264 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003265 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003266 return False
3267 setattr(self, queue, [])
3268 return True
3269
3270 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003271 if self._messages or self._failures:
3272 if os.isatty(2):
3273 self.out.write(progress.CSI_ERASE_LINE)
3274 self.out.write('\r')
3275
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003276 for m in self._messages:
3277 m.Print(self)
3278 for m in self._failures:
3279 m.Print(self)
3280
3281 self._messages = []
3282 self._failures = []
3283
3284
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003285class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003286 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003287
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003288 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003289 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003290 manifest=manifest,
3291 name=name,
3292 gitdir=gitdir,
3293 objdir=gitdir,
3294 worktree=worktree,
3295 remote=RemoteSpec('origin'),
3296 relpath='.repo/%s' % name,
3297 revisionExpr='refs/heads/master',
3298 revisionId=None,
3299 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003300
3301 def PreSync(self):
3302 if self.Exists:
3303 cb = self.CurrentBranch
3304 if cb:
3305 base = self.GetBranch(cb).merge
3306 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003307 self.revisionExpr = base
3308 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003309
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003310 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003311 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003312 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003313 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003314 return False
3315
David Pursehouse8a68ff92012-09-24 12:15:13 +09003316 all_refs = self.bare_ref.all
3317 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003318 head = self.work_git.GetHead()
3319 if head.startswith(R_HEADS):
3320 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003321 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003322 except KeyError:
3323 head = None
3324
3325 if revid == head:
3326 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003327 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003328 return True
3329 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003330
3331
3332class RepoProject(MetaProject):
3333 """The MetaProject for repo itself."""
3334
3335 @property
3336 def LastFetch(self):
3337 try:
3338 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3339 return os.path.getmtime(fh)
3340 except OSError:
3341 return 0
3342
3343class ManifestProject(MetaProject):
3344 """The MetaProject for manifests."""
3345
3346 def MetaBranchSwitch(self, submodules=False):
3347 """Prepare for manifest branch switch."""
3348
3349 # detach and delete manifest branch, allowing a new
3350 # branch to take over
3351 syncbuf = SyncBuffer(self.config, detach_head=True)
3352 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3353 syncbuf.Finish()
3354
3355 return GitCommand(self,
3356 ['update-ref', '-d', 'refs/heads/default'],
3357 capture_stdout=True,
3358 capture_stderr=True).Wait() == 0
LaMont Jones9b03f152022-03-29 23:01:18 +00003359
3360 @property
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003361 def standalone_manifest_url(self):
3362 """The URL of the standalone manifest, or None."""
3363 return self.config.getString('manifest.standalone')
3364
3365 @property
3366 def manifest_groups(self):
3367 """The manifest groups string."""
3368 return self.config.GetString('manifest.groups')
3369
3370 @property
3371 def reference(self):
3372 """The --reference for this manifest."""
3373 self.config.GetString('repo.reference')
3374
3375 @property
3376 def dissociate(self):
3377 """Whether to dissociate."""
3378 self.config.GetBoolean('repo.dissociate')
3379
3380 @property
3381 def archive(self):
3382 """Whether we use archive."""
3383 self.config.GetBoolean('repo.archive')
3384
3385 @property
3386 def mirror(self):
3387 """Whether we use mirror."""
3388 self.config.GetBoolean('repo.mirror')
3389
3390 @property
3391 def use_worktree(self):
3392 """Whether we use worktree."""
3393 self.config.GetBoolean('repo.worktree')
3394
3395 @property
3396 def clone_bundle(self):
3397 """Whether we use clone_bundle."""
3398 self.config.GetBoolean('repo.clonebundle')
3399
3400 @property
3401 def submodules(self):
3402 """Whether we use submodules."""
3403 self.config.GetBoolean('repo.submodules')
3404
3405 @property
3406 def git_lfs(self):
3407 """Whether we use git_lfs."""
3408 self.config.GetBoolean('repo.git-lfs')
3409
3410 @property
3411 def use_superproject(self):
3412 """Whether we use superproject."""
3413 self.config.GetBoolean('repo.superproject')
3414
3415 @property
3416 def partial_clone(self):
3417 """Whether this is a partial clone."""
3418 self.config.GetBoolean('repo.partialclone')
3419
3420 @property
3421 def depth(self):
3422 """Partial clone depth."""
3423 self.config.GetString('repo.depth')
3424
3425 @property
3426 def clone_filter(self):
3427 """The clone filter."""
3428 self.config.GetString('repo.clonefilter')
3429
3430 @property
3431 def partial_clone_exclude(self):
3432 """Partial clone exclude string"""
3433 self.config.GetBoolean('repo.partialcloneexclude')
3434
3435 @property
LaMont Jones9b03f152022-03-29 23:01:18 +00003436 def _platform_name(self):
3437 """Return the name of the platform."""
3438 return platform.system().lower()
3439
3440 def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003441 standalone_manifest=False, groups='', mirror=False, reference='',
3442 dissociate=False, worktree=False, submodules=False, archive=False,
3443 partial_clone=None, depth=None, clone_filter='blob:none',
LaMont Jones9b03f152022-03-29 23:01:18 +00003444 partial_clone_exclude=None, clone_bundle=None, git_lfs=None,
3445 use_superproject=None, verbose=False, current_branch_only=False,
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003446 platform='', tags=''):
LaMont Jones9b03f152022-03-29 23:01:18 +00003447 """Sync the manifest and all submanifests.
3448
3449 Args:
3450 manifest_url: a string, the URL of the manifest project.
3451 manifest_branch: a string, the manifest branch to use.
3452 standalone_manifest: a boolean, whether to store the manifest as a static
3453 file.
3454 groups: a string, restricts the checkout to projects with the specified
3455 groups.
LaMont Jones9b03f152022-03-29 23:01:18 +00003456 mirror: a boolean, whether to create a mirror of the remote repository.
3457 reference: a string, location of a repo instance to use as a reference.
3458 dissociate: a boolean, whether to dissociate from reference mirrors after
3459 clone.
3460 worktree: a boolean, whether to use git-worktree to manage projects.
3461 submodules: a boolean, whether sync submodules associated with the
3462 manifest project.
3463 archive: a boolean, whether to checkout each project as an archive. See
3464 git-archive.
3465 partial_clone: a boolean, whether to perform a partial clone.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003466 depth: an int, how deep of a shallow clone to create.
LaMont Jones9b03f152022-03-29 23:01:18 +00003467 clone_filter: a string, filter to use with partial_clone.
3468 partial_clone_exclude : a string, comma-delimeted list of project namess
3469 to exclude from partial clone.
3470 clone_bundle: a boolean, whether to enable /clone.bundle on HTTP/HTTPS.
3471 git_lfs: a boolean, whether to enable git LFS support.
3472 use_superproject: a boolean, whether to use the manifest superproject to
3473 sync projects.
3474 verbose: a boolean, whether to show all output, rather than only errors.
3475 current_branch_only: a boolean, whether to only fetch the current manifest
3476 branch from the server.
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003477 platform: a string, restrict the checkout to projects with the specified
3478 platform group.
LaMont Jones9b03f152022-03-29 23:01:18 +00003479 tags: a boolean, whether to fetch tags.,
LaMont Jones9b03f152022-03-29 23:01:18 +00003480
3481 Returns:
3482 a boolean, whether the sync was successful.
3483 """
3484 assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
3485
3486 # If repo has already been initialized, we take -u with the absence of
3487 # --standalone-manifest to mean "transition to a standard repo set up",
3488 # which necessitates starting fresh.
3489 # If --standalone-manifest is set, we always tear everything down and start
3490 # anew.
3491 if self.Exists:
3492 was_standalone_manifest = self.config.GetString('manifest.standalone')
3493 if was_standalone_manifest and not manifest_url:
3494 print('fatal: repo was initialized with a standlone manifest, '
3495 'cannot be re-initialized without --manifest-url/-u')
3496 return False
3497
3498 if standalone_manifest or (was_standalone_manifest and manifest_url):
3499 self.config.ClearCache()
3500 if self.gitdir and os.path.exists(self.gitdir):
3501 platform_utils.rmtree(self.gitdir)
3502 if self.worktree and os.path.exists(self.worktree):
3503 platform_utils.rmtree(self.worktree)
3504
3505 is_new = not self.Exists
3506 if is_new:
3507 if not manifest_url:
3508 print('fatal: manifest url is required.', file=sys.stderr)
3509 return False
3510
3511 if not quiet:
3512 print('Downloading manifest from %s' %
3513 (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
3514 file=sys.stderr)
3515
3516 # The manifest project object doesn't keep track of the path on the
3517 # server where this git is located, so let's save that here.
3518 mirrored_manifest_git = None
3519 if reference:
3520 manifest_git_path = urllib.parse.urlparse(manifest_url).path[1:]
3521 mirrored_manifest_git = os.path.join(reference, manifest_git_path)
3522 if not mirrored_manifest_git.endswith(".git"):
3523 mirrored_manifest_git += ".git"
3524 if not os.path.exists(mirrored_manifest_git):
3525 mirrored_manifest_git = os.path.join(reference,
3526 '.repo/manifests.git')
3527
3528 self._InitGitDir(mirror_git=mirrored_manifest_git)
3529
3530 # If standalone_manifest is set, mark the project as "standalone" -- we'll
3531 # still do much of the manifests.git set up, but will avoid actual syncs to
3532 # a remote.
3533 if standalone_manifest:
3534 self.config.SetString('manifest.standalone', manifest_url)
3535 elif not manifest_url and not manifest_branch:
3536 # If -u is set and --standalone-manifest is not, then we're not in
3537 # standalone mode. Otherwise, use config to infer what we were in the last
3538 # init.
3539 standalone_manifest = bool(self.config.GetString('manifest.standalone'))
3540 if not standalone_manifest:
3541 self.config.SetString('manifest.standalone', None)
3542
3543 self._ConfigureDepth(depth)
3544
3545 # Set the remote URL before the remote branch as we might need it below.
3546 if manifest_url:
3547 r = self.GetRemote(self.remote.name)
3548 r.url = manifest_url
3549 r.ResetFetch()
3550 r.Save()
3551
3552 if not standalone_manifest:
3553 if manifest_branch:
3554 if manifest_branch == 'HEAD':
3555 manifest_branch = self.ResolveRemoteHead()
3556 if manifest_branch is None:
3557 print('fatal: unable to resolve HEAD', file=sys.stderr)
3558 return False
3559 self.revisionExpr = manifest_branch
3560 else:
3561 if is_new:
3562 default_branch = self.ResolveRemoteHead()
3563 if default_branch is None:
3564 # If the remote doesn't have HEAD configured, default to master.
3565 default_branch = 'refs/heads/master'
3566 self.revisionExpr = default_branch
3567 else:
3568 self.PreSync()
3569
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003570 groups = re.split(r'[,\s]+', groups or '')
LaMont Jones9b03f152022-03-29 23:01:18 +00003571 all_platforms = ['linux', 'darwin', 'windows']
3572 platformize = lambda x: 'platform-' + x
3573 if platform == 'auto':
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003574 if not mirror and not self.mirror:
LaMont Jones9b03f152022-03-29 23:01:18 +00003575 groups.append(platformize(self._platform_name))
3576 elif platform == 'all':
3577 groups.extend(map(platformize, all_platforms))
3578 elif platform in all_platforms:
3579 groups.append(platformize(platform))
3580 elif platform != 'none':
3581 print('fatal: invalid platform flag', file=sys.stderr)
3582 return False
3583
3584 groups = [x for x in groups if x]
3585 groupstr = ','.join(groups)
3586 if platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr():
3587 groupstr = None
3588 self.config.SetString('manifest.groups', groupstr)
3589
3590 if reference:
3591 self.config.SetString('repo.reference', reference)
3592
3593 if dissociate:
3594 self.config.SetBoolean('repo.dissociate', dissociate)
3595
3596 if worktree:
3597 if mirror:
3598 print('fatal: --mirror and --worktree are incompatible',
3599 file=sys.stderr)
3600 return False
3601 if submodules:
3602 print('fatal: --submodules and --worktree are incompatible',
3603 file=sys.stderr)
3604 return False
3605 self.config.SetBoolean('repo.worktree', worktree)
3606 if is_new:
3607 self.use_git_worktrees = True
3608 print('warning: --worktree is experimental!', file=sys.stderr)
3609
3610 if archive:
3611 if is_new:
3612 self.config.SetBoolean('repo.archive', archive)
3613 else:
3614 print('fatal: --archive is only supported when initializing a new '
3615 'workspace.', file=sys.stderr)
3616 print('Either delete the .repo folder in this workspace, or initialize '
3617 'in another location.', file=sys.stderr)
3618 return False
3619
3620 if mirror:
3621 if is_new:
3622 self.config.SetBoolean('repo.mirror', mirror)
3623 else:
3624 print('fatal: --mirror is only supported when initializing a new '
3625 'workspace.', file=sys.stderr)
3626 print('Either delete the .repo folder in this workspace, or initialize '
3627 'in another location.', file=sys.stderr)
3628 return False
3629
3630 if partial_clone is not None:
3631 if mirror:
3632 print('fatal: --mirror and --partial-clone are mutually exclusive',
3633 file=sys.stderr)
3634 return False
3635 self.config.SetBoolean('repo.partialclone', partial_clone)
3636 if clone_filter:
3637 self.config.SetString('repo.clonefilter', clone_filter)
LaMont Jonesd82be3e2022-04-05 19:30:46 +00003638 elif self.partialclone:
3639 clone_filter = self.clone_filter
LaMont Jones9b03f152022-03-29 23:01:18 +00003640 else:
3641 clone_filter = None
3642
3643 if partial_clone_exclude is not None:
3644 self.config.SetString('repo.partialcloneexclude', partial_clone_exclude)
3645
3646 if clone_bundle is None:
3647 clone_bundle = False if partial_clone else True
3648 else:
3649 self.config.SetBoolean('repo.clonebundle', clone_bundle)
3650
3651 if submodules:
3652 self.config.SetBoolean('repo.submodules', submodules)
3653
3654 if git_lfs is not None:
3655 if git_lfs:
3656 git_require((2, 17, 0), fail=True, msg='Git LFS support')
3657
3658 self.config.SetBoolean('repo.git-lfs', git_lfs)
3659 if not is_new:
3660 print('warning: Changing --git-lfs settings will only affect new project checkouts.\n'
3661 ' Existing projects will require manual updates.\n', file=sys.stderr)
3662
3663 if use_superproject is not None:
3664 self.config.SetBoolean('repo.superproject', use_superproject)
3665
3666 if standalone_manifest:
3667 if is_new:
3668 manifest_name = 'default.xml'
3669 manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
3670 dest = os.path.join(self.worktree, manifest_name)
3671 os.makedirs(os.path.dirname(dest), exist_ok=True)
3672 with open(dest, 'wb') as f:
3673 f.write(manifest_data)
3674 return
3675
3676 if not self.Sync_NetworkHalf(is_new=is_new, quiet=not verbose, verbose=verbose,
3677 clone_bundle=clone_bundle,
3678 current_branch_only=current_branch_only,
3679 tags=tags, submodules=submodules,
3680 clone_filter=clone_filter,
3681 partial_clone_exclude=self.manifest.PartialCloneExclude):
3682 r = self.GetRemote(self.remote.name)
3683 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
3684
3685 # Better delete the manifest git dir if we created it; otherwise next
3686 # time (when user fixes problems) we won't go through the "is_new" logic.
3687 if is_new:
3688 platform_utils.rmtree(self.gitdir)
3689 return False
3690
3691 if manifest_branch:
3692 self.MetaBranchSwitch(submodules=submodules)
3693
3694 syncbuf = SyncBuffer(self.config)
3695 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3696 syncbuf.Finish()
3697
3698 if is_new or self.CurrentBranch is None:
3699 if not self.StartBranch('default'):
3700 print('fatal: cannot create default in manifest', file=sys.stderr)
3701 return False
3702
3703 return True
3704
3705 def _ConfigureDepth(self, depth):
3706 """Configure the depth we'll sync down.
3707
3708 Args:
3709 depth: an int, how deep of a partial clone to create.
3710 """
3711 # Opt.depth will be non-None if user actually passed --depth to repo init.
3712 if depth is not None:
3713 if depth > 0:
3714 # Positive values will set the depth.
3715 depth = str(depth)
3716 else:
3717 # Negative numbers will clear the depth; passing None to SetString
3718 # will do that.
3719 depth = None
3720
3721 # We store the depth in the main manifest project.
3722 self.config.SetString('repo.depth', depth)