blob: 8490d9f9004a0e6d882eed359cd5486c7d78062d [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
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070019import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import re
21import shutil
22import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070023import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020025import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080026import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070027import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040028import urllib.parse
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070029
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070031from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070032from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
33 ID_RE
Remy Bohmer16c13282020-09-10 10:38:04 +020034from error import GitError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040035from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080036from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070037import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040038import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040039from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050041from 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 -070042
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070043
George Engelbrecht9bc283e2020-04-02 12:36:09 -060044# Maximum sleep time allowed during retries.
45MAXIMUM_RETRY_SLEEP_SEC = 3600.0
46# +-10% random jitter is added to each Fetches retry sleep duration.
47RETRY_JITTER_PERCENT = 0.1
48
49
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070050def _lwrite(path, content):
51 lock = '%s.lock' % path
52
Remy Bohmer169b0212020-11-21 10:57:52 +010053 # Maintain Unix line endings on all OS's to match git behavior.
54 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070055 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070056
57 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070058 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070059 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080060 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070061 raise
62
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070063
Shawn O. Pearce48244782009-04-16 08:25:57 -070064def _error(fmt, *args):
65 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070066 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070067
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070068
David Pursehousef33929d2015-08-24 14:39:14 +090069def _warn(fmt, *args):
70 msg = fmt % args
71 print('warn: %s' % msg, file=sys.stderr)
72
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070073
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070074def not_rev(r):
75 return '^' + r
76
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070077
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080078def sq(r):
79 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080080
David Pursehouse819827a2020-02-12 15:20:19 +090081
Jonathan Nieder93719792015-03-17 11:29:58 -070082_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070083
84
Jonathan Nieder93719792015-03-17 11:29:58 -070085def _ProjectHooks():
86 """List the hooks present in the 'hooks' directory.
87
88 These hooks are project hooks and are copied to the '.git/hooks' directory
89 of all subprojects.
90
91 This function caches the list of hooks (based on the contents of the
92 'repo/hooks' directory) on the first call.
93
94 Returns:
95 A list of absolute paths to all of the files in the hooks directory.
96 """
97 global _project_hook_list
98 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -070099 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700100 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700101 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700102 return _project_hook_list
103
104
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700105class DownloadedChange(object):
106 _commit_cache = None
107
108 def __init__(self, project, base, change_id, ps_id, commit):
109 self.project = project
110 self.base = base
111 self.change_id = change_id
112 self.ps_id = ps_id
113 self.commit = commit
114
115 @property
116 def commits(self):
117 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700118 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
119 '--abbrev-commit',
120 '--pretty=oneline',
121 '--reverse',
122 '--date-order',
123 not_rev(self.base),
124 self.commit,
125 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700126 return self._commit_cache
127
128
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700129class ReviewableBranch(object):
130 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400131 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700132
133 def __init__(self, project, branch, base):
134 self.project = project
135 self.branch = branch
136 self.base = base
137
138 @property
139 def name(self):
140 return self.branch.name
141
142 @property
143 def commits(self):
144 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400145 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
146 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
147 try:
148 self._commit_cache = self.project.bare_git.rev_list(*args)
149 except GitError:
150 # We weren't able to probe the commits for this branch. Was it tracking
151 # a branch that no longer exists? If so, return no commits. Otherwise,
152 # rethrow the error as we don't know what's going on.
153 if self.base_exists:
154 raise
155
156 self._commit_cache = []
157
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158 return self._commit_cache
159
160 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800161 def unabbrev_commits(self):
162 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700163 for commit in self.project.bare_git.rev_list(not_rev(self.base),
164 R_HEADS + self.name,
165 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800166 r[commit[0:8]] = commit
167 return r
168
169 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700171 return self.project.bare_git.log('--pretty=format:%cd',
172 '-n', '1',
173 R_HEADS + self.name,
174 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175
Mike Frysinger6da17752019-09-11 18:43:17 -0400176 @property
177 def base_exists(self):
178 """Whether the branch we're tracking exists.
179
180 Normally it should, but sometimes branches we track can get deleted.
181 """
182 if self._base_exists is None:
183 try:
184 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
185 # If we're still here, the base branch exists.
186 self._base_exists = True
187 except GitError:
188 # If we failed to verify, the base branch doesn't exist.
189 self._base_exists = False
190
191 return self._base_exists
192
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700193 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500194 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700195 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500196 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500197 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200198 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700199 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200200 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200201 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800202 validate_certs=True,
203 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500204 self.project.UploadForReview(branch=self.name,
205 people=people,
206 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700207 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500208 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500209 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200210 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700211 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200212 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200213 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800214 validate_certs=validate_certs,
215 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700217 def GetPublishedRefs(self):
218 refs = {}
219 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700220 self.branch.remote.SshReviewUrl(self.project.UserEmail),
221 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700222 for line in output.split('\n'):
223 try:
224 (sha, ref) = line.split()
225 refs[sha] = ref
226 except ValueError:
227 pass
228
229 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700231
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700233
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500235 super().__init__(config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100236 self.project = self.printer('header', attr='bold')
237 self.branch = self.printer('header', attr='bold')
238 self.nobranch = self.printer('nobranch', fg='red')
239 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240
Anthony King7bdac712014-07-16 12:56:40 +0100241 self.added = self.printer('added', fg='green')
242 self.changed = self.printer('changed', fg='red')
243 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
245
246class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700247
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500249 super().__init__(config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100250 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400251 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700253
Jack Neus6ea0cae2021-07-20 20:52:33 +0000254class Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700255
James W. Mills24c13082012-04-12 15:04:13 -0500256 def __init__(self, name, value, keep):
257 self.name = name
258 self.value = value
259 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700260
Jack Neus6ea0cae2021-07-20 20:52:33 +0000261 def __eq__(self, other):
262 if not isinstance(other, Annotation):
263 return False
264 return self.__dict__ == other.__dict__
265
266 def __lt__(self, other):
267 # This exists just so that lists of Annotation objects can be sorted, for
268 # use in comparisons.
269 if not isinstance(other, Annotation):
270 raise ValueError('comparison is not between two Annotation objects')
271 if self.name == other.name:
272 if self.value == other.value:
273 return self.keep < other.keep
274 return self.value < other.value
275 return self.name < other.name
276
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700277
Mike Frysingere6a202f2019-08-02 15:57:57 -0400278def _SafeExpandPath(base, subpath, skipfinal=False):
279 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700280
Mike Frysingere6a202f2019-08-02 15:57:57 -0400281 We make sure no intermediate symlinks are traversed, and that the final path
282 is not a special file (e.g. not a socket or fifo).
283
284 NB: We rely on a number of paths already being filtered out while parsing the
285 manifest. See the validation logic in manifest_xml.py for more details.
286 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500287 # Split up the path by its components. We can't use os.path.sep exclusively
288 # as some platforms (like Windows) will convert / to \ and that bypasses all
289 # our constructed logic here. Especially since manifest authors only use
290 # / in their paths.
291 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
292 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400293 if skipfinal:
294 # Whether the caller handles the final component itself.
295 finalpart = components.pop()
296
297 path = base
298 for part in components:
299 if part in {'.', '..'}:
300 raise ManifestInvalidPathError(
301 '%s: "%s" not allowed in paths' % (subpath, part))
302
303 path = os.path.join(path, part)
304 if platform_utils.islink(path):
305 raise ManifestInvalidPathError(
306 '%s: traversing symlinks not allow' % (path,))
307
308 if os.path.exists(path):
309 if not os.path.isfile(path) and not platform_utils.isdir(path):
310 raise ManifestInvalidPathError(
311 '%s: only regular files & directories allowed' % (path,))
312
313 if skipfinal:
314 path = os.path.join(path, finalpart)
315
316 return path
317
318
319class _CopyFile(object):
320 """Container for <copyfile> manifest element."""
321
322 def __init__(self, git_worktree, src, topdir, dest):
323 """Register a <copyfile> request.
324
325 Args:
326 git_worktree: Absolute path to the git project checkout.
327 src: Relative path under |git_worktree| of file to read.
328 topdir: Absolute path to the top of the repo client checkout.
329 dest: Relative path under |topdir| of file to write.
330 """
331 self.git_worktree = git_worktree
332 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700333 self.src = src
334 self.dest = dest
335
336 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400337 src = _SafeExpandPath(self.git_worktree, self.src)
338 dest = _SafeExpandPath(self.topdir, self.dest)
339
340 if platform_utils.isdir(src):
341 raise ManifestInvalidPathError(
342 '%s: copying from directory not supported' % (self.src,))
343 if platform_utils.isdir(dest):
344 raise ManifestInvalidPathError(
345 '%s: copying to directory not allowed' % (self.dest,))
346
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700347 # copy file if it does not exist or is out of date
348 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
349 try:
350 # remove existing file first, since it might be read-only
351 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800352 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400353 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200354 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700355 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200356 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700357 shutil.copy(src, dest)
358 # make the file read-only
359 mode = os.stat(dest)[stat.ST_MODE]
360 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
361 os.chmod(dest, mode)
362 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700363 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700364
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700365
Anthony King7bdac712014-07-16 12:56:40 +0100366class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400367 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700368
Mike Frysingere6a202f2019-08-02 15:57:57 -0400369 def __init__(self, git_worktree, src, topdir, dest):
370 """Register a <linkfile> request.
371
372 Args:
373 git_worktree: Absolute path to the git project checkout.
374 src: Target of symlink relative to path under |git_worktree|.
375 topdir: Absolute path to the top of the repo client checkout.
376 dest: Relative path under |topdir| of symlink to create.
377 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700378 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400379 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500380 self.src = src
381 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500382
Wink Saville4c426ef2015-06-03 08:05:17 -0700383 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500384 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700385 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500386 try:
387 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800388 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800389 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500390 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700391 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700392 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500393 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700394 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500395 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700396 _error('Cannot link file %s to %s', relSrc, absDest)
397
398 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400399 """Link the self.src & self.dest paths.
400
401 Handles wild cards on the src linking all of the files in the source in to
402 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700403 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500404 # Some people use src="." to create stable links to projects. Lets allow
405 # that but reject all other uses of "." to keep things simple.
406 if self.src == '.':
407 src = self.git_worktree
408 else:
409 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400410
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300411 if not glob.has_magic(src):
412 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400413 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
414 # dest & src are absolute paths at this point. Make sure the target of
415 # the symlink is relative in the context of the repo client checkout.
416 relpath = os.path.relpath(src, os.path.dirname(dest))
417 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700418 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400419 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300420 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400421 if os.path.exists(dest) and not platform_utils.isdir(dest):
422 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700423 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400424 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700425 # Create a releative path from source dir to destination dir
426 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400427 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700428
429 # Get the source file name
430 srcFile = os.path.basename(absSrcFile)
431
432 # Now form the final full paths to srcFile. They will be
433 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400434 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700435 relSrc = os.path.join(relSrcDir, srcFile)
436 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500437
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700438
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700439class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700440
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700441 def __init__(self,
442 name,
Anthony King7bdac712014-07-16 12:56:40 +0100443 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700444 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100445 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700446 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700447 orig_name=None,
448 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700449 self.name = name
450 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700451 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700452 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100453 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700454 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700455 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700456
Ian Kasprzak0286e312021-02-05 10:06:18 -0800457
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700458class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600459 # These objects can be shared between several working trees.
Mike Frysinger41289c62021-12-20 17:30:33 -0500460 shareable_dirs = ['hooks', 'objects', 'rr-cache']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700461
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700462 def __init__(self,
463 manifest,
464 name,
465 remote,
466 gitdir,
David James8d201162013-10-11 17:03:19 -0700467 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700468 worktree,
469 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700470 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800471 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100472 rebase=True,
473 groups=None,
474 sync_c=False,
475 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900476 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100477 clone_depth=None,
478 upstream=None,
479 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500480 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100481 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900482 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700483 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600484 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700485 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800486 """Init a Project object.
487
488 Args:
489 manifest: The XmlManifest object.
490 name: The `name` attribute of manifest.xml's project element.
491 remote: RemoteSpec object specifying its remote's properties.
492 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700493 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800494 worktree: Absolute path of git working tree.
495 relpath: Relative path of git working tree to repo's top directory.
496 revisionExpr: The `revision` attribute of manifest.xml's project element.
497 revisionId: git commit id for checking out.
498 rebase: The `rebase` attribute of manifest.xml's project element.
499 groups: The `groups` attribute of manifest.xml's project element.
500 sync_c: The `sync-c` attribute of manifest.xml's project element.
501 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900502 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800503 upstream: The `upstream` attribute of manifest.xml's project element.
504 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500505 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800506 is_derived: False if the project was explicitly defined in the manifest;
507 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400508 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900509 optimized_fetch: If True, when a project is set to a sha1 revision, only
510 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600511 retry_fetches: Retry remote fetches n times upon receiving transient error
512 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700513 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800514 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400515 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700516 self.name = name
517 self.remote = remote
Michael Kelly37c21c22020-06-13 02:10:40 -0700518 self.UpdatePaths(relpath, worktree, gitdir, objdir)
Michael Kelly2f3c3312020-07-21 19:40:38 -0700519 self.SetRevision(revisionExpr, revisionId=revisionId)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700520
Mike Pontillod3153822012-02-28 11:53:24 -0800521 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700522 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700523 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800524 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900525 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900526 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700527 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800528 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500529 # NB: Do not use this setting in __init__ to change behavior so that the
530 # manifest.git checkout can inspect & change it after instantiating. See
531 # the XmlManifest init code for more info.
532 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800533 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900534 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600535 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800536 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800537
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700538 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700539 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500540 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500541 self.annotations = []
Bryan Jacobsf609f912013-05-06 13:36:24 -0400542 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700543 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700544
Doug Anderson37282b42011-03-04 11:54:18 -0800545 # This will be filled in if a project is later identified to be the
546 # project containing repo hooks.
547 self.enabled_repo_hooks = []
548
Michael Kelly2f3c3312020-07-21 19:40:38 -0700549 def SetRevision(self, revisionExpr, revisionId=None):
550 """Set revisionId based on revision expression and id"""
551 self.revisionExpr = revisionExpr
552 if revisionId is None and revisionExpr and IsId(revisionExpr):
553 self.revisionId = self.revisionExpr
554 else:
555 self.revisionId = revisionId
556
Michael Kelly37c21c22020-06-13 02:10:40 -0700557 def UpdatePaths(self, relpath, worktree, gitdir, objdir):
558 """Update paths used by this project"""
559 self.gitdir = gitdir.replace('\\', '/')
560 self.objdir = objdir.replace('\\', '/')
561 if worktree:
562 self.worktree = os.path.normpath(worktree).replace('\\', '/')
563 else:
564 self.worktree = None
565 self.relpath = relpath
566
567 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
568 defaults=self.manifest.globalConfig)
569
570 if self.worktree:
571 self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
572 else:
573 self.work_git = None
574 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
575 self.bare_ref = GitRefs(self.gitdir)
576 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
577
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700578 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800579 def Derived(self):
580 return self.is_derived
581
582 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700584 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700585
586 @property
587 def CurrentBranch(self):
588 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400589
590 The branch name omits the 'refs/heads/' prefix.
591 None is returned if the project is on a detached HEAD, or if the work_git is
592 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700593 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400594 try:
595 b = self.work_git.GetHead()
596 except NoManifestException:
597 # If the local checkout is in a bad state, don't barf. Let the callers
598 # process this like the head is unreadable.
599 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700600 if b.startswith(R_HEADS):
601 return b[len(R_HEADS):]
602 return None
603
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700604 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500605 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
606 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
607 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200608
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700609 def IsDirty(self, consider_untracked=True):
610 """Is the working directory modified in some way?
611 """
612 self.work_git.update_index('-q',
613 '--unmerged',
614 '--ignore-missing',
615 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900616 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700617 return True
618 if self.work_git.DiffZ('diff-files'):
619 return True
620 if consider_untracked and self.work_git.LsOthers():
621 return True
622 return False
623
624 _userident_name = None
625 _userident_email = None
626
627 @property
628 def UserName(self):
629 """Obtain the user's personal name.
630 """
631 if self._userident_name is None:
632 self._LoadUserIdentity()
633 return self._userident_name
634
635 @property
636 def UserEmail(self):
637 """Obtain the user's email address. This is very likely
638 to be their Gerrit login.
639 """
640 if self._userident_email is None:
641 self._LoadUserIdentity()
642 return self._userident_email
643
644 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900645 u = self.bare_git.var('GIT_COMMITTER_IDENT')
646 m = re.compile("^(.*) <([^>]*)> ").match(u)
647 if m:
648 self._userident_name = m.group(1)
649 self._userident_email = m.group(2)
650 else:
651 self._userident_name = ''
652 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653
654 def GetRemote(self, name):
655 """Get the configuration for a single remote.
656 """
657 return self.config.GetRemote(name)
658
659 def GetBranch(self, name):
660 """Get the configuration for a single branch.
661 """
662 return self.config.GetBranch(name)
663
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700664 def GetBranches(self):
665 """Get all existing local branches.
666 """
667 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900668 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700669 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700670
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530671 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700672 if name.startswith(R_HEADS):
673 name = name[len(R_HEADS):]
674 b = self.GetBranch(name)
675 b.current = name == current
676 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900677 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700678 heads[name] = b
679
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530680 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700681 if name.startswith(R_PUB):
682 name = name[len(R_PUB):]
683 b = heads.get(name)
684 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900685 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700686
687 return heads
688
Colin Cross5acde752012-03-28 20:15:45 -0700689 def MatchesGroups(self, manifest_groups):
690 """Returns true if the manifest groups specified at init should cause
691 this project to be synced.
692 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700693 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700694
695 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700696 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700697 manifest_groups: "-group1,group2"
698 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500699
700 The special manifest group "default" will match any project that
701 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700702 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500703 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700704 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700705 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500706 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700707
Conley Owens971de8e2012-04-16 10:36:08 -0700708 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700709 for group in expanded_manifest_groups:
710 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700711 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700712 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700713 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700714
Conley Owens971de8e2012-04-16 10:36:08 -0700715 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700716
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700717# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700718 def UncommitedFiles(self, get_all=True):
719 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700721 Args:
722 get_all: a boolean, if True - get information about all different
723 uncommitted files. If False - return as soon as any kind of
724 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500725 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700726 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500727 self.work_git.update_index('-q',
728 '--unmerged',
729 '--ignore-missing',
730 '--refresh')
731 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700732 details.append("rebase in progress")
733 if not get_all:
734 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500735
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700736 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
737 if changes:
738 details.extend(changes)
739 if not get_all:
740 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500741
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700742 changes = self.work_git.DiffZ('diff-files').keys()
743 if changes:
744 details.extend(changes)
745 if not get_all:
746 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500747
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700748 changes = self.work_git.LsOthers()
749 if changes:
750 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500751
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700752 return details
753
754 def HasChanges(self):
755 """Returns true if there are uncommitted changes.
756 """
757 if self.UncommitedFiles(get_all=False):
758 return True
759 else:
760 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500761
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600762 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700763 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200764
765 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200766 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600767 quiet: If True then only print the project name. Do not print
768 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700769 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700770 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700771 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200772 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700773 print(file=output_redir)
774 print('project %s/' % self.relpath, file=output_redir)
775 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700776 return
777
778 self.work_git.update_index('-q',
779 '--unmerged',
780 '--ignore-missing',
781 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700782 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700783 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
784 df = self.work_git.DiffZ('diff-files')
785 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100786 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700787 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788
789 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700790 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200791 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700792 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700793
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600794 if quiet:
795 out.nl()
796 return 'DIRTY'
797
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700798 branch = self.CurrentBranch
799 if branch is None:
800 out.nobranch('(*** NO BRANCH ***)')
801 else:
802 out.branch('branch %s', branch)
803 out.nl()
804
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700805 if rb:
806 out.important('prior sync failed; rebase still in progress')
807 out.nl()
808
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809 paths = list()
810 paths.extend(di.keys())
811 paths.extend(df.keys())
812 paths.extend(do)
813
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530814 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900815 try:
816 i = di[p]
817 except KeyError:
818 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700819
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900820 try:
821 f = df[p]
822 except KeyError:
823 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200824
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900825 if i:
826 i_status = i.status.upper()
827 else:
828 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900830 if f:
831 f_status = f.status.lower()
832 else:
833 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834
835 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800836 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700837 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 else:
839 line = ' %s%s\t%s' % (i_status, f_status, p)
840
841 if i and not f:
842 out.added('%s', line)
843 elif (i and f) or (not i and f):
844 out.changed('%s', line)
845 elif not i and not f:
846 out.untracked('%s', line)
847 else:
848 out.write('%s', line)
849 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200850
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700851 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700852
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500853 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854 """Prints the status of the repository to stdout.
855 """
856 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500857 if output_redir:
858 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700859 cmd = ['diff']
860 if out.is_on:
861 cmd.append('--color')
862 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300863 if absolute_paths:
864 cmd.append('--src-prefix=a/%s/' % self.relpath)
865 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700866 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400867 try:
868 p = GitCommand(self,
869 cmd,
870 capture_stdout=True,
871 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500872 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400873 except GitError as e:
874 out.nl()
875 out.project('project %s/' % self.relpath)
876 out.nl()
877 out.fail('%s', str(e))
878 out.nl()
879 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500880 if p.stdout:
881 out.nl()
882 out.project('project %s/' % self.relpath)
883 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500884 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400885 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700887# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900888 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889 """Was the branch published (uploaded) for code review?
890 If so, returns the SHA-1 hash of the last published
891 state for the branch.
892 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700893 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900894 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700895 try:
896 return self.bare_git.rev_parse(key)
897 except GitError:
898 return None
899 else:
900 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900901 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700902 except KeyError:
903 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904
David Pursehouse8a68ff92012-09-24 12:15:13 +0900905 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906 """Prunes any stale published refs.
907 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900908 if all_refs is None:
909 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700910 heads = set()
911 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530912 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913 if name.startswith(R_HEADS):
914 heads.add(name)
915 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900916 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530918 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700919 n = name[len(R_PUB):]
920 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900921 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700923 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 """List any branches which can be uploaded for review.
925 """
926 heads = {}
927 pubed = {}
928
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530929 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900931 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900933 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934
935 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530936 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900937 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700938 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700939 if selected_branch and branch != selected_branch:
940 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800942 rb = self.GetUploadableBranch(branch)
943 if rb:
944 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945 return ready
946
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800947 def GetUploadableBranch(self, branch_name):
948 """Get a single uploadable branch, or None.
949 """
950 branch = self.GetBranch(branch_name)
951 base = branch.LocalMerge
952 if branch.LocalMerge:
953 rb = ReviewableBranch(self, branch, base)
954 if rb.commits:
955 return rb
956 return None
957
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700958 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100959 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500960 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700961 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500962 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500963 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200964 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700965 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200966 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200967 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800968 validate_certs=True,
969 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970 """Uploads the named branch for code review.
971 """
972 if branch is None:
973 branch = self.CurrentBranch
974 if branch is None:
975 raise GitError('not currently on a branch')
976
977 branch = self.GetBranch(branch)
978 if not branch.LocalMerge:
979 raise GitError('branch %s does not track a remote' % branch.name)
980 if not branch.remote.review:
981 raise GitError('remote %s has no review url' % branch.remote.name)
982
Bryan Jacobsf609f912013-05-06 13:36:24 -0400983 if dest_branch is None:
984 dest_branch = self.dest_branch
985 if dest_branch is None:
986 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987 if not dest_branch.startswith(R_HEADS):
988 dest_branch = R_HEADS + dest_branch
989
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800990 if not branch.remote.projectname:
991 branch.remote.projectname = self.name
992 branch.remote.Save()
993
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200994 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800995 if url is None:
996 raise UploadError('review not configured')
997 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -0500998 if dryrun:
999 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001000
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001001 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001002 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001003
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001004 for push_option in (push_options or []):
1005 cmd.append('-o')
1006 cmd.append(push_option)
1007
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001008 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001009
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001010 if dest_branch.startswith(R_HEADS):
1011 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001012
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001013 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001014 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001015 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001016 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001017 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001018 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001019
David Pursehousef25a3702018-11-14 19:01:22 -08001020 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001021 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001022 if notify:
1023 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001024 if private:
1025 opts += ['private']
1026 if wip:
1027 opts += ['wip']
1028 if opts:
1029 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001030 cmd.append(ref_spec)
1031
Anthony King7bdac712014-07-16 12:56:40 +01001032 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001033 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001034
Mike Frysingerd7f86832020-11-19 19:18:46 -05001035 if not dryrun:
1036 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1037 self.bare_git.UpdateRef(R_PUB + branch.name,
1038 R_HEADS + branch.name,
1039 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001040
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001041# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001042 def _ExtractArchive(self, tarpath, path=None):
1043 """Extract the given tar on its current location
1044
1045 Args:
1046 - tarpath: The path to the actual tar file
1047
1048 """
1049 try:
1050 with tarfile.open(tarpath, 'r') as tar:
1051 tar.extractall(path=path)
1052 return True
1053 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001054 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001055 return False
1056
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001057 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001058 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001059 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001060 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001061 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001062 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001063 force_sync=False,
1064 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001065 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001066 archive=False,
1067 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001068 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001069 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001070 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001071 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001072 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001073 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001074 """Perform only the network IO portion of the sync process.
1075 Local working directory/branch state is not affected.
1076 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001077 if archive and not isinstance(self, MetaProject):
1078 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001079 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001080 return False
1081
1082 name = self.relpath.replace('\\', '/')
1083 name = name.replace('/', '_')
1084 tarpath = '%s.tar' % name
1085 topdir = self.manifest.topdir
1086
1087 try:
1088 self._FetchArchive(tarpath, cwd=topdir)
1089 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001090 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001091 return False
1092
1093 # From now on, we only need absolute tarpath
1094 tarpath = os.path.join(topdir, tarpath)
1095
1096 if not self._ExtractArchive(tarpath, path=topdir):
1097 return False
1098 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001099 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001100 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001101 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001102 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001103 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001104
1105 # If the shared object dir already exists, don't try to rebootstrap with a
1106 # clone bundle download. We should have the majority of objects already.
1107 if clone_bundle and os.path.exists(self.objdir):
1108 clone_bundle = False
1109
Raman Tennetif32f2432021-04-12 20:57:25 -07001110 if self.name in partial_clone_exclude:
1111 clone_bundle = True
1112 clone_filter = None
1113
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001114 if is_new is None:
1115 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001116 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001117 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001118 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001119 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001120 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001121
1122 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001123 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001124 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001125 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001126 # This works for both absolute and relative alternate directories.
1127 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001128 except IOError:
1129 alt_dir = None
1130 else:
1131 alt_dir = None
1132
Mike Frysingere50b6a72020-02-19 01:45:48 -05001133 if (clone_bundle
1134 and alt_dir is None
1135 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001136 is_new = False
1137
Mike Frysinger73561142021-05-03 01:10:09 -04001138 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001139 if self.sync_c:
1140 current_branch_only = True
1141 elif not self.manifest._loaded:
1142 # Manifest cannot check defaults until it syncs.
1143 current_branch_only = False
1144 elif self.manifest.default.sync_c:
1145 current_branch_only = True
1146
Mike Frysingerd68ed632021-05-03 01:21:35 -04001147 if tags is None:
1148 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001149
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001150 if self.clone_depth:
1151 depth = self.clone_depth
1152 else:
1153 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1154
Mike Frysinger521d01b2020-02-17 01:51:49 -05001155 # See if we can skip the network fetch entirely.
1156 if not (optimized_fetch and
1157 (ID_RE.match(self.revisionExpr) and
1158 self._CheckForImmutableRevision())):
1159 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001160 initial=is_new,
1161 quiet=quiet, verbose=verbose, output_redir=output_redir,
1162 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001163 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001164 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001165 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001166 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001167 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001168
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001169 mp = self.manifest.manifestProject
1170 dissociate = mp.config.GetBoolean('repo.dissociate')
1171 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001172 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001173 if os.path.exists(alternates_file):
1174 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001175 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1176 merge_output=bool(output_redir))
1177 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001178 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001179 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001180 return False
1181 platform_utils.remove(alternates_file)
1182
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001183 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001184 self._InitMRef()
1185 else:
1186 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001187 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1188 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001189 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001190
1191 def PostRepoUpgrade(self):
1192 self._InitHooks()
1193
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001194 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001195 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001196 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001197 for copyfile in self.copyfiles:
1198 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001199 for linkfile in self.linkfiles:
1200 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201
Julien Camperguedd654222014-01-09 16:21:37 +01001202 def GetCommitRevisionId(self):
1203 """Get revisionId of a commit.
1204
1205 Use this method instead of GetRevisionId to get the id of the commit rather
1206 than the id of the current git object (for example, a tag)
1207
1208 """
1209 if not self.revisionExpr.startswith(R_TAGS):
1210 return self.GetRevisionId(self._allrefs)
1211
1212 try:
1213 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1214 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001215 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1216 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001217
David Pursehouse8a68ff92012-09-24 12:15:13 +09001218 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001219 if self.revisionId:
1220 return self.revisionId
1221
1222 rem = self.GetRemote(self.remote.name)
1223 rev = rem.ToLocal(self.revisionExpr)
1224
David Pursehouse8a68ff92012-09-24 12:15:13 +09001225 if all_refs is not None and rev in all_refs:
1226 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001227
1228 try:
1229 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1230 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001231 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1232 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001233
Raman Tenneti6a872c92021-01-14 19:17:50 -08001234 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001235 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001236 self.upstream = self.revisionExpr
1237
Raman Tenneti6a872c92021-01-14 19:17:50 -08001238 self.revisionId = revisionId
1239
Martin Kellye4e94d22017-03-21 16:05:12 -07001240 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001241 """Perform only the local IO portion of the sync process.
1242 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001243 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001244 if not os.path.exists(self.gitdir):
1245 syncbuf.fail(self,
1246 'Cannot checkout %s due to missing network sync; Run '
1247 '`repo sync -n %s` first.' %
1248 (self.name, self.name))
1249 return
1250
Martin Kellye4e94d22017-03-21 16:05:12 -07001251 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001252 all_refs = self.bare_ref.all
1253 self.CleanPublishedCache(all_refs)
1254 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001255
Mike Frysinger0458faa2021-03-10 23:35:44 -05001256 # Special case the root of the repo client checkout. Make sure it doesn't
1257 # contain files being checked out to dirs we don't allow.
1258 if self.relpath == '.':
1259 PROTECTED_PATHS = {'.repo'}
1260 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1261 bad_paths = paths & PROTECTED_PATHS
1262 if bad_paths:
1263 syncbuf.fail(self,
1264 'Refusing to checkout project that writes to protected '
1265 'paths: %s' % (', '.join(bad_paths),))
1266 return
1267
David Pursehouse1d947b32012-10-25 12:23:11 +09001268 def _doff():
1269 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001270 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001271
Martin Kellye4e94d22017-03-21 16:05:12 -07001272 def _dosubmodules():
1273 self._SyncSubmodules(quiet=True)
1274
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001275 head = self.work_git.GetHead()
1276 if head.startswith(R_HEADS):
1277 branch = head[len(R_HEADS):]
1278 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001279 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001280 except KeyError:
1281 head = None
1282 else:
1283 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001284
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001285 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286 # Currently on a detached HEAD. The user is assumed to
1287 # not have any local modifications worth worrying about.
1288 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001289 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001290 syncbuf.fail(self, _PriorSyncFailedError())
1291 return
1292
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001293 if head == revid:
1294 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001295 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001296 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001297 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001298 # The copy/linkfile config may have changed.
1299 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001300 return
1301 else:
1302 lost = self._revlist(not_rev(revid), HEAD)
1303 if lost:
1304 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001305
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001307 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001308 if submodules:
1309 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001310 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001311 syncbuf.fail(self, e)
1312 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001313 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001314 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001316 if head == revid:
1317 # No changes; don't do anything further.
1318 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001319 # The copy/linkfile config may have changed.
1320 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001321 return
1322
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001323 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001325 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001327 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001329 syncbuf.info(self,
1330 "leaving %s; does not track upstream",
1331 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001332 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001333 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001334 if submodules:
1335 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001336 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001337 syncbuf.fail(self, e)
1338 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001339 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001340 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001341
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001342 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001343
1344 # See if we can perform a fast forward merge. This can happen if our
1345 # branch isn't in the exact same state as we last published.
1346 try:
1347 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1348 # Skip the published logic.
1349 pub = False
1350 except GitError:
1351 pub = self.WasPublished(branch.name, all_refs)
1352
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001354 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001355 if not_merged:
1356 if upstream_gain:
1357 # The user has published this branch and some of those
1358 # commits are not yet merged upstream. We do not want
1359 # to rewrite the published commits so we punt.
1360 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001361 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001362 "branch %s is published (but not merged) and is now "
1363 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001364 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001365 elif pub == head:
1366 # All published commits are merged, and thus we are a
1367 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001368 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001369 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001370 if submodules:
1371 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001372 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001374 # Examine the local commits not in the remote. Find the
1375 # last one attributed to this user, if any.
1376 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001377 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001378 last_mine = None
1379 cnt_mine = 0
1380 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001381 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001382 if committer_email == self.UserEmail:
1383 last_mine = commit_id
1384 cnt_mine += 1
1385
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001386 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001387 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001388
1389 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001390 syncbuf.fail(self, _DirtyError())
1391 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001392
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001393 # If the upstream switched on us, warn the user.
1394 #
1395 if branch.merge != self.revisionExpr:
1396 if branch.merge and self.revisionExpr:
1397 syncbuf.info(self,
1398 'manifest switched %s...%s',
1399 branch.merge,
1400 self.revisionExpr)
1401 elif branch.merge:
1402 syncbuf.info(self,
1403 'manifest no longer tracks %s',
1404 branch.merge)
1405
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001406 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001408 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001409 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001410 syncbuf.info(self,
1411 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001412 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001413
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001414 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001415 if not ID_RE.match(self.revisionExpr):
1416 # in case of manifest sync the revisionExpr might be a SHA1
1417 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001418 if not branch.merge.startswith('refs/'):
1419 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001420 branch.Save()
1421
Mike Pontillod3153822012-02-28 11:53:24 -08001422 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001423 def _docopyandlink():
1424 self._CopyAndLinkFiles()
1425
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001426 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001427 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001428 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001429 if submodules:
1430 syncbuf.later2(self, _dosubmodules)
1431 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001432 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001433 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001434 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001435 if submodules:
1436 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001437 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001438 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001439 syncbuf.fail(self, e)
1440 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001441 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001442 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001443 if submodules:
1444 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001445
Mike Frysingere6a202f2019-08-02 15:57:57 -04001446 def AddCopyFile(self, src, dest, topdir):
1447 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001448
Mike Frysingere6a202f2019-08-02 15:57:57 -04001449 No filesystem changes occur here. Actual copying happens later on.
1450
1451 Paths should have basic validation run on them before being queued.
1452 Further checking will be handled when the actual copy happens.
1453 """
1454 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1455
1456 def AddLinkFile(self, src, dest, topdir):
1457 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1458
1459 No filesystem changes occur here. Actual linking happens later on.
1460
1461 Paths should have basic validation run on them before being queued.
1462 Further checking will be handled when the actual link happens.
1463 """
1464 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001465
James W. Mills24c13082012-04-12 15:04:13 -05001466 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001467 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001468
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001469 def DownloadPatchSet(self, change_id, patch_id):
1470 """Download a single patch set of a single change to FETCH_HEAD.
1471 """
1472 remote = self.GetRemote(self.remote.name)
1473
1474 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001475 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001476 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001477 if GitCommand(self, cmd, bare=True).Wait() != 0:
1478 return None
1479 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001480 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001481 change_id,
1482 patch_id,
1483 self.bare_git.rev_parse('FETCH_HEAD'))
1484
Mike Frysingerc0d18662020-02-19 19:19:18 -05001485 def DeleteWorktree(self, quiet=False, force=False):
1486 """Delete the source checkout and any other housekeeping tasks.
1487
1488 This currently leaves behind the internal .repo/ cache state. This helps
1489 when switching branches or manifest changes get reverted as we don't have
1490 to redownload all the git objects. But we should do some GC at some point.
1491
1492 Args:
1493 quiet: Whether to hide normal messages.
1494 force: Always delete tree even if dirty.
1495
1496 Returns:
1497 True if the worktree was completely cleaned out.
1498 """
1499 if self.IsDirty():
1500 if force:
1501 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1502 (self.relpath,), file=sys.stderr)
1503 else:
1504 print('error: %s: Cannot remove project: uncommitted changes are '
1505 'present.\n' % (self.relpath,), file=sys.stderr)
1506 return False
1507
1508 if not quiet:
1509 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1510
1511 # Unlock and delink from the main worktree. We don't use git's worktree
1512 # remove because it will recursively delete projects -- we handle that
1513 # ourselves below. https://crbug.com/git/48
1514 if self.use_git_worktrees:
1515 needle = platform_utils.realpath(self.gitdir)
1516 # Find the git worktree commondir under .repo/worktrees/.
1517 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1518 assert output.startswith('worktree '), output
1519 commondir = output[9:]
1520 # Walk each of the git worktrees to see where they point.
1521 configs = os.path.join(commondir, 'worktrees')
1522 for name in os.listdir(configs):
1523 gitdir = os.path.join(configs, name, 'gitdir')
1524 with open(gitdir) as fp:
1525 relpath = fp.read().strip()
1526 # Resolve the checkout path and see if it matches this project.
1527 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1528 if fullpath == needle:
1529 platform_utils.rmtree(os.path.join(configs, name))
1530
1531 # Delete the .git directory first, so we're less likely to have a partially
1532 # working git repository around. There shouldn't be any git projects here,
1533 # so rmtree works.
1534
1535 # Try to remove plain files first in case of git worktrees. If this fails
1536 # for any reason, we'll fall back to rmtree, and that'll display errors if
1537 # it can't remove things either.
1538 try:
1539 platform_utils.remove(self.gitdir)
1540 except OSError:
1541 pass
1542 try:
1543 platform_utils.rmtree(self.gitdir)
1544 except OSError as e:
1545 if e.errno != errno.ENOENT:
1546 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1547 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1548 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1549 return False
1550
1551 # Delete everything under the worktree, except for directories that contain
1552 # another git project.
1553 dirs_to_remove = []
1554 failed = False
1555 for root, dirs, files in platform_utils.walk(self.worktree):
1556 for f in files:
1557 path = os.path.join(root, f)
1558 try:
1559 platform_utils.remove(path)
1560 except OSError as e:
1561 if e.errno != errno.ENOENT:
1562 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1563 failed = True
1564 dirs[:] = [d for d in dirs
1565 if not os.path.lexists(os.path.join(root, d, '.git'))]
1566 dirs_to_remove += [os.path.join(root, d) for d in dirs
1567 if os.path.join(root, d) not in dirs_to_remove]
1568 for d in reversed(dirs_to_remove):
1569 if platform_utils.islink(d):
1570 try:
1571 platform_utils.remove(d)
1572 except OSError as e:
1573 if e.errno != errno.ENOENT:
1574 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1575 failed = True
1576 elif not platform_utils.listdir(d):
1577 try:
1578 platform_utils.rmdir(d)
1579 except OSError as e:
1580 if e.errno != errno.ENOENT:
1581 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1582 failed = True
1583 if failed:
1584 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1585 file=sys.stderr)
1586 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1587 return False
1588
1589 # Try deleting parent dirs if they are empty.
1590 path = self.worktree
1591 while path != self.manifest.topdir:
1592 try:
1593 platform_utils.rmdir(path)
1594 except OSError as e:
1595 if e.errno != errno.ENOENT:
1596 break
1597 path = os.path.dirname(path)
1598
1599 return True
1600
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001601# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001602 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001603 """Create a new branch off the manifest's revision.
1604 """
Simran Basib9a1b732015-08-20 12:19:28 -07001605 if not branch_merge:
1606 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001607 head = self.work_git.GetHead()
1608 if head == (R_HEADS + name):
1609 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001610
David Pursehouse8a68ff92012-09-24 12:15:13 +09001611 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001612 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001613 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001614 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001615 capture_stdout=True,
1616 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001617
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001618 branch = self.GetBranch(name)
1619 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001620 branch.merge = branch_merge
1621 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1622 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001623
1624 if revision is None:
1625 revid = self.GetRevisionId(all_refs)
1626 else:
1627 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001628
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001629 if head.startswith(R_HEADS):
1630 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001631 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001632 except KeyError:
1633 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001634 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001635 ref = R_HEADS + name
1636 self.work_git.update_ref(ref, revid)
1637 self.work_git.symbolic_ref(HEAD, ref)
1638 branch.Save()
1639 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001640
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001641 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001642 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001643 capture_stdout=True,
1644 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001645 branch.Save()
1646 return True
1647 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001648
Wink Saville02d79452009-04-10 13:01:24 -07001649 def CheckoutBranch(self, name):
1650 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001651
1652 Args:
1653 name: The name of the branch to checkout.
1654
1655 Returns:
1656 True if the checkout succeeded; False if it didn't; None if the branch
1657 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001658 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001659 rev = R_HEADS + name
1660 head = self.work_git.GetHead()
1661 if head == rev:
1662 # Already on the branch
1663 #
1664 return True
Wink Saville02d79452009-04-10 13:01:24 -07001665
David Pursehouse8a68ff92012-09-24 12:15:13 +09001666 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001667 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001668 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001669 except KeyError:
1670 # Branch does not exist in this project
1671 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001672 return None
Wink Saville02d79452009-04-10 13:01:24 -07001673
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001674 if head.startswith(R_HEADS):
1675 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001676 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001677 except KeyError:
1678 head = None
1679
1680 if head == revid:
1681 # Same revision; just update HEAD to point to the new
1682 # target branch, but otherwise take no other action.
1683 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001684 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1685 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001686 return True
1687
1688 return GitCommand(self,
1689 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001690 capture_stdout=True,
1691 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001692
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001693 def AbandonBranch(self, name):
1694 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001695
1696 Args:
1697 name: The name of the branch to abandon.
1698
1699 Returns:
1700 True if the abandon succeeded; False if it didn't; None if the branch
1701 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001702 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001703 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001704 all_refs = self.bare_ref.all
1705 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001706 # Doesn't exist
1707 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001708
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001709 head = self.work_git.GetHead()
1710 if head == rev:
1711 # We can't destroy the branch while we are sitting
1712 # on it. Switch to a detached HEAD.
1713 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001714 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001715
David Pursehouse8a68ff92012-09-24 12:15:13 +09001716 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001717 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001718 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001719 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001720 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001721
1722 return GitCommand(self,
1723 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001724 capture_stdout=True,
1725 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001726
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001727 def PruneHeads(self):
1728 """Prune any topic branches already merged into upstream.
1729 """
1730 cb = self.CurrentBranch
1731 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001732 left = self._allrefs
1733 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001734 if name.startswith(R_HEADS):
1735 name = name[len(R_HEADS):]
1736 if cb is None or name != cb:
1737 kill.append(name)
1738
Mike Frysingera3794e92021-03-11 23:24:01 -05001739 # Minor optimization: If there's nothing to prune, then don't try to read
1740 # any project state.
1741 if not kill and not cb:
1742 return []
1743
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001744 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001745 if cb is not None \
1746 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001747 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001748 self.work_git.DetachHead(HEAD)
1749 kill.append(cb)
1750
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001751 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001752 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001753
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001754 try:
1755 self.bare_git.DetachHead(rev)
1756
1757 b = ['branch', '-d']
1758 b.extend(kill)
1759 b = GitCommand(self, b, bare=True,
1760 capture_stdout=True,
1761 capture_stderr=True)
1762 b.Wait()
1763 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001764 if ID_RE.match(old):
1765 self.bare_git.DetachHead(old)
1766 else:
1767 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001768 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001770 for branch in kill:
1771 if (R_HEADS + branch) not in left:
1772 self.CleanPublishedCache()
1773 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001774
1775 if cb and cb not in kill:
1776 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001777 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001778
1779 kept = []
1780 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001781 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001782 branch = self.GetBranch(branch)
1783 base = branch.LocalMerge
1784 if not base:
1785 base = rev
1786 kept.append(ReviewableBranch(self, branch, base))
1787 return kept
1788
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001789# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001790 def GetRegisteredSubprojects(self):
1791 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001792
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001793 def rec(subprojects):
1794 if not subprojects:
1795 return
1796 result.extend(subprojects)
1797 for p in subprojects:
1798 rec(p.subprojects)
1799 rec(self.subprojects)
1800 return result
1801
1802 def _GetSubmodules(self):
1803 # Unfortunately we cannot call `git submodule status --recursive` here
1804 # because the working tree might not exist yet, and it cannot be used
1805 # without a working tree in its current implementation.
1806
1807 def get_submodules(gitdir, rev):
1808 # Parse .gitmodules for submodule sub_paths and sub_urls
1809 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1810 if not sub_paths:
1811 return []
1812 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1813 # revision of submodule repository
1814 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1815 submodules = []
1816 for sub_path, sub_url in zip(sub_paths, sub_urls):
1817 try:
1818 sub_rev = sub_revs[sub_path]
1819 except KeyError:
1820 # Ignore non-exist submodules
1821 continue
1822 submodules.append((sub_rev, sub_path, sub_url))
1823 return submodules
1824
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001825 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1826 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001827
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001828 def parse_gitmodules(gitdir, rev):
1829 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1830 try:
Anthony King7bdac712014-07-16 12:56:40 +01001831 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1832 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001833 except GitError:
1834 return [], []
1835 if p.Wait() != 0:
1836 return [], []
1837
1838 gitmodules_lines = []
1839 fd, temp_gitmodules_path = tempfile.mkstemp()
1840 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001841 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001842 os.close(fd)
1843 cmd = ['config', '--file', temp_gitmodules_path, '--list']
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 if p.Wait() != 0:
1847 return [], []
1848 gitmodules_lines = p.stdout.split('\n')
1849 except GitError:
1850 return [], []
1851 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001852 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001853
1854 names = set()
1855 paths = {}
1856 urls = {}
1857 for line in gitmodules_lines:
1858 if not line:
1859 continue
1860 m = re_path.match(line)
1861 if m:
1862 names.add(m.group(1))
1863 paths[m.group(1)] = m.group(2)
1864 continue
1865 m = re_url.match(line)
1866 if m:
1867 names.add(m.group(1))
1868 urls[m.group(1)] = m.group(2)
1869 continue
1870 names = sorted(names)
1871 return ([paths.get(name, '') for name in names],
1872 [urls.get(name, '') for name in names])
1873
1874 def git_ls_tree(gitdir, rev, paths):
1875 cmd = ['ls-tree', rev, '--']
1876 cmd.extend(paths)
1877 try:
Anthony King7bdac712014-07-16 12:56:40 +01001878 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1879 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001880 except GitError:
1881 return []
1882 if p.Wait() != 0:
1883 return []
1884 objects = {}
1885 for line in p.stdout.split('\n'):
1886 if not line.strip():
1887 continue
1888 object_rev, object_path = line.split()[2:4]
1889 objects[object_path] = object_rev
1890 return objects
1891
1892 try:
1893 rev = self.GetRevisionId()
1894 except GitError:
1895 return []
1896 return get_submodules(self.gitdir, rev)
1897
1898 def GetDerivedSubprojects(self):
1899 result = []
1900 if not self.Exists:
1901 # If git repo does not exist yet, querying its submodules will
1902 # mess up its states; so return here.
1903 return result
1904 for rev, path, url in self._GetSubmodules():
1905 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001906 relpath, worktree, gitdir, objdir = \
1907 self.manifest.GetSubprojectPaths(self, name, path)
1908 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001909 if project:
1910 result.extend(project.GetDerivedSubprojects())
1911 continue
David James8d201162013-10-11 17:03:19 -07001912
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001913 if url.startswith('..'):
1914 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001915 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001916 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001917 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001918 review=self.remote.review,
1919 revision=self.remote.revision)
1920 subproject = Project(manifest=self.manifest,
1921 name=name,
1922 remote=remote,
1923 gitdir=gitdir,
1924 objdir=objdir,
1925 worktree=worktree,
1926 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001927 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001928 revisionId=rev,
1929 rebase=self.rebase,
1930 groups=self.groups,
1931 sync_c=self.sync_c,
1932 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001933 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001934 parent=self,
1935 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001936 result.append(subproject)
1937 result.extend(subproject.GetDerivedSubprojects())
1938 return result
1939
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001940# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001941 def EnableRepositoryExtension(self, key, value='true', version=1):
1942 """Enable git repository extension |key| with |value|.
1943
1944 Args:
1945 key: The extension to enabled. Omit the "extensions." prefix.
1946 value: The value to use for the extension.
1947 version: The minimum git repository version needed.
1948 """
1949 # Make sure the git repo version is new enough already.
1950 found_version = self.config.GetInt('core.repositoryFormatVersion')
1951 if found_version is None:
1952 found_version = 0
1953 if found_version < version:
1954 self.config.SetString('core.repositoryFormatVersion', str(version))
1955
1956 # Enable the extension!
1957 self.config.SetString('extensions.%s' % (key,), value)
1958
Mike Frysinger50a81de2020-09-06 15:51:21 -04001959 def ResolveRemoteHead(self, name=None):
1960 """Find out what the default branch (HEAD) points to.
1961
1962 Normally this points to refs/heads/master, but projects are moving to main.
1963 Support whatever the server uses rather than hardcoding "master" ourselves.
1964 """
1965 if name is None:
1966 name = self.remote.name
1967
1968 # The output will look like (NB: tabs are separators):
1969 # ref: refs/heads/master HEAD
1970 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1971 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1972
1973 for line in output.splitlines():
1974 lhs, rhs = line.split('\t', 1)
1975 if rhs == 'HEAD' and lhs.startswith('ref:'):
1976 return lhs[4:].strip()
1977
1978 return None
1979
Zac Livingstone4332262017-06-16 08:56:09 -06001980 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001981 try:
1982 # if revision (sha or tag) is not present then following function
1983 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08001984 self.bare_git.rev_list('-1', '--missing=allow-any',
1985 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00001986 if self.upstream:
1987 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
1988 self.bare_git.rev_list('-1', '--missing=allow-any',
1989 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00001990 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05001991 return True
1992 except GitError:
1993 # There is no such persistent revision. We have to fetch it.
1994 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001995
Julien Campergue335f5ef2013-10-16 11:02:35 +02001996 def _FetchArchive(self, tarpath, cwd=None):
1997 cmd = ['archive', '-v', '-o', tarpath]
1998 cmd.append('--remote=%s' % self.remote.url)
1999 cmd.append('--prefix=%s/' % self.relpath)
2000 cmd.append(self.revisionExpr)
2001
2002 command = GitCommand(self, cmd, cwd=cwd,
2003 capture_stdout=True,
2004 capture_stderr=True)
2005
2006 if command.Wait() != 0:
2007 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2008
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002009 def _RemoteFetch(self, name=None,
2010 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002011 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002012 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002013 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002014 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002015 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002016 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002017 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002018 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002019 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002020 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002021 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002022 clone_filter=None,
2023 retry_fetches=2,
2024 retry_sleep_initial_sec=4.0,
2025 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002026 is_sha1 = False
2027 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002028 # The depth should not be used when fetching to a mirror because
2029 # it will result in a shallow repository that cannot be cloned or
2030 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002031 # The repo project should also never be synced with partial depth.
2032 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2033 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002034
Shawn Pearce69e04d82014-01-29 12:48:54 -08002035 if depth:
2036 current_branch_only = True
2037
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002038 if ID_RE.match(self.revisionExpr) is not None:
2039 is_sha1 = True
2040
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002041 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002042 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002043 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002044 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002045 elif self.upstream and self.upstream.startswith(R_TAGS):
2046 # This is a tag and its commit id should never change.
2047 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002048
2049 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002050 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002051 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002052 print('Skipped fetching project %s (already have persistent ref)'
2053 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002054 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002055 if is_sha1 and not depth:
2056 # When syncing a specific commit and --depth is not set:
2057 # * if upstream is explicitly specified and is not a sha1, fetch only
2058 # upstream as users expect only upstream to be fetch.
2059 # Note: The commit might not be in upstream in which case the sync
2060 # will fail.
2061 # * otherwise, fetch all branches to make sure we end up with the
2062 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002063 if self.upstream:
2064 current_branch_only = not ID_RE.match(self.upstream)
2065 else:
2066 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002067
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002068 if not name:
2069 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002070
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002071 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002072 if not remote.PreConnectFetch(ssh_proxy):
2073 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002074
Shawn O. Pearce88443382010-10-08 10:02:09 +02002075 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002076 if alt_dir and 'objects' == os.path.basename(alt_dir):
2077 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002078 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002079
David Pursehouse8a68ff92012-09-24 12:15:13 +09002080 all_refs = self.bare_ref.all
2081 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002082 tmp = set()
2083
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302084 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002085 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002086 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002087 all_refs[r] = ref_id
2088 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002089 continue
2090
David Pursehouse8a68ff92012-09-24 12:15:13 +09002091 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002092 continue
2093
David Pursehouse8a68ff92012-09-24 12:15:13 +09002094 r = 'refs/_alt/%s' % ref_id
2095 all_refs[r] = ref_id
2096 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002097 tmp.add(r)
2098
heping3d7bbc92017-04-12 19:51:47 +08002099 tmp_packed_lines = []
2100 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002101
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302102 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002103 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002104 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002105 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002106 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002107
heping3d7bbc92017-04-12 19:51:47 +08002108 tmp_packed = ''.join(tmp_packed_lines)
2109 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002110 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002111 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002112 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002113
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002114 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002115
Xin Li745be2e2019-06-03 11:24:30 -07002116 if clone_filter:
2117 git_require((2, 19, 0), fail=True, msg='partial clones')
2118 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002119 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002120
Conley Owensf97e8382015-01-21 11:12:46 -08002121 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002122 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002123 else:
2124 # If this repo has shallow objects, then we don't know which refs have
2125 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2126 # do this with projects that don't have shallow objects, since it is less
2127 # efficient.
2128 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2129 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002130
Mike Frysinger4847e052020-02-22 00:07:35 -05002131 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002132 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002133 if not quiet and sys.stdout.isatty():
2134 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002135 if not self.worktree:
2136 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002137 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002138
Mike Frysingere57f1142019-03-18 21:27:54 -04002139 if force_sync:
2140 cmd.append('--force')
2141
David Pursehouse74cfd272015-10-14 10:50:15 +09002142 if prune:
2143 cmd.append('--prune')
2144
Martin Kellye4e94d22017-03-21 16:05:12 -07002145 if submodules:
2146 cmd.append('--recurse-submodules=on-demand')
2147
Kuang-che Wu6856f982019-11-25 12:37:55 +08002148 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002149 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002150 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002151 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002152 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002153 spec.append('tag')
2154 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002155
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302156 if self.manifest.IsMirror and not current_branch_only:
2157 branch = None
2158 else:
2159 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002160 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002161 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002162 # Shallow checkout of a specific commit, fetch from that commit and not
2163 # the heads only as the commit might be deeper in the history.
2164 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002165 if self.upstream:
2166 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002167 else:
2168 if is_sha1:
2169 branch = self.upstream
2170 if branch is not None and branch.strip():
2171 if not branch.startswith('refs/'):
2172 branch = R_HEADS + branch
2173 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2174
2175 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2176 # whole repo.
2177 if self.manifest.IsMirror and not spec:
2178 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2179
2180 # If using depth then we should not get all the tags since they may
2181 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002182 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002183 cmd.append('--no-tags')
2184 else:
2185 cmd.append('--tags')
2186 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2187
Conley Owens80b87fe2014-05-09 17:13:44 -07002188 cmd.extend(spec)
2189
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002190 # At least one retry minimum due to git remote prune.
2191 retry_fetches = max(retry_fetches, 2)
2192 retry_cur_sleep = retry_sleep_initial_sec
2193 ok = prune_tried = False
2194 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002195 gitcmd = GitCommand(
2196 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2197 ssh_proxy=ssh_proxy,
2198 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002199 if gitcmd.stdout and not quiet and output_redir:
2200 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002201 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002202 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002203 ok = True
2204 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002205
2206 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002207 elif (gitcmd.stdout and
2208 'error:' in gitcmd.stdout and
2209 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002210 # Fallthru to sleep+retry logic at the bottom.
2211 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002212
Mike Frysinger6823bc22021-04-15 02:06:28 -04002213 # Try to prune remote branches once in case there are conflicts.
2214 # For example, if the remote had refs/heads/upstream, but deleted that and
2215 # now has refs/heads/upstream/foo.
2216 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002217 'error:' in gitcmd.stdout and
2218 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002219 not prune_tried):
2220 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002221 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002222 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002223 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002224 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002225 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002226 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002227 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002228 continue
Brian Harring14a66742012-09-28 20:21:57 -07002229 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002230 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2231 # in sha1 mode, we just tried sync'ing from the upstream field; it
2232 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002233 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002234 elif ret < 0:
2235 # Git died with a signal, exit immediately
2236 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002237
2238 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002239 if not verbose and gitcmd.stdout:
2240 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002241 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002242 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2243 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002244 time.sleep(retry_cur_sleep)
2245 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2246 MAXIMUM_RETRY_SLEEP_SEC)
2247 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2248 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002249
2250 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002251 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002252 if old_packed != '':
2253 _lwrite(packed_refs, old_packed)
2254 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002255 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002256 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002257
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002258 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002259 # We just synced the upstream given branch; verify we
2260 # got what we wanted, else trigger a second run of all
2261 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002262 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002263 # Sync the current branch only with depth set to None.
2264 # We always pass depth=None down to avoid infinite recursion.
2265 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002266 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002267 current_branch_only=current_branch_only and depth,
2268 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002269 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002270
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002271 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002272
Mike Frysingere50b6a72020-02-19 01:45:48 -05002273 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002274 if initial and \
2275 (self.manifest.manifestProject.config.GetString('repo.depth') or
2276 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002277 return False
2278
2279 remote = self.GetRemote(self.remote.name)
2280 bundle_url = remote.url + '/clone.bundle'
2281 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002282 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2283 'persistent-http',
2284 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002285 return False
2286
2287 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2288 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2289
2290 exist_dst = os.path.exists(bundle_dst)
2291 exist_tmp = os.path.exists(bundle_tmp)
2292
2293 if not initial and not exist_dst and not exist_tmp:
2294 return False
2295
2296 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002297 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2298 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002299 if not exist_dst:
2300 return False
2301
2302 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002303 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002304 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002305 if not quiet and sys.stdout.isatty():
2306 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002307 if not self.worktree:
2308 cmd.append('--update-head-ok')
2309 cmd.append(bundle_dst)
2310 for f in remote.fetch:
2311 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002312 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002313
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002314 ok = GitCommand(
2315 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002316 platform_utils.remove(bundle_dst, missing_ok=True)
2317 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002318 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002319
Mike Frysingere50b6a72020-02-19 01:45:48 -05002320 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002321 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002322
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002323 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002324 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002325 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002326 if os.path.exists(tmpPath):
2327 size = os.stat(tmpPath).st_size
2328 if size >= 1024:
2329 cmd += ['--continue-at', '%d' % (size,)]
2330 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002331 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002332 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002333 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002334 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002335 if proxy:
2336 cmd += ['--proxy', proxy]
2337 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2338 cmd += ['--proxy', os.environ['http_proxy']]
2339 if srcUrl.startswith('persistent-https'):
2340 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2341 elif srcUrl.startswith('persistent-http'):
2342 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002343 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002344
Dave Borowitz137d0132015-01-02 11:12:54 -08002345 if IsTrace():
2346 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002347 if verbose:
2348 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2349 stdout = None if verbose else subprocess.PIPE
2350 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002351 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002352 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002353 except OSError:
2354 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002355
Mike Frysingere50b6a72020-02-19 01:45:48 -05002356 (output, _) = proc.communicate()
2357 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002358
Dave Borowitz137d0132015-01-02 11:12:54 -08002359 if curlret == 22:
2360 # From curl man page:
2361 # 22: HTTP page not retrieved. The requested url was not found or
2362 # returned another error with the HTTP error code being 400 or above.
2363 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002364 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002365 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2366 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002367 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002368 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002369 elif curlret and not verbose and output:
2370 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002371
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002372 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002373 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002374 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002375 return True
2376 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002377 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002378 return False
2379 else:
2380 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002381
Kris Giesingc8d882a2014-12-23 13:02:32 -08002382 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002383 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002384 with open(path, 'rb') as f:
2385 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002386 return True
2387 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002388 if not quiet:
2389 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002390 return False
2391 except OSError:
2392 return False
2393
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002394 def _Checkout(self, rev, quiet=False):
2395 cmd = ['checkout']
2396 if quiet:
2397 cmd.append('-q')
2398 cmd.append(rev)
2399 cmd.append('--')
2400 if GitCommand(self, cmd).Wait() != 0:
2401 if self._allrefs:
2402 raise GitError('%s checkout %s ' % (self.name, rev))
2403
Mike Frysinger915fda12020-03-22 12:15:20 -04002404 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002405 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002406 if ffonly:
2407 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002408 if record_origin:
2409 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002410 cmd.append(rev)
2411 cmd.append('--')
2412 if GitCommand(self, cmd).Wait() != 0:
2413 if self._allrefs:
2414 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2415
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302416 def _LsRemote(self, refs):
2417 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302418 p = GitCommand(self, cmd, capture_stdout=True)
2419 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002420 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302421 return None
2422
Anthony King7bdac712014-07-16 12:56:40 +01002423 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002424 cmd = ['revert']
2425 cmd.append('--no-edit')
2426 cmd.append(rev)
2427 cmd.append('--')
2428 if GitCommand(self, cmd).Wait() != 0:
2429 if self._allrefs:
2430 raise GitError('%s revert %s ' % (self.name, rev))
2431
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002432 def _ResetHard(self, rev, quiet=True):
2433 cmd = ['reset', '--hard']
2434 if quiet:
2435 cmd.append('-q')
2436 cmd.append(rev)
2437 if GitCommand(self, cmd).Wait() != 0:
2438 raise GitError('%s reset --hard %s ' % (self.name, rev))
2439
Martin Kellye4e94d22017-03-21 16:05:12 -07002440 def _SyncSubmodules(self, quiet=True):
2441 cmd = ['submodule', 'update', '--init', '--recursive']
2442 if quiet:
2443 cmd.append('-q')
2444 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002445 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002446
Anthony King7bdac712014-07-16 12:56:40 +01002447 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002448 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002449 if onto is not None:
2450 cmd.extend(['--onto', onto])
2451 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002452 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002453 raise GitError('%s rebase %s ' % (self.name, upstream))
2454
Pierre Tardy3d125942012-05-04 12:18:12 +02002455 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002456 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002457 if ffonly:
2458 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002459 if GitCommand(self, cmd).Wait() != 0:
2460 raise GitError('%s merge %s ' % (self.name, head))
2461
David Pursehousee8ace262020-02-13 12:41:15 +09002462 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002463 init_git_dir = not os.path.exists(self.gitdir)
2464 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002465 try:
2466 # Initialize the bare repository, which contains all of the objects.
2467 if init_obj_dir:
2468 os.makedirs(self.objdir)
2469 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002470
Mike Frysinger333c0a42021-11-15 12:39:00 -05002471 self._UpdateHooks(quiet=quiet)
2472
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002473 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002474 # Enable per-worktree config file support if possible. This is more a
2475 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002476 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002477 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002478
Kevin Degib1a07b82015-07-27 13:33:43 -06002479 # If we have a separate directory to hold refs, initialize it as well.
2480 if self.objdir != self.gitdir:
2481 if init_git_dir:
2482 os.makedirs(self.gitdir)
2483
2484 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002485 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002486 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002487 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002488 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002489 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002490 print("Retrying clone after deleting %s" %
2491 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002492 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002493 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2494 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002495 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002496 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002497 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2498 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002499 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002500 raise e
2501 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002502
Kevin Degi384b3c52014-10-16 16:02:58 -06002503 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002504 mp = self.manifest.manifestProject
2505 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002506
Kevin Degib1a07b82015-07-27 13:33:43 -06002507 if ref_dir or mirror_git:
2508 if not mirror_git:
2509 mirror_git = os.path.join(ref_dir, self.name + '.git')
Mike Frysinger152032c2021-12-20 21:17:43 -05002510 repo_git = os.path.join(ref_dir, '.repo', 'project-objects',
2511 self.name + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002512 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2513 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002514
Kevin Degib1a07b82015-07-27 13:33:43 -06002515 if os.path.exists(mirror_git):
2516 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002517 elif os.path.exists(repo_git):
2518 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002519 elif os.path.exists(worktrees_git):
2520 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002521 else:
2522 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002523
Kevin Degib1a07b82015-07-27 13:33:43 -06002524 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002525 if not os.path.isabs(ref_dir):
2526 # The alternate directory is relative to the object database.
2527 ref_dir = os.path.relpath(ref_dir,
2528 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002529 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002530 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002531
Kevin Degib1a07b82015-07-27 13:33:43 -06002532 m = self.manifest.manifestProject.config
2533 for key in ['user.name', 'user.email']:
2534 if m.Has(key, include_defaults=False):
2535 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002536 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002537 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002538 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002539 except Exception:
2540 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002541 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002542 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002543 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002544 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002545
David Pursehousee8ace262020-02-13 12:41:15 +09002546 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002547 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002548 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002549
David Pursehousee8ace262020-02-13 12:41:15 +09002550 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002551 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002552 if not os.path.exists(hooks):
2553 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002554
2555 # Delete sample hooks. They're noise.
2556 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002557 try:
2558 platform_utils.remove(hook, missing_ok=True)
2559 except PermissionError:
2560 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002561
Jonathan Nieder93719792015-03-17 11:29:58 -07002562 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002563 name = os.path.basename(stock_hook)
2564
Victor Boivie65e0f352011-04-18 11:23:29 +02002565 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002566 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002567 # Don't install a Gerrit Code Review hook if this
2568 # project does not appear to use it for reviews.
2569 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002570 # Since the manifest project is one of those, but also
2571 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002572 continue
2573
2574 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002575 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002576 continue
2577 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002578 # If the files are the same, we'll leave it alone. We create symlinks
2579 # below by default but fallback to hardlinks if the OS blocks them.
2580 # So if we're here, it's probably because we made a hardlink below.
2581 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002582 if not quiet:
2583 _warn("%s: Not replacing locally modified %s hook",
2584 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002585 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002586 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002587 platform_utils.symlink(
2588 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002589 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002590 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002591 try:
2592 os.link(stock_hook, dst)
2593 except OSError:
2594 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002595 else:
2596 raise
2597
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002598 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002599 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002600 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002601 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002602 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002603 remote.review = self.remote.review
2604 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002605
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002606 if self.worktree:
2607 remote.ResetFetch(mirror=False)
2608 else:
2609 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002610 remote.Save()
2611
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002612 def _InitMRef(self):
2613 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002614 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002615 # Set up the m/ space to point to the worktree-specific ref space.
2616 # We'll update the worktree-specific ref space on each checkout.
2617 ref = R_M + self.manifest.branch
2618 if not self.bare_ref.symref(ref):
2619 self.bare_git.symbolic_ref(
2620 '-m', 'redirecting to worktree scope',
2621 ref, R_WORKTREE_M + self.manifest.branch)
2622
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002623 # We can't update this ref with git worktrees until it exists.
2624 # We'll wait until the initial checkout to set it.
2625 if not os.path.exists(self.worktree):
2626 return
2627
2628 base = R_WORKTREE_M
2629 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002630
2631 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002632 else:
2633 base = R_M
2634 active_git = self.bare_git
2635
2636 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002637
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002638 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002639 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002640
Remy Böhmer1469c282020-12-15 18:49:02 +01002641 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002642 cur = self.bare_ref.symref(ref)
2643
2644 if self.revisionId:
2645 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2646 msg = 'manifest set to %s' % self.revisionId
2647 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002648 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002649 else:
2650 remote = self.GetRemote(self.remote.name)
2651 dst = remote.ToLocal(self.revisionExpr)
2652 if cur != dst:
2653 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002654 if detach:
2655 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2656 else:
2657 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002658
Mike Frysingerc72bd842021-11-14 03:58:00 -05002659 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002660 # Git worktrees don't use symlinks to share at all.
2661 if self.use_git_worktrees:
2662 return
2663
Mike Frysingerd33dce02021-12-20 18:16:33 -05002664 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002665 # Try to self-heal a bit in simple cases.
2666 dst_path = os.path.join(destdir, name)
2667 src_path = os.path.join(srcdir, name)
2668
Mike Frysingered4f2112020-02-11 23:06:29 -05002669 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002670 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002671 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002672 # Fail if the links are pointing to the wrong place
2673 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002674 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002675 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002676 'work tree. If you\'re comfortable with the '
2677 'possibility of losing the work tree\'s git metadata,'
2678 ' use `repo sync --force-sync {0}` to '
2679 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002680
Mike Frysingerc72bd842021-11-14 03:58:00 -05002681 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002682 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2683
2684 Args:
2685 gitdir: The bare git repository. Must already be initialized.
2686 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002687 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2688 This saves you the effort of initializing |dotgit| yourself.
2689 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002690 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002691 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002692
2693 to_copy = []
2694 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002695 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002696
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002697 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002698 for name in set(to_copy).union(to_symlink):
2699 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002700 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002701 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002702
Kevin Degi384b3c52014-10-16 16:02:58 -06002703 if os.path.lexists(dst):
2704 continue
David James8d201162013-10-11 17:03:19 -07002705
2706 # If the source dir doesn't exist, create an empty dir.
2707 if name in symlink_dirs and not os.path.lexists(src):
2708 os.makedirs(src)
2709
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002710 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002711 platform_utils.symlink(
2712 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002713 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002714 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002715 shutil.copytree(src, dst)
2716 elif os.path.isfile(src):
2717 shutil.copy(src, dst)
2718
David James8d201162013-10-11 17:03:19 -07002719 except OSError as e:
2720 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002721 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002722 else:
2723 raise
2724
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002725 def _InitGitWorktree(self):
2726 """Init the project using git worktrees."""
2727 self.bare_git.worktree('prune')
2728 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2729 self.worktree, self.GetRevisionId())
2730
2731 # Rewrite the internal state files to use relative paths between the
2732 # checkouts & worktrees.
2733 dotgit = os.path.join(self.worktree, '.git')
2734 with open(dotgit, 'r') as fp:
2735 # Figure out the checkout->worktree path.
2736 setting = fp.read()
2737 assert setting.startswith('gitdir:')
2738 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002739 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2740 # of file permissions. Delete it and recreate it from scratch to avoid.
2741 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002742 # Use relative path from checkout->worktree & maintain Unix line endings
2743 # on all OS's to match git behavior.
2744 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002745 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2746 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002747 # Use relative path from worktree->checkout & maintain Unix line endings
2748 # on all OS's to match git behavior.
2749 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002750 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2751
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002752 self._InitMRef()
2753
Martin Kellye4e94d22017-03-21 16:05:12 -07002754 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002755 """Setup the worktree .git path.
2756
2757 This is the user-visible path like src/foo/.git/.
2758
2759 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2760 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2761
2762 Older checkouts had .git/ directories. If we see that, migrate it.
2763
2764 This also handles changes in the manifest. Maybe this project was backed
2765 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2766 the path we point to under .repo/projects/ to match.
2767 """
2768 dotgit = os.path.join(self.worktree, '.git')
2769
2770 # If using an old layout style (a directory), migrate it.
2771 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2772 self._MigrateOldWorkTreeGitDir(dotgit)
2773
2774 init_dotgit = not os.path.exists(dotgit)
2775 if self.use_git_worktrees:
2776 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002777 self._InitGitWorktree()
2778 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002779 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002780 if not init_dotgit:
2781 # See if the project has changed.
2782 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2783 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002784
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002785 if init_dotgit or not os.path.exists(dotgit):
2786 os.makedirs(self.worktree, exist_ok=True)
2787 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002788
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002789 if init_dotgit:
2790 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002791
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002792 # Finish checking out the worktree.
2793 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2794 if GitCommand(self, cmd).Wait() != 0:
2795 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002796
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002797 if submodules:
2798 self._SyncSubmodules(quiet=True)
2799 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002800
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002801 @classmethod
2802 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2803 """Migrate the old worktree .git/ dir style to a symlink.
2804
2805 This logic specifically only uses state from |dotgit| to figure out where to
2806 move content and not |self|. This way if the backing project also changed
2807 places, we only do the .git/ dir to .git symlink migration here. The path
2808 updates will happen independently.
2809 """
2810 # Figure out where in .repo/projects/ it's pointing to.
2811 if not os.path.islink(os.path.join(dotgit, 'refs')):
2812 raise GitError(f'{dotgit}: unsupported checkout state')
2813 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2814
2815 # Remove known symlink paths that exist in .repo/projects/.
2816 KNOWN_LINKS = {
2817 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2818 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2819 }
2820 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2821 SAFE_TO_CLOBBER = {
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002822 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gitk.cache', 'index', 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002823 }
2824
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002825 # First see if we'd succeed before starting the migration.
2826 unknown_paths = []
2827 for name in platform_utils.listdir(dotgit):
2828 # Ignore all temporary/backup names. These are common with vim & emacs.
2829 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2830 continue
2831
2832 dotgit_path = os.path.join(dotgit, name)
2833 if name in KNOWN_LINKS:
2834 if not platform_utils.islink(dotgit_path):
2835 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2836 else:
2837 gitdir_path = os.path.join(gitdir, name)
2838 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2839 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2840 if unknown_paths:
2841 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2842
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002843 # Now walk the paths and sync the .git/ to .repo/projects/.
2844 for name in platform_utils.listdir(dotgit):
2845 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002846
2847 # Ignore all temporary/backup names. These are common with vim & emacs.
2848 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2849 platform_utils.remove(dotgit_path)
2850 elif name in KNOWN_LINKS:
2851 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002852 else:
2853 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002854 platform_utils.remove(gitdir_path, missing_ok=True)
2855 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002856
2857 # Now that the dir should be empty, clear it out, and symlink it over.
2858 platform_utils.rmdir(dotgit)
2859 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002860
Renaud Paquay788e9622017-01-27 11:41:12 -08002861 def _get_symlink_error_message(self):
2862 if platform_utils.isWindows():
2863 return ('Unable to create symbolic link. Please re-run the command as '
2864 'Administrator, or see '
2865 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2866 'for other options.')
2867 return 'filesystem must support symlinks'
2868
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002869 def _revlist(self, *args, **kw):
2870 a = []
2871 a.extend(args)
2872 a.append('--')
2873 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002874
2875 @property
2876 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002877 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002878
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002879 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002880 """Get logs between two revisions of this project."""
2881 comp = '..'
2882 if rev1:
2883 revs = [rev1]
2884 if rev2:
2885 revs.extend([comp, rev2])
2886 cmd = ['log', ''.join(revs)]
2887 out = DiffColoring(self.config)
2888 if out.is_on and color:
2889 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002890 if pretty_format is not None:
2891 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002892 if oneline:
2893 cmd.append('--oneline')
2894
2895 try:
2896 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2897 if log.Wait() == 0:
2898 return log.stdout
2899 except GitError:
2900 # worktree may not exist if groups changed for example. In that case,
2901 # try in gitdir instead.
2902 if not os.path.exists(self.worktree):
2903 return self.bare_git.log(*cmd[1:])
2904 else:
2905 raise
2906 return None
2907
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002908 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2909 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002910 """Get the list of logs from this revision to given revisionId"""
2911 logs = {}
2912 selfId = self.GetRevisionId(self._allrefs)
2913 toId = toProject.GetRevisionId(toProject._allrefs)
2914
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002915 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2916 pretty_format=pretty_format)
2917 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2918 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002919 return logs
2920
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002921 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002922
David James8d201162013-10-11 17:03:19 -07002923 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002924 self._project = project
2925 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002926 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002927
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002928 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2929 def __getstate__(self):
2930 return (self._project, self._bare, self._gitdir)
2931
2932 def __setstate__(self, state):
2933 self._project, self._bare, self._gitdir = state
2934
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002935 def LsOthers(self):
2936 p = GitCommand(self._project,
2937 ['ls-files',
2938 '-z',
2939 '--others',
2940 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002941 bare=False,
David James8d201162013-10-11 17:03:19 -07002942 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002943 capture_stdout=True,
2944 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002945 if p.Wait() == 0:
2946 out = p.stdout
2947 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002948 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002949 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002950 return []
2951
2952 def DiffZ(self, name, *args):
2953 cmd = [name]
2954 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002955 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002956 cmd.extend(args)
2957 p = GitCommand(self._project,
2958 cmd,
David James8d201162013-10-11 17:03:19 -07002959 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002960 bare=False,
2961 capture_stdout=True,
2962 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002963 p.Wait()
2964 r = {}
2965 out = p.stdout
2966 if out:
2967 out = iter(out[:-1].split('\0'))
2968 while out:
2969 try:
2970 info = next(out)
2971 path = next(out)
2972 except StopIteration:
2973 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002974
Mike Frysinger84230002021-02-16 17:08:35 -05002975 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002976
Mike Frysinger84230002021-02-16 17:08:35 -05002977 def __init__(self, path, omode, nmode, oid, nid, state):
2978 self.path = path
2979 self.src_path = None
2980 self.old_mode = omode
2981 self.new_mode = nmode
2982 self.old_id = oid
2983 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002984
Mike Frysinger84230002021-02-16 17:08:35 -05002985 if len(state) == 1:
2986 self.status = state
2987 self.level = None
2988 else:
2989 self.status = state[:1]
2990 self.level = state[1:]
2991 while self.level.startswith('0'):
2992 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002993
Mike Frysinger84230002021-02-16 17:08:35 -05002994 info = info[1:].split(' ')
2995 info = _Info(path, *info)
2996 if info.status in ('R', 'C'):
2997 info.src_path = info.path
2998 info.path = next(out)
2999 r[info.path] = info
3000 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003001
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003002 def GetDotgitPath(self, subpath=None):
3003 """Return the full path to the .git dir.
3004
3005 As a convenience, append |subpath| if provided.
3006 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003007 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003008 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003009 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003010 dotgit = os.path.join(self._project.worktree, '.git')
3011 if os.path.isfile(dotgit):
3012 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3013 with open(dotgit) as fp:
3014 setting = fp.read()
3015 assert setting.startswith('gitdir:')
3016 gitdir = setting.split(':', 1)[1].strip()
3017 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3018
3019 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3020
3021 def GetHead(self):
3022 """Return the ref that HEAD points to."""
3023 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003024 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003025 with open(path) as fd:
3026 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003027 except IOError as e:
3028 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003029 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303030 line = line.decode()
3031 except AttributeError:
3032 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003033 if line.startswith('ref: '):
3034 return line[5:-1]
3035 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003036
3037 def SetHead(self, ref, message=None):
3038 cmdv = []
3039 if message is not None:
3040 cmdv.extend(['-m', message])
3041 cmdv.append(HEAD)
3042 cmdv.append(ref)
3043 self.symbolic_ref(*cmdv)
3044
3045 def DetachHead(self, new, message=None):
3046 cmdv = ['--no-deref']
3047 if message is not None:
3048 cmdv.extend(['-m', message])
3049 cmdv.append(HEAD)
3050 cmdv.append(new)
3051 self.update_ref(*cmdv)
3052
3053 def UpdateRef(self, name, new, old=None,
3054 message=None,
3055 detach=False):
3056 cmdv = []
3057 if message is not None:
3058 cmdv.extend(['-m', message])
3059 if detach:
3060 cmdv.append('--no-deref')
3061 cmdv.append(name)
3062 cmdv.append(new)
3063 if old is not None:
3064 cmdv.append(old)
3065 self.update_ref(*cmdv)
3066
3067 def DeleteRef(self, name, old=None):
3068 if not old:
3069 old = self.rev_parse(name)
3070 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003071 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003072
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003073 def rev_list(self, *args, **kw):
3074 if 'format' in kw:
3075 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3076 else:
3077 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003078 cmdv.extend(args)
3079 p = GitCommand(self._project,
3080 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003081 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003082 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003083 capture_stdout=True,
3084 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003085 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003086 raise GitError('%s rev-list %s: %s' %
3087 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003088 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003089
3090 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003091 """Allow arbitrary git commands using pythonic syntax.
3092
3093 This allows you to do things like:
3094 git_obj.rev_parse('HEAD')
3095
3096 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3097 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003098 Any other positional arguments will be passed to the git command, and the
3099 following keyword arguments are supported:
3100 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003101
3102 Args:
3103 name: The name of the git command to call. Any '_' characters will
3104 be replaced with '-'.
3105
3106 Returns:
3107 A callable object that will try to call git with the named command.
3108 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003109 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003110
Dave Borowitz091f8932012-10-23 17:01:04 -07003111 def runner(*args, **kwargs):
3112 cmdv = []
3113 config = kwargs.pop('config', None)
3114 for k in kwargs:
3115 raise TypeError('%s() got an unexpected keyword argument %r'
3116 % (name, k))
3117 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303118 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003119 cmdv.append('-c')
3120 cmdv.append('%s=%s' % (k, v))
3121 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003122 cmdv.extend(args)
3123 p = GitCommand(self._project,
3124 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003125 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003126 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003127 capture_stdout=True,
3128 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003129 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003130 raise GitError('%s %s: %s' %
3131 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003132 r = p.stdout
3133 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3134 return r[:-1]
3135 return r
3136 return runner
3137
3138
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003139class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003140
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003141 def __str__(self):
3142 return 'prior sync failed; rebase still in progress'
3143
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003144
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003145class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003146
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003147 def __str__(self):
3148 return 'contains uncommitted changes'
3149
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003150
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003151class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003152
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003153 def __init__(self, project, text):
3154 self.project = project
3155 self.text = text
3156
3157 def Print(self, syncbuf):
3158 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3159 syncbuf.out.nl()
3160
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003161
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003162class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003163
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003164 def __init__(self, project, why):
3165 self.project = project
3166 self.why = why
3167
3168 def Print(self, syncbuf):
3169 syncbuf.out.fail('error: %s/: %s',
3170 self.project.relpath,
3171 str(self.why))
3172 syncbuf.out.nl()
3173
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003174
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003175class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003176
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003177 def __init__(self, project, action):
3178 self.project = project
3179 self.action = action
3180
3181 def Run(self, syncbuf):
3182 out = syncbuf.out
3183 out.project('project %s/', self.project.relpath)
3184 out.nl()
3185 try:
3186 self.action()
3187 out.nl()
3188 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003189 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003190 out.nl()
3191 return False
3192
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003193
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003194class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003195
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003196 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003197 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003198 self.project = self.printer('header', attr='bold')
3199 self.info = self.printer('info')
3200 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003201
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003202
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003203class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003204
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003205 def __init__(self, config, detach_head=False):
3206 self._messages = []
3207 self._failures = []
3208 self._later_queue1 = []
3209 self._later_queue2 = []
3210
3211 self.out = _SyncColoring(config)
3212 self.out.redirect(sys.stderr)
3213
3214 self.detach_head = detach_head
3215 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003216 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003217
3218 def info(self, project, fmt, *args):
3219 self._messages.append(_InfoMessage(project, fmt % args))
3220
3221 def fail(self, project, err=None):
3222 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003223 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003224
3225 def later1(self, project, what):
3226 self._later_queue1.append(_Later(project, what))
3227
3228 def later2(self, project, what):
3229 self._later_queue2.append(_Later(project, what))
3230
3231 def Finish(self):
3232 self._PrintMessages()
3233 self._RunLater()
3234 self._PrintMessages()
3235 return self.clean
3236
David Rileye0684ad2017-04-05 00:02:59 -07003237 def Recently(self):
3238 recent_clean = self.recent_clean
3239 self.recent_clean = True
3240 return recent_clean
3241
3242 def _MarkUnclean(self):
3243 self.clean = False
3244 self.recent_clean = False
3245
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003246 def _RunLater(self):
3247 for q in ['_later_queue1', '_later_queue2']:
3248 if not self._RunQueue(q):
3249 return
3250
3251 def _RunQueue(self, queue):
3252 for m in getattr(self, queue):
3253 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003254 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003255 return False
3256 setattr(self, queue, [])
3257 return True
3258
3259 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003260 if self._messages or self._failures:
3261 if os.isatty(2):
3262 self.out.write(progress.CSI_ERASE_LINE)
3263 self.out.write('\r')
3264
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003265 for m in self._messages:
3266 m.Print(self)
3267 for m in self._failures:
3268 m.Print(self)
3269
3270 self._messages = []
3271 self._failures = []
3272
3273
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003274class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003275
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003276 """A special project housed under .repo.
3277 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003278
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003279 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003280 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003281 manifest=manifest,
3282 name=name,
3283 gitdir=gitdir,
3284 objdir=gitdir,
3285 worktree=worktree,
3286 remote=RemoteSpec('origin'),
3287 relpath='.repo/%s' % name,
3288 revisionExpr='refs/heads/master',
3289 revisionId=None,
3290 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003291
3292 def PreSync(self):
3293 if self.Exists:
3294 cb = self.CurrentBranch
3295 if cb:
3296 base = self.GetBranch(cb).merge
3297 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003298 self.revisionExpr = base
3299 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003300
Martin Kelly224a31a2017-07-10 14:46:25 -07003301 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003302 """ Prepare MetaProject for manifest branch switch
3303 """
3304
3305 # detach and delete manifest branch, allowing a new
3306 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003307 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003308 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003309 syncbuf.Finish()
3310
3311 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003312 ['update-ref', '-d', 'refs/heads/default'],
3313 capture_stdout=True,
3314 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003315
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003316 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003317 def LastFetch(self):
3318 try:
3319 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3320 return os.path.getmtime(fh)
3321 except OSError:
3322 return 0
3323
3324 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003325 def HasChanges(self):
3326 """Has the remote received new commits not yet checked out?
3327 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003328 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003329 return False
3330
David Pursehouse8a68ff92012-09-24 12:15:13 +09003331 all_refs = self.bare_ref.all
3332 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003333 head = self.work_git.GetHead()
3334 if head.startswith(R_HEADS):
3335 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003336 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003337 except KeyError:
3338 head = None
3339
3340 if revid == head:
3341 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003342 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003343 return True
3344 return False