blob: ed58c956b38b2b75bec88dde293de5ab4f84708e [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
LaMont Jonescc879a92021-11-18 22:40:18 +0000549 def RelPath(self, local=True):
550 """Return the path for the project relative to a manifest.
551
552 Args:
553 local: a boolean, if True, the path is relative to the local
554 (sub)manifest. If false, the path is relative to the
555 outermost manifest.
556 """
557 if local:
558 return self.relpath
559 return os.path.join(self.manifest.path_prefix, self.relpath)
560
Michael Kelly2f3c3312020-07-21 19:40:38 -0700561 def SetRevision(self, revisionExpr, revisionId=None):
562 """Set revisionId based on revision expression and id"""
563 self.revisionExpr = revisionExpr
564 if revisionId is None and revisionExpr and IsId(revisionExpr):
565 self.revisionId = self.revisionExpr
566 else:
567 self.revisionId = revisionId
568
Michael Kelly37c21c22020-06-13 02:10:40 -0700569 def UpdatePaths(self, relpath, worktree, gitdir, objdir):
570 """Update paths used by this project"""
571 self.gitdir = gitdir.replace('\\', '/')
572 self.objdir = objdir.replace('\\', '/')
573 if worktree:
574 self.worktree = os.path.normpath(worktree).replace('\\', '/')
575 else:
576 self.worktree = None
577 self.relpath = relpath
578
579 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
580 defaults=self.manifest.globalConfig)
581
582 if self.worktree:
583 self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
584 else:
585 self.work_git = None
586 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
587 self.bare_ref = GitRefs(self.gitdir)
588 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
589
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700590 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800591 def Derived(self):
592 return self.is_derived
593
594 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700595 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700596 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700597
598 @property
599 def CurrentBranch(self):
600 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400601
602 The branch name omits the 'refs/heads/' prefix.
603 None is returned if the project is on a detached HEAD, or if the work_git is
604 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700605 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400606 try:
607 b = self.work_git.GetHead()
608 except NoManifestException:
609 # If the local checkout is in a bad state, don't barf. Let the callers
610 # process this like the head is unreadable.
611 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700612 if b.startswith(R_HEADS):
613 return b[len(R_HEADS):]
614 return None
615
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700616 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500617 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
618 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
619 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200620
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700621 def IsDirty(self, consider_untracked=True):
622 """Is the working directory modified in some way?
623 """
624 self.work_git.update_index('-q',
625 '--unmerged',
626 '--ignore-missing',
627 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900628 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700629 return True
630 if self.work_git.DiffZ('diff-files'):
631 return True
632 if consider_untracked and self.work_git.LsOthers():
633 return True
634 return False
635
636 _userident_name = None
637 _userident_email = None
638
639 @property
640 def UserName(self):
641 """Obtain the user's personal name.
642 """
643 if self._userident_name is None:
644 self._LoadUserIdentity()
645 return self._userident_name
646
647 @property
648 def UserEmail(self):
649 """Obtain the user's email address. This is very likely
650 to be their Gerrit login.
651 """
652 if self._userident_email is None:
653 self._LoadUserIdentity()
654 return self._userident_email
655
656 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900657 u = self.bare_git.var('GIT_COMMITTER_IDENT')
658 m = re.compile("^(.*) <([^>]*)> ").match(u)
659 if m:
660 self._userident_name = m.group(1)
661 self._userident_email = m.group(2)
662 else:
663 self._userident_name = ''
664 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700665
666 def GetRemote(self, name):
667 """Get the configuration for a single remote.
668 """
669 return self.config.GetRemote(name)
670
671 def GetBranch(self, name):
672 """Get the configuration for a single branch.
673 """
674 return self.config.GetBranch(name)
675
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700676 def GetBranches(self):
677 """Get all existing local branches.
678 """
679 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900680 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700681 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700682
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530683 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700684 if name.startswith(R_HEADS):
685 name = name[len(R_HEADS):]
686 b = self.GetBranch(name)
687 b.current = name == current
688 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900689 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700690 heads[name] = b
691
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530692 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700693 if name.startswith(R_PUB):
694 name = name[len(R_PUB):]
695 b = heads.get(name)
696 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900697 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700698
699 return heads
700
Colin Cross5acde752012-03-28 20:15:45 -0700701 def MatchesGroups(self, manifest_groups):
702 """Returns true if the manifest groups specified at init should cause
703 this project to be synced.
704 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700705 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700706
707 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700708 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700709 manifest_groups: "-group1,group2"
710 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500711
712 The special manifest group "default" will match any project that
713 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700714 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500715 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700716 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700717 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500718 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700719
Conley Owens971de8e2012-04-16 10:36:08 -0700720 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700721 for group in expanded_manifest_groups:
722 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700723 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700724 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700725 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700726
Conley Owens971de8e2012-04-16 10:36:08 -0700727 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700729# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700730 def UncommitedFiles(self, get_all=True):
731 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700733 Args:
734 get_all: a boolean, if True - get information about all different
735 uncommitted files. If False - return as soon as any kind of
736 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500737 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700738 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500739 self.work_git.update_index('-q',
740 '--unmerged',
741 '--ignore-missing',
742 '--refresh')
743 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700744 details.append("rebase in progress")
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.DiffZ('diff-index', '--cached', HEAD).keys()
749 if changes:
750 details.extend(changes)
751 if not get_all:
752 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500753
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700754 changes = self.work_git.DiffZ('diff-files').keys()
755 if changes:
756 details.extend(changes)
757 if not get_all:
758 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500759
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700760 changes = self.work_git.LsOthers()
761 if changes:
762 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500763
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700764 return details
765
766 def HasChanges(self):
767 """Returns true if there are uncommitted changes.
768 """
769 if self.UncommitedFiles(get_all=False):
770 return True
771 else:
772 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500773
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600774 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200776
777 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200778 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600779 quiet: If True then only print the project name. Do not print
780 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700781 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700782 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700783 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200784 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700785 print(file=output_redir)
786 print('project %s/' % self.relpath, file=output_redir)
787 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788 return
789
790 self.work_git.update_index('-q',
791 '--unmerged',
792 '--ignore-missing',
793 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700794 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700795 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
796 df = self.work_git.DiffZ('diff-files')
797 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100798 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700799 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700800
801 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700802 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200803 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700804 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700805
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600806 if quiet:
807 out.nl()
808 return 'DIRTY'
809
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810 branch = self.CurrentBranch
811 if branch is None:
812 out.nobranch('(*** NO BRANCH ***)')
813 else:
814 out.branch('branch %s', branch)
815 out.nl()
816
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700817 if rb:
818 out.important('prior sync failed; rebase still in progress')
819 out.nl()
820
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700821 paths = list()
822 paths.extend(di.keys())
823 paths.extend(df.keys())
824 paths.extend(do)
825
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530826 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900827 try:
828 i = di[p]
829 except KeyError:
830 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700831
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900832 try:
833 f = df[p]
834 except KeyError:
835 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200836
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900837 if i:
838 i_status = i.status.upper()
839 else:
840 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900842 if f:
843 f_status = f.status.lower()
844 else:
845 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846
847 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800848 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700849 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700850 else:
851 line = ' %s%s\t%s' % (i_status, f_status, p)
852
853 if i and not f:
854 out.added('%s', line)
855 elif (i and f) or (not i and f):
856 out.changed('%s', line)
857 elif not i and not f:
858 out.untracked('%s', line)
859 else:
860 out.write('%s', line)
861 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200862
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700863 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700864
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500865 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700866 """Prints the status of the repository to stdout.
867 """
868 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500869 if output_redir:
870 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871 cmd = ['diff']
872 if out.is_on:
873 cmd.append('--color')
874 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300875 if absolute_paths:
876 cmd.append('--src-prefix=a/%s/' % self.relpath)
877 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400879 try:
880 p = GitCommand(self,
881 cmd,
882 capture_stdout=True,
883 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500884 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400885 except GitError as e:
886 out.nl()
887 out.project('project %s/' % self.relpath)
888 out.nl()
889 out.fail('%s', str(e))
890 out.nl()
891 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500892 if p.stdout:
893 out.nl()
894 out.project('project %s/' % self.relpath)
895 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500896 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400897 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700899# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900900 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901 """Was the branch published (uploaded) for code review?
902 If so, returns the SHA-1 hash of the last published
903 state for the branch.
904 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700905 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900906 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700907 try:
908 return self.bare_git.rev_parse(key)
909 except GitError:
910 return None
911 else:
912 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900913 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700914 except KeyError:
915 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916
David Pursehouse8a68ff92012-09-24 12:15:13 +0900917 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918 """Prunes any stale published refs.
919 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900920 if all_refs is None:
921 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 heads = set()
923 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530924 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925 if name.startswith(R_HEADS):
926 heads.add(name)
927 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900928 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700929
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530930 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931 n = name[len(R_PUB):]
932 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900933 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700935 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936 """List any branches which can be uploaded for review.
937 """
938 heads = {}
939 pubed = {}
940
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530941 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900943 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900945 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946
947 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530948 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900949 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700951 if selected_branch and branch != selected_branch:
952 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800954 rb = self.GetUploadableBranch(branch)
955 if rb:
956 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957 return ready
958
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800959 def GetUploadableBranch(self, branch_name):
960 """Get a single uploadable branch, or None.
961 """
962 branch = self.GetBranch(branch_name)
963 base = branch.LocalMerge
964 if branch.LocalMerge:
965 rb = ReviewableBranch(self, branch, base)
966 if rb.commits:
967 return rb
968 return None
969
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700970 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100971 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500972 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700973 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500974 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500975 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200976 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700977 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200978 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200979 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800980 validate_certs=True,
981 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982 """Uploads the named branch for code review.
983 """
984 if branch is None:
985 branch = self.CurrentBranch
986 if branch is None:
987 raise GitError('not currently on a branch')
988
989 branch = self.GetBranch(branch)
990 if not branch.LocalMerge:
991 raise GitError('branch %s does not track a remote' % branch.name)
992 if not branch.remote.review:
993 raise GitError('remote %s has no review url' % branch.remote.name)
994
Bryan Jacobsf609f912013-05-06 13:36:24 -0400995 if dest_branch is None:
996 dest_branch = self.dest_branch
997 if dest_branch is None:
998 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700999 if not dest_branch.startswith(R_HEADS):
1000 dest_branch = R_HEADS + dest_branch
1001
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001002 if not branch.remote.projectname:
1003 branch.remote.projectname = self.name
1004 branch.remote.Save()
1005
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001006 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001007 if url is None:
1008 raise UploadError('review not configured')
1009 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001010 if dryrun:
1011 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001012
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001013 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001014 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001015
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001016 for push_option in (push_options or []):
1017 cmd.append('-o')
1018 cmd.append(push_option)
1019
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001020 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001021
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001022 if dest_branch.startswith(R_HEADS):
1023 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001024
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001025 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001026 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001027 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001028 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001029 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001030 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001031
David Pursehousef25a3702018-11-14 19:01:22 -08001032 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001033 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001034 if notify:
1035 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001036 if private:
1037 opts += ['private']
1038 if wip:
1039 opts += ['wip']
1040 if opts:
1041 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001042 cmd.append(ref_spec)
1043
Anthony King7bdac712014-07-16 12:56:40 +01001044 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001045 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001046
Mike Frysingerd7f86832020-11-19 19:18:46 -05001047 if not dryrun:
1048 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1049 self.bare_git.UpdateRef(R_PUB + branch.name,
1050 R_HEADS + branch.name,
1051 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001052
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001053# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001054 def _ExtractArchive(self, tarpath, path=None):
1055 """Extract the given tar on its current location
1056
1057 Args:
1058 - tarpath: The path to the actual tar file
1059
1060 """
1061 try:
1062 with tarfile.open(tarpath, 'r') as tar:
1063 tar.extractall(path=path)
1064 return True
1065 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001066 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001067 return False
1068
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001069 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001070 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001071 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001072 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001073 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001074 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001075 force_sync=False,
1076 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001077 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001078 archive=False,
1079 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001080 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001081 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001082 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001083 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001084 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001085 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086 """Perform only the network IO portion of the sync process.
1087 Local working directory/branch state is not affected.
1088 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001089 if archive and not isinstance(self, MetaProject):
1090 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001091 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001092 return False
1093
1094 name = self.relpath.replace('\\', '/')
1095 name = name.replace('/', '_')
1096 tarpath = '%s.tar' % name
1097 topdir = self.manifest.topdir
1098
1099 try:
1100 self._FetchArchive(tarpath, cwd=topdir)
1101 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001102 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001103 return False
1104
1105 # From now on, we only need absolute tarpath
1106 tarpath = os.path.join(topdir, tarpath)
1107
1108 if not self._ExtractArchive(tarpath, path=topdir):
1109 return False
1110 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001111 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001112 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001113 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001114 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001115 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001116
1117 # If the shared object dir already exists, don't try to rebootstrap with a
1118 # clone bundle download. We should have the majority of objects already.
1119 if clone_bundle and os.path.exists(self.objdir):
1120 clone_bundle = False
1121
Raman Tennetif32f2432021-04-12 20:57:25 -07001122 if self.name in partial_clone_exclude:
1123 clone_bundle = True
1124 clone_filter = None
1125
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001126 if is_new is None:
1127 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001128 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001129 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001130 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001131 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001132 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001133
1134 if is_new:
Mike Frysinger152032c2021-12-20 21:17:43 -05001135 alt = os.path.join(self.objdir, 'objects/info/alternates')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001136 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001137 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001138 # This works for both absolute and relative alternate directories.
1139 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001140 except IOError:
1141 alt_dir = None
1142 else:
1143 alt_dir = None
1144
Mike Frysingere50b6a72020-02-19 01:45:48 -05001145 if (clone_bundle
1146 and alt_dir is None
1147 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001148 is_new = False
1149
Mike Frysinger73561142021-05-03 01:10:09 -04001150 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001151 if self.sync_c:
1152 current_branch_only = True
1153 elif not self.manifest._loaded:
1154 # Manifest cannot check defaults until it syncs.
1155 current_branch_only = False
1156 elif self.manifest.default.sync_c:
1157 current_branch_only = True
1158
Mike Frysingerd68ed632021-05-03 01:21:35 -04001159 if tags is None:
1160 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001161
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001162 if self.clone_depth:
1163 depth = self.clone_depth
1164 else:
1165 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1166
Mike Frysinger521d01b2020-02-17 01:51:49 -05001167 # See if we can skip the network fetch entirely.
1168 if not (optimized_fetch and
1169 (ID_RE.match(self.revisionExpr) and
1170 self._CheckForImmutableRevision())):
1171 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001172 initial=is_new,
1173 quiet=quiet, verbose=verbose, output_redir=output_redir,
1174 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001175 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001176 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001177 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001178 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001179 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001180
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001181 mp = self.manifest.manifestProject
1182 dissociate = mp.config.GetBoolean('repo.dissociate')
1183 if dissociate:
Mike Frysinger152032c2021-12-20 21:17:43 -05001184 alternates_file = os.path.join(self.objdir, 'objects/info/alternates')
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001185 if os.path.exists(alternates_file):
1186 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001187 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1188 merge_output=bool(output_redir))
1189 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001190 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001191 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001192 return False
1193 platform_utils.remove(alternates_file)
1194
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001195 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001196 self._InitMRef()
1197 else:
1198 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001199 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1200 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001202
1203 def PostRepoUpgrade(self):
1204 self._InitHooks()
1205
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001206 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001207 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001208 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001209 for copyfile in self.copyfiles:
1210 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001211 for linkfile in self.linkfiles:
1212 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213
Julien Camperguedd654222014-01-09 16:21:37 +01001214 def GetCommitRevisionId(self):
1215 """Get revisionId of a commit.
1216
1217 Use this method instead of GetRevisionId to get the id of the commit rather
1218 than the id of the current git object (for example, a tag)
1219
1220 """
1221 if not self.revisionExpr.startswith(R_TAGS):
1222 return self.GetRevisionId(self._allrefs)
1223
1224 try:
1225 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1226 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001227 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1228 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001229
David Pursehouse8a68ff92012-09-24 12:15:13 +09001230 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001231 if self.revisionId:
1232 return self.revisionId
1233
1234 rem = self.GetRemote(self.remote.name)
1235 rev = rem.ToLocal(self.revisionExpr)
1236
David Pursehouse8a68ff92012-09-24 12:15:13 +09001237 if all_refs is not None and rev in all_refs:
1238 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001239
1240 try:
1241 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1242 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001243 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1244 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001245
Raman Tenneti6a872c92021-01-14 19:17:50 -08001246 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001247 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001248 self.upstream = self.revisionExpr
1249
Raman Tenneti6a872c92021-01-14 19:17:50 -08001250 self.revisionId = revisionId
1251
Martin Kellye4e94d22017-03-21 16:05:12 -07001252 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001253 """Perform only the local IO portion of the sync process.
1254 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001255 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001256 if not os.path.exists(self.gitdir):
1257 syncbuf.fail(self,
1258 'Cannot checkout %s due to missing network sync; Run '
1259 '`repo sync -n %s` first.' %
1260 (self.name, self.name))
1261 return
1262
Martin Kellye4e94d22017-03-21 16:05:12 -07001263 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001264 all_refs = self.bare_ref.all
1265 self.CleanPublishedCache(all_refs)
1266 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001267
Mike Frysinger0458faa2021-03-10 23:35:44 -05001268 # Special case the root of the repo client checkout. Make sure it doesn't
1269 # contain files being checked out to dirs we don't allow.
1270 if self.relpath == '.':
1271 PROTECTED_PATHS = {'.repo'}
1272 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1273 bad_paths = paths & PROTECTED_PATHS
1274 if bad_paths:
1275 syncbuf.fail(self,
1276 'Refusing to checkout project that writes to protected '
1277 'paths: %s' % (', '.join(bad_paths),))
1278 return
1279
David Pursehouse1d947b32012-10-25 12:23:11 +09001280 def _doff():
1281 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001282 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001283
Martin Kellye4e94d22017-03-21 16:05:12 -07001284 def _dosubmodules():
1285 self._SyncSubmodules(quiet=True)
1286
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001287 head = self.work_git.GetHead()
1288 if head.startswith(R_HEADS):
1289 branch = head[len(R_HEADS):]
1290 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001291 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001292 except KeyError:
1293 head = None
1294 else:
1295 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001297 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298 # Currently on a detached HEAD. The user is assumed to
1299 # not have any local modifications worth worrying about.
1300 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001301 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001302 syncbuf.fail(self, _PriorSyncFailedError())
1303 return
1304
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001305 if head == revid:
1306 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001307 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001308 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001309 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001310 # The copy/linkfile config may have changed.
1311 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001312 return
1313 else:
1314 lost = self._revlist(not_rev(revid), HEAD)
1315 if lost:
1316 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001317
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001319 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001320 if submodules:
1321 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001322 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001323 syncbuf.fail(self, e)
1324 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001325 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001326 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001328 if head == revid:
1329 # No changes; don't do anything further.
1330 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001331 # The copy/linkfile config may have changed.
1332 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001333 return
1334
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001335 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001336
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001337 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001338 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001339 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001340 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001341 syncbuf.info(self,
1342 "leaving %s; does not track upstream",
1343 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001345 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001346 if submodules:
1347 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001348 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001349 syncbuf.fail(self, e)
1350 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001351 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001352 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001353
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001354 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001355
1356 # See if we can perform a fast forward merge. This can happen if our
1357 # branch isn't in the exact same state as we last published.
1358 try:
1359 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1360 # Skip the published logic.
1361 pub = False
1362 except GitError:
1363 pub = self.WasPublished(branch.name, all_refs)
1364
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001366 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367 if not_merged:
1368 if upstream_gain:
1369 # The user has published this branch and some of those
1370 # commits are not yet merged upstream. We do not want
1371 # to rewrite the published commits so we punt.
1372 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001373 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001374 "branch %s is published (but not merged) and is now "
1375 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001376 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001377 elif pub == head:
1378 # All published commits are merged, and thus we are a
1379 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001380 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001381 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001382 if submodules:
1383 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001384 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001386 # Examine the local commits not in the remote. Find the
1387 # last one attributed to this user, if any.
1388 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001389 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001390 last_mine = None
1391 cnt_mine = 0
1392 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001393 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001394 if committer_email == self.UserEmail:
1395 last_mine = commit_id
1396 cnt_mine += 1
1397
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001398 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001399 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001400
1401 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001402 syncbuf.fail(self, _DirtyError())
1403 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001404
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001405 # If the upstream switched on us, warn the user.
1406 #
1407 if branch.merge != self.revisionExpr:
1408 if branch.merge and self.revisionExpr:
1409 syncbuf.info(self,
1410 'manifest switched %s...%s',
1411 branch.merge,
1412 self.revisionExpr)
1413 elif branch.merge:
1414 syncbuf.info(self,
1415 'manifest no longer tracks %s',
1416 branch.merge)
1417
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001418 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001419 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001420 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001421 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001422 syncbuf.info(self,
1423 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001424 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001425
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001426 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001427 if not ID_RE.match(self.revisionExpr):
1428 # in case of manifest sync the revisionExpr might be a SHA1
1429 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001430 if not branch.merge.startswith('refs/'):
1431 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001432 branch.Save()
1433
Mike Pontillod3153822012-02-28 11:53:24 -08001434 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001435 def _docopyandlink():
1436 self._CopyAndLinkFiles()
1437
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001438 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001439 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001440 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001441 if submodules:
1442 syncbuf.later2(self, _dosubmodules)
1443 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001444 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001445 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001446 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001447 if submodules:
1448 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001449 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001450 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001451 syncbuf.fail(self, e)
1452 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001453 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001454 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001455 if submodules:
1456 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001457
Mike Frysingere6a202f2019-08-02 15:57:57 -04001458 def AddCopyFile(self, src, dest, topdir):
1459 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001460
Mike Frysingere6a202f2019-08-02 15:57:57 -04001461 No filesystem changes occur here. Actual copying happens later on.
1462
1463 Paths should have basic validation run on them before being queued.
1464 Further checking will be handled when the actual copy happens.
1465 """
1466 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1467
1468 def AddLinkFile(self, src, dest, topdir):
1469 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1470
1471 No filesystem changes occur here. Actual linking happens later on.
1472
1473 Paths should have basic validation run on them before being queued.
1474 Further checking will be handled when the actual link happens.
1475 """
1476 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001477
James W. Mills24c13082012-04-12 15:04:13 -05001478 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001479 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001480
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001481 def DownloadPatchSet(self, change_id, patch_id):
1482 """Download a single patch set of a single change to FETCH_HEAD.
1483 """
1484 remote = self.GetRemote(self.remote.name)
1485
1486 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001487 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001488 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001489 if GitCommand(self, cmd, bare=True).Wait() != 0:
1490 return None
1491 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001492 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001493 change_id,
1494 patch_id,
1495 self.bare_git.rev_parse('FETCH_HEAD'))
1496
Mike Frysingerc0d18662020-02-19 19:19:18 -05001497 def DeleteWorktree(self, quiet=False, force=False):
1498 """Delete the source checkout and any other housekeeping tasks.
1499
1500 This currently leaves behind the internal .repo/ cache state. This helps
1501 when switching branches or manifest changes get reverted as we don't have
1502 to redownload all the git objects. But we should do some GC at some point.
1503
1504 Args:
1505 quiet: Whether to hide normal messages.
1506 force: Always delete tree even if dirty.
1507
1508 Returns:
1509 True if the worktree was completely cleaned out.
1510 """
1511 if self.IsDirty():
1512 if force:
1513 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1514 (self.relpath,), file=sys.stderr)
1515 else:
1516 print('error: %s: Cannot remove project: uncommitted changes are '
1517 'present.\n' % (self.relpath,), file=sys.stderr)
1518 return False
1519
1520 if not quiet:
1521 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1522
1523 # Unlock and delink from the main worktree. We don't use git's worktree
1524 # remove because it will recursively delete projects -- we handle that
1525 # ourselves below. https://crbug.com/git/48
1526 if self.use_git_worktrees:
1527 needle = platform_utils.realpath(self.gitdir)
1528 # Find the git worktree commondir under .repo/worktrees/.
1529 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1530 assert output.startswith('worktree '), output
1531 commondir = output[9:]
1532 # Walk each of the git worktrees to see where they point.
1533 configs = os.path.join(commondir, 'worktrees')
1534 for name in os.listdir(configs):
1535 gitdir = os.path.join(configs, name, 'gitdir')
1536 with open(gitdir) as fp:
1537 relpath = fp.read().strip()
1538 # Resolve the checkout path and see if it matches this project.
1539 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1540 if fullpath == needle:
1541 platform_utils.rmtree(os.path.join(configs, name))
1542
1543 # Delete the .git directory first, so we're less likely to have a partially
1544 # working git repository around. There shouldn't be any git projects here,
1545 # so rmtree works.
1546
1547 # Try to remove plain files first in case of git worktrees. If this fails
1548 # for any reason, we'll fall back to rmtree, and that'll display errors if
1549 # it can't remove things either.
1550 try:
1551 platform_utils.remove(self.gitdir)
1552 except OSError:
1553 pass
1554 try:
1555 platform_utils.rmtree(self.gitdir)
1556 except OSError as e:
1557 if e.errno != errno.ENOENT:
1558 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1559 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1560 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1561 return False
1562
1563 # Delete everything under the worktree, except for directories that contain
1564 # another git project.
1565 dirs_to_remove = []
1566 failed = False
1567 for root, dirs, files in platform_utils.walk(self.worktree):
1568 for f in files:
1569 path = os.path.join(root, f)
1570 try:
1571 platform_utils.remove(path)
1572 except OSError as e:
1573 if e.errno != errno.ENOENT:
1574 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1575 failed = True
1576 dirs[:] = [d for d in dirs
1577 if not os.path.lexists(os.path.join(root, d, '.git'))]
1578 dirs_to_remove += [os.path.join(root, d) for d in dirs
1579 if os.path.join(root, d) not in dirs_to_remove]
1580 for d in reversed(dirs_to_remove):
1581 if platform_utils.islink(d):
1582 try:
1583 platform_utils.remove(d)
1584 except OSError as e:
1585 if e.errno != errno.ENOENT:
1586 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1587 failed = True
1588 elif not platform_utils.listdir(d):
1589 try:
1590 platform_utils.rmdir(d)
1591 except OSError as e:
1592 if e.errno != errno.ENOENT:
1593 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1594 failed = True
1595 if failed:
1596 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1597 file=sys.stderr)
1598 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1599 return False
1600
1601 # Try deleting parent dirs if they are empty.
1602 path = self.worktree
1603 while path != self.manifest.topdir:
1604 try:
1605 platform_utils.rmdir(path)
1606 except OSError as e:
1607 if e.errno != errno.ENOENT:
1608 break
1609 path = os.path.dirname(path)
1610
1611 return True
1612
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001613# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001614 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001615 """Create a new branch off the manifest's revision.
1616 """
Simran Basib9a1b732015-08-20 12:19:28 -07001617 if not branch_merge:
1618 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001619 head = self.work_git.GetHead()
1620 if head == (R_HEADS + name):
1621 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001622
David Pursehouse8a68ff92012-09-24 12:15:13 +09001623 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001624 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001625 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001626 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001627 capture_stdout=True,
1628 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001629
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001630 branch = self.GetBranch(name)
1631 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001632 branch.merge = branch_merge
1633 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1634 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001635
1636 if revision is None:
1637 revid = self.GetRevisionId(all_refs)
1638 else:
1639 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001640
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001641 if head.startswith(R_HEADS):
1642 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001643 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001644 except KeyError:
1645 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001646 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001647 ref = R_HEADS + name
1648 self.work_git.update_ref(ref, revid)
1649 self.work_git.symbolic_ref(HEAD, ref)
1650 branch.Save()
1651 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001652
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001653 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001654 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001655 capture_stdout=True,
1656 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001657 branch.Save()
1658 return True
1659 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001660
Wink Saville02d79452009-04-10 13:01:24 -07001661 def CheckoutBranch(self, name):
1662 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001663
1664 Args:
1665 name: The name of the branch to checkout.
1666
1667 Returns:
1668 True if the checkout succeeded; False if it didn't; None if the branch
1669 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001670 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001671 rev = R_HEADS + name
1672 head = self.work_git.GetHead()
1673 if head == rev:
1674 # Already on the branch
1675 #
1676 return True
Wink Saville02d79452009-04-10 13:01:24 -07001677
David Pursehouse8a68ff92012-09-24 12:15:13 +09001678 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001679 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001680 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001681 except KeyError:
1682 # Branch does not exist in this project
1683 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001684 return None
Wink Saville02d79452009-04-10 13:01:24 -07001685
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001686 if head.startswith(R_HEADS):
1687 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001688 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001689 except KeyError:
1690 head = None
1691
1692 if head == revid:
1693 # Same revision; just update HEAD to point to the new
1694 # target branch, but otherwise take no other action.
1695 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001696 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1697 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001698 return True
1699
1700 return GitCommand(self,
1701 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001702 capture_stdout=True,
1703 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001704
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001705 def AbandonBranch(self, name):
1706 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001707
1708 Args:
1709 name: The name of the branch to abandon.
1710
1711 Returns:
1712 True if the abandon succeeded; False if it didn't; None if the branch
1713 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001714 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001715 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001716 all_refs = self.bare_ref.all
1717 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001718 # Doesn't exist
1719 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001720
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001721 head = self.work_git.GetHead()
1722 if head == rev:
1723 # We can't destroy the branch while we are sitting
1724 # on it. Switch to a detached HEAD.
1725 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001726 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001727
David Pursehouse8a68ff92012-09-24 12:15:13 +09001728 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001729 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001730 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001731 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001732 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001733
1734 return GitCommand(self,
1735 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001736 capture_stdout=True,
1737 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001738
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001739 def PruneHeads(self):
1740 """Prune any topic branches already merged into upstream.
1741 """
1742 cb = self.CurrentBranch
1743 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001744 left = self._allrefs
1745 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001746 if name.startswith(R_HEADS):
1747 name = name[len(R_HEADS):]
1748 if cb is None or name != cb:
1749 kill.append(name)
1750
Mike Frysingera3794e92021-03-11 23:24:01 -05001751 # Minor optimization: If there's nothing to prune, then don't try to read
1752 # any project state.
1753 if not kill and not cb:
1754 return []
1755
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001756 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001757 if cb is not None \
1758 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001759 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001760 self.work_git.DetachHead(HEAD)
1761 kill.append(cb)
1762
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001763 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001764 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001765
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766 try:
1767 self.bare_git.DetachHead(rev)
1768
1769 b = ['branch', '-d']
1770 b.extend(kill)
1771 b = GitCommand(self, b, bare=True,
1772 capture_stdout=True,
1773 capture_stderr=True)
1774 b.Wait()
1775 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001776 if ID_RE.match(old):
1777 self.bare_git.DetachHead(old)
1778 else:
1779 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001780 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001781
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001782 for branch in kill:
1783 if (R_HEADS + branch) not in left:
1784 self.CleanPublishedCache()
1785 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001786
1787 if cb and cb not in kill:
1788 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001789 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001790
1791 kept = []
1792 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001793 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001794 branch = self.GetBranch(branch)
1795 base = branch.LocalMerge
1796 if not base:
1797 base = rev
1798 kept.append(ReviewableBranch(self, branch, base))
1799 return kept
1800
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001801# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001802 def GetRegisteredSubprojects(self):
1803 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001804
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001805 def rec(subprojects):
1806 if not subprojects:
1807 return
1808 result.extend(subprojects)
1809 for p in subprojects:
1810 rec(p.subprojects)
1811 rec(self.subprojects)
1812 return result
1813
1814 def _GetSubmodules(self):
1815 # Unfortunately we cannot call `git submodule status --recursive` here
1816 # because the working tree might not exist yet, and it cannot be used
1817 # without a working tree in its current implementation.
1818
1819 def get_submodules(gitdir, rev):
1820 # Parse .gitmodules for submodule sub_paths and sub_urls
1821 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1822 if not sub_paths:
1823 return []
1824 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1825 # revision of submodule repository
1826 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1827 submodules = []
1828 for sub_path, sub_url in zip(sub_paths, sub_urls):
1829 try:
1830 sub_rev = sub_revs[sub_path]
1831 except KeyError:
1832 # Ignore non-exist submodules
1833 continue
1834 submodules.append((sub_rev, sub_path, sub_url))
1835 return submodules
1836
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001837 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1838 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001839
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001840 def parse_gitmodules(gitdir, rev):
1841 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1842 try:
Anthony King7bdac712014-07-16 12:56:40 +01001843 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1844 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001845 except GitError:
1846 return [], []
1847 if p.Wait() != 0:
1848 return [], []
1849
1850 gitmodules_lines = []
1851 fd, temp_gitmodules_path = tempfile.mkstemp()
1852 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001853 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001854 os.close(fd)
1855 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001856 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1857 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001858 if p.Wait() != 0:
1859 return [], []
1860 gitmodules_lines = p.stdout.split('\n')
1861 except GitError:
1862 return [], []
1863 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001864 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001865
1866 names = set()
1867 paths = {}
1868 urls = {}
1869 for line in gitmodules_lines:
1870 if not line:
1871 continue
1872 m = re_path.match(line)
1873 if m:
1874 names.add(m.group(1))
1875 paths[m.group(1)] = m.group(2)
1876 continue
1877 m = re_url.match(line)
1878 if m:
1879 names.add(m.group(1))
1880 urls[m.group(1)] = m.group(2)
1881 continue
1882 names = sorted(names)
1883 return ([paths.get(name, '') for name in names],
1884 [urls.get(name, '') for name in names])
1885
1886 def git_ls_tree(gitdir, rev, paths):
1887 cmd = ['ls-tree', rev, '--']
1888 cmd.extend(paths)
1889 try:
Anthony King7bdac712014-07-16 12:56:40 +01001890 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1891 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001892 except GitError:
1893 return []
1894 if p.Wait() != 0:
1895 return []
1896 objects = {}
1897 for line in p.stdout.split('\n'):
1898 if not line.strip():
1899 continue
1900 object_rev, object_path = line.split()[2:4]
1901 objects[object_path] = object_rev
1902 return objects
1903
1904 try:
1905 rev = self.GetRevisionId()
1906 except GitError:
1907 return []
1908 return get_submodules(self.gitdir, rev)
1909
1910 def GetDerivedSubprojects(self):
1911 result = []
1912 if not self.Exists:
1913 # If git repo does not exist yet, querying its submodules will
1914 # mess up its states; so return here.
1915 return result
1916 for rev, path, url in self._GetSubmodules():
1917 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001918 relpath, worktree, gitdir, objdir = \
1919 self.manifest.GetSubprojectPaths(self, name, path)
1920 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001921 if project:
1922 result.extend(project.GetDerivedSubprojects())
1923 continue
David James8d201162013-10-11 17:03:19 -07001924
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001925 if url.startswith('..'):
1926 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001927 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001928 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001929 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001930 review=self.remote.review,
1931 revision=self.remote.revision)
1932 subproject = Project(manifest=self.manifest,
1933 name=name,
1934 remote=remote,
1935 gitdir=gitdir,
1936 objdir=objdir,
1937 worktree=worktree,
1938 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001939 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001940 revisionId=rev,
1941 rebase=self.rebase,
1942 groups=self.groups,
1943 sync_c=self.sync_c,
1944 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001945 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001946 parent=self,
1947 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001948 result.append(subproject)
1949 result.extend(subproject.GetDerivedSubprojects())
1950 return result
1951
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001952# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001953 def EnableRepositoryExtension(self, key, value='true', version=1):
1954 """Enable git repository extension |key| with |value|.
1955
1956 Args:
1957 key: The extension to enabled. Omit the "extensions." prefix.
1958 value: The value to use for the extension.
1959 version: The minimum git repository version needed.
1960 """
1961 # Make sure the git repo version is new enough already.
1962 found_version = self.config.GetInt('core.repositoryFormatVersion')
1963 if found_version is None:
1964 found_version = 0
1965 if found_version < version:
1966 self.config.SetString('core.repositoryFormatVersion', str(version))
1967
1968 # Enable the extension!
1969 self.config.SetString('extensions.%s' % (key,), value)
1970
Mike Frysinger50a81de2020-09-06 15:51:21 -04001971 def ResolveRemoteHead(self, name=None):
1972 """Find out what the default branch (HEAD) points to.
1973
1974 Normally this points to refs/heads/master, but projects are moving to main.
1975 Support whatever the server uses rather than hardcoding "master" ourselves.
1976 """
1977 if name is None:
1978 name = self.remote.name
1979
1980 # The output will look like (NB: tabs are separators):
1981 # ref: refs/heads/master HEAD
1982 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1983 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1984
1985 for line in output.splitlines():
1986 lhs, rhs = line.split('\t', 1)
1987 if rhs == 'HEAD' and lhs.startswith('ref:'):
1988 return lhs[4:].strip()
1989
1990 return None
1991
Zac Livingstone4332262017-06-16 08:56:09 -06001992 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001993 try:
1994 # if revision (sha or tag) is not present then following function
1995 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08001996 self.bare_git.rev_list('-1', '--missing=allow-any',
1997 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00001998 if self.upstream:
1999 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
2000 self.bare_git.rev_list('-1', '--missing=allow-any',
2001 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00002002 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05002003 return True
2004 except GitError:
2005 # There is no such persistent revision. We have to fetch it.
2006 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002007
Julien Campergue335f5ef2013-10-16 11:02:35 +02002008 def _FetchArchive(self, tarpath, cwd=None):
2009 cmd = ['archive', '-v', '-o', tarpath]
2010 cmd.append('--remote=%s' % self.remote.url)
2011 cmd.append('--prefix=%s/' % self.relpath)
2012 cmd.append(self.revisionExpr)
2013
2014 command = GitCommand(self, cmd, cwd=cwd,
2015 capture_stdout=True,
2016 capture_stderr=True)
2017
2018 if command.Wait() != 0:
2019 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2020
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002021 def _RemoteFetch(self, name=None,
2022 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002023 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002024 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002025 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002026 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002027 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002028 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002029 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002030 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002031 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002032 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002033 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002034 clone_filter=None,
2035 retry_fetches=2,
2036 retry_sleep_initial_sec=4.0,
2037 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002038 is_sha1 = False
2039 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002040 # The depth should not be used when fetching to a mirror because
2041 # it will result in a shallow repository that cannot be cloned or
2042 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002043 # The repo project should also never be synced with partial depth.
2044 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2045 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002046
Shawn Pearce69e04d82014-01-29 12:48:54 -08002047 if depth:
2048 current_branch_only = True
2049
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002050 if ID_RE.match(self.revisionExpr) is not None:
2051 is_sha1 = True
2052
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002053 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002054 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002055 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002056 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002057 elif self.upstream and self.upstream.startswith(R_TAGS):
2058 # This is a tag and its commit id should never change.
2059 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002060
2061 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002062 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002063 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002064 print('Skipped fetching project %s (already have persistent ref)'
2065 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002066 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002067 if is_sha1 and not depth:
2068 # When syncing a specific commit and --depth is not set:
2069 # * if upstream is explicitly specified and is not a sha1, fetch only
2070 # upstream as users expect only upstream to be fetch.
2071 # Note: The commit might not be in upstream in which case the sync
2072 # will fail.
2073 # * otherwise, fetch all branches to make sure we end up with the
2074 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002075 if self.upstream:
2076 current_branch_only = not ID_RE.match(self.upstream)
2077 else:
2078 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002079
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002080 if not name:
2081 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002082
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002083 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002084 if not remote.PreConnectFetch(ssh_proxy):
2085 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002086
Shawn O. Pearce88443382010-10-08 10:02:09 +02002087 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002088 if alt_dir and 'objects' == os.path.basename(alt_dir):
2089 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002090 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002091
David Pursehouse8a68ff92012-09-24 12:15:13 +09002092 all_refs = self.bare_ref.all
2093 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002094 tmp = set()
2095
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302096 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002097 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002098 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002099 all_refs[r] = ref_id
2100 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002101 continue
2102
David Pursehouse8a68ff92012-09-24 12:15:13 +09002103 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002104 continue
2105
David Pursehouse8a68ff92012-09-24 12:15:13 +09002106 r = 'refs/_alt/%s' % ref_id
2107 all_refs[r] = ref_id
2108 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002109 tmp.add(r)
2110
heping3d7bbc92017-04-12 19:51:47 +08002111 tmp_packed_lines = []
2112 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002113
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302114 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002115 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002116 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002117 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002118 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002119
heping3d7bbc92017-04-12 19:51:47 +08002120 tmp_packed = ''.join(tmp_packed_lines)
2121 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002122 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002123 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002124 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002125
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002126 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002127
Xin Li745be2e2019-06-03 11:24:30 -07002128 if clone_filter:
2129 git_require((2, 19, 0), fail=True, msg='partial clones')
2130 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002131 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002132
Conley Owensf97e8382015-01-21 11:12:46 -08002133 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002134 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002135 else:
2136 # If this repo has shallow objects, then we don't know which refs have
2137 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2138 # do this with projects that don't have shallow objects, since it is less
2139 # efficient.
2140 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2141 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002142
Mike Frysinger4847e052020-02-22 00:07:35 -05002143 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002144 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002145 if not quiet and sys.stdout.isatty():
2146 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002147 if not self.worktree:
2148 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002149 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002150
Mike Frysingere57f1142019-03-18 21:27:54 -04002151 if force_sync:
2152 cmd.append('--force')
2153
David Pursehouse74cfd272015-10-14 10:50:15 +09002154 if prune:
2155 cmd.append('--prune')
2156
LaMont Jonesadaa1d82022-02-10 17:34:36 +00002157 cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
Martin Kellye4e94d22017-03-21 16:05:12 -07002158
Kuang-che Wu6856f982019-11-25 12:37:55 +08002159 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002160 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002161 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002162 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002163 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002164 spec.append('tag')
2165 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002166
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302167 if self.manifest.IsMirror and not current_branch_only:
2168 branch = None
2169 else:
2170 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002171 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002172 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002173 # Shallow checkout of a specific commit, fetch from that commit and not
2174 # the heads only as the commit might be deeper in the history.
2175 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002176 if self.upstream:
2177 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002178 else:
2179 if is_sha1:
2180 branch = self.upstream
2181 if branch is not None and branch.strip():
2182 if not branch.startswith('refs/'):
2183 branch = R_HEADS + branch
2184 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2185
2186 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2187 # whole repo.
2188 if self.manifest.IsMirror and not spec:
2189 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2190
2191 # If using depth then we should not get all the tags since they may
2192 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002193 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002194 cmd.append('--no-tags')
2195 else:
2196 cmd.append('--tags')
2197 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2198
Conley Owens80b87fe2014-05-09 17:13:44 -07002199 cmd.extend(spec)
2200
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002201 # At least one retry minimum due to git remote prune.
2202 retry_fetches = max(retry_fetches, 2)
2203 retry_cur_sleep = retry_sleep_initial_sec
2204 ok = prune_tried = False
2205 for try_n in range(retry_fetches):
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002206 gitcmd = GitCommand(
2207 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects'),
2208 ssh_proxy=ssh_proxy,
2209 merge_output=True, capture_stdout=quiet or bool(output_redir))
Mike Frysinger7b586f22021-02-23 18:38:39 -05002210 if gitcmd.stdout and not quiet and output_redir:
2211 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002212 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002213 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002214 ok = True
2215 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002216
2217 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002218 elif (gitcmd.stdout and
2219 'error:' in gitcmd.stdout and
2220 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002221 # Fallthru to sleep+retry logic at the bottom.
2222 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002223
Mike Frysinger6823bc22021-04-15 02:06:28 -04002224 # Try to prune remote branches once in case there are conflicts.
2225 # For example, if the remote had refs/heads/upstream, but deleted that and
2226 # now has refs/heads/upstream/foo.
2227 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002228 'error:' in gitcmd.stdout and
2229 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002230 not prune_tried):
2231 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002232 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002233 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002234 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002235 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002236 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002237 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002238 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002239 continue
Brian Harring14a66742012-09-28 20:21:57 -07002240 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002241 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2242 # in sha1 mode, we just tried sync'ing from the upstream field; it
2243 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002244 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002245 elif ret < 0:
2246 # Git died with a signal, exit immediately
2247 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002248
2249 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002250 if not verbose and gitcmd.stdout:
2251 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002252 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002253 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2254 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002255 time.sleep(retry_cur_sleep)
2256 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2257 MAXIMUM_RETRY_SLEEP_SEC)
2258 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2259 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002260
2261 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002262 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002263 if old_packed != '':
2264 _lwrite(packed_refs, old_packed)
2265 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002266 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002267 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002268
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002269 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002270 # We just synced the upstream given branch; verify we
2271 # got what we wanted, else trigger a second run of all
2272 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002273 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002274 # Sync the current branch only with depth set to None.
2275 # We always pass depth=None down to avoid infinite recursion.
2276 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002277 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002278 current_branch_only=current_branch_only and depth,
2279 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002280 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002281
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002282 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002283
Mike Frysingere50b6a72020-02-19 01:45:48 -05002284 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002285 if initial and \
2286 (self.manifest.manifestProject.config.GetString('repo.depth') or
2287 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002288 return False
2289
2290 remote = self.GetRemote(self.remote.name)
2291 bundle_url = remote.url + '/clone.bundle'
2292 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002293 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2294 'persistent-http',
2295 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002296 return False
2297
2298 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2299 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2300
2301 exist_dst = os.path.exists(bundle_dst)
2302 exist_tmp = os.path.exists(bundle_tmp)
2303
2304 if not initial and not exist_dst and not exist_tmp:
2305 return False
2306
2307 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002308 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2309 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002310 if not exist_dst:
2311 return False
2312
2313 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002314 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002315 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002316 if not quiet and sys.stdout.isatty():
2317 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002318 if not self.worktree:
2319 cmd.append('--update-head-ok')
2320 cmd.append(bundle_dst)
2321 for f in remote.fetch:
2322 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002323 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002324
Mike Frysinger67d6cdf2021-12-23 17:36:09 -05002325 ok = GitCommand(
2326 self, cmd, bare=True, objdir=os.path.join(self.objdir, 'objects')).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002327 platform_utils.remove(bundle_dst, missing_ok=True)
2328 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002329 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002330
Mike Frysingere50b6a72020-02-19 01:45:48 -05002331 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002332 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002333
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002334 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002335 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002336 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002337 if os.path.exists(tmpPath):
2338 size = os.stat(tmpPath).st_size
2339 if size >= 1024:
2340 cmd += ['--continue-at', '%d' % (size,)]
2341 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002342 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002343 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002344 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002345 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002346 if proxy:
2347 cmd += ['--proxy', proxy]
2348 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2349 cmd += ['--proxy', os.environ['http_proxy']]
2350 if srcUrl.startswith('persistent-https'):
2351 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2352 elif srcUrl.startswith('persistent-http'):
2353 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002354 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002355
Dave Borowitz137d0132015-01-02 11:12:54 -08002356 if IsTrace():
2357 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002358 if verbose:
2359 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2360 stdout = None if verbose else subprocess.PIPE
2361 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002362 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002363 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002364 except OSError:
2365 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002366
Mike Frysingere50b6a72020-02-19 01:45:48 -05002367 (output, _) = proc.communicate()
2368 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002369
Dave Borowitz137d0132015-01-02 11:12:54 -08002370 if curlret == 22:
2371 # From curl man page:
2372 # 22: HTTP page not retrieved. The requested url was not found or
2373 # returned another error with the HTTP error code being 400 or above.
2374 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002375 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002376 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2377 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002378 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002379 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002380 elif curlret and not verbose and output:
2381 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002382
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002383 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002384 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002385 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002386 return True
2387 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002388 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002389 return False
2390 else:
2391 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002392
Kris Giesingc8d882a2014-12-23 13:02:32 -08002393 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002394 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002395 with open(path, 'rb') as f:
2396 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002397 return True
2398 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002399 if not quiet:
2400 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002401 return False
2402 except OSError:
2403 return False
2404
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002405 def _Checkout(self, rev, quiet=False):
2406 cmd = ['checkout']
2407 if quiet:
2408 cmd.append('-q')
2409 cmd.append(rev)
2410 cmd.append('--')
2411 if GitCommand(self, cmd).Wait() != 0:
2412 if self._allrefs:
2413 raise GitError('%s checkout %s ' % (self.name, rev))
2414
Mike Frysinger915fda12020-03-22 12:15:20 -04002415 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002416 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002417 if ffonly:
2418 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002419 if record_origin:
2420 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002421 cmd.append(rev)
2422 cmd.append('--')
2423 if GitCommand(self, cmd).Wait() != 0:
2424 if self._allrefs:
2425 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2426
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302427 def _LsRemote(self, refs):
2428 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302429 p = GitCommand(self, cmd, capture_stdout=True)
2430 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002431 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302432 return None
2433
Anthony King7bdac712014-07-16 12:56:40 +01002434 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002435 cmd = ['revert']
2436 cmd.append('--no-edit')
2437 cmd.append(rev)
2438 cmd.append('--')
2439 if GitCommand(self, cmd).Wait() != 0:
2440 if self._allrefs:
2441 raise GitError('%s revert %s ' % (self.name, rev))
2442
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002443 def _ResetHard(self, rev, quiet=True):
2444 cmd = ['reset', '--hard']
2445 if quiet:
2446 cmd.append('-q')
2447 cmd.append(rev)
2448 if GitCommand(self, cmd).Wait() != 0:
2449 raise GitError('%s reset --hard %s ' % (self.name, rev))
2450
Martin Kellye4e94d22017-03-21 16:05:12 -07002451 def _SyncSubmodules(self, quiet=True):
2452 cmd = ['submodule', 'update', '--init', '--recursive']
2453 if quiet:
2454 cmd.append('-q')
2455 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002456 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002457
Anthony King7bdac712014-07-16 12:56:40 +01002458 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002459 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002460 if onto is not None:
2461 cmd.extend(['--onto', onto])
2462 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002463 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002464 raise GitError('%s rebase %s ' % (self.name, upstream))
2465
Pierre Tardy3d125942012-05-04 12:18:12 +02002466 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002467 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002468 if ffonly:
2469 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002470 if GitCommand(self, cmd).Wait() != 0:
2471 raise GitError('%s merge %s ' % (self.name, head))
2472
David Pursehousee8ace262020-02-13 12:41:15 +09002473 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002474 init_git_dir = not os.path.exists(self.gitdir)
2475 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002476 try:
2477 # Initialize the bare repository, which contains all of the objects.
2478 if init_obj_dir:
2479 os.makedirs(self.objdir)
2480 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002481
Mike Frysinger333c0a42021-11-15 12:39:00 -05002482 self._UpdateHooks(quiet=quiet)
2483
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002484 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002485 # Enable per-worktree config file support if possible. This is more a
2486 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002487 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002488 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002489
Kevin Degib1a07b82015-07-27 13:33:43 -06002490 # If we have a separate directory to hold refs, initialize it as well.
2491 if self.objdir != self.gitdir:
2492 if init_git_dir:
2493 os.makedirs(self.gitdir)
2494
2495 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002496 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002497 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002498 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002499 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002500 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002501 print("Retrying clone after deleting %s" %
2502 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002503 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002504 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2505 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002506 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002507 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002508 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2509 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002510 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002511 raise e
2512 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002513
Kevin Degi384b3c52014-10-16 16:02:58 -06002514 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002515 mp = self.manifest.manifestProject
2516 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002517
LaMont Jonescc879a92021-11-18 22:40:18 +00002518 def _expanded_ref_dirs():
2519 """Iterate through the possible git reference directory paths."""
2520 name = self.name + '.git'
2521 yield mirror_git or os.path.join(ref_dir, name)
2522 for prefix in '', self.remote.name:
2523 yield os.path.join(ref_dir, '.repo', 'project-objects', prefix, name)
2524 yield os.path.join(ref_dir, '.repo', 'worktrees', prefix, name)
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002525
LaMont Jonescc879a92021-11-18 22:40:18 +00002526 if ref_dir or mirror_git:
2527 found_ref_dir = None
2528 for path in _expanded_ref_dirs():
2529 if os.path.exists(path):
2530 found_ref_dir = path
2531 break
2532 ref_dir = found_ref_dir
Shawn O. Pearce88443382010-10-08 10:02:09 +02002533
Kevin Degib1a07b82015-07-27 13:33:43 -06002534 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002535 if not os.path.isabs(ref_dir):
2536 # The alternate directory is relative to the object database.
2537 ref_dir = os.path.relpath(ref_dir,
2538 os.path.join(self.objdir, 'objects'))
Mike Frysinger152032c2021-12-20 21:17:43 -05002539 _lwrite(os.path.join(self.objdir, 'objects/info/alternates'),
Kevin Degib1a07b82015-07-27 13:33:43 -06002540 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002541
Kevin Degib1a07b82015-07-27 13:33:43 -06002542 m = self.manifest.manifestProject.config
2543 for key in ['user.name', 'user.email']:
2544 if m.Has(key, include_defaults=False):
2545 self.config.SetString(key, m.GetString(key))
XD Trol630876f2022-01-17 23:29:04 +08002546 if not self.manifest.EnableGitLfs:
2547 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2548 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002549 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002550 except Exception:
2551 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002552 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002553 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002554 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002555 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002556
David Pursehousee8ace262020-02-13 12:41:15 +09002557 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002558 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002559 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002560
David Pursehousee8ace262020-02-13 12:41:15 +09002561 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002562 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002563 if not os.path.exists(hooks):
2564 os.makedirs(hooks)
Mike Frysinger98bb7652021-12-20 21:15:59 -05002565
2566 # Delete sample hooks. They're noise.
2567 for hook in glob.glob(os.path.join(hooks, '*.sample')):
Peter Kjellerstedtb5505012022-01-21 23:09:19 +01002568 try:
2569 platform_utils.remove(hook, missing_ok=True)
2570 except PermissionError:
2571 pass
Mike Frysinger98bb7652021-12-20 21:15:59 -05002572
Jonathan Nieder93719792015-03-17 11:29:58 -07002573 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002574 name = os.path.basename(stock_hook)
2575
Victor Boivie65e0f352011-04-18 11:23:29 +02002576 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002577 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002578 # Don't install a Gerrit Code Review hook if this
2579 # project does not appear to use it for reviews.
2580 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002581 # Since the manifest project is one of those, but also
2582 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002583 continue
2584
2585 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002586 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002587 continue
2588 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002589 # If the files are the same, we'll leave it alone. We create symlinks
2590 # below by default but fallback to hardlinks if the OS blocks them.
2591 # So if we're here, it's probably because we made a hardlink below.
2592 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002593 if not quiet:
2594 _warn("%s: Not replacing locally modified %s hook",
2595 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002596 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002597 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002598 platform_utils.symlink(
2599 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002600 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002601 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002602 try:
2603 os.link(stock_hook, dst)
2604 except OSError:
2605 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002606 else:
2607 raise
2608
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002609 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002610 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002611 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002612 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002613 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002614 remote.review = self.remote.review
2615 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002616
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002617 if self.worktree:
2618 remote.ResetFetch(mirror=False)
2619 else:
2620 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002621 remote.Save()
2622
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002623 def _InitMRef(self):
2624 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002625 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002626 # Set up the m/ space to point to the worktree-specific ref space.
2627 # We'll update the worktree-specific ref space on each checkout.
2628 ref = R_M + self.manifest.branch
2629 if not self.bare_ref.symref(ref):
2630 self.bare_git.symbolic_ref(
2631 '-m', 'redirecting to worktree scope',
2632 ref, R_WORKTREE_M + self.manifest.branch)
2633
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002634 # We can't update this ref with git worktrees until it exists.
2635 # We'll wait until the initial checkout to set it.
2636 if not os.path.exists(self.worktree):
2637 return
2638
2639 base = R_WORKTREE_M
2640 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002641
2642 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002643 else:
2644 base = R_M
2645 active_git = self.bare_git
2646
2647 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002648
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002649 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002650 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002651
Remy Böhmer1469c282020-12-15 18:49:02 +01002652 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002653 cur = self.bare_ref.symref(ref)
2654
2655 if self.revisionId:
2656 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2657 msg = 'manifest set to %s' % self.revisionId
2658 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002659 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002660 else:
2661 remote = self.GetRemote(self.remote.name)
2662 dst = remote.ToLocal(self.revisionExpr)
2663 if cur != dst:
2664 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002665 if detach:
2666 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2667 else:
2668 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002669
Mike Frysingerc72bd842021-11-14 03:58:00 -05002670 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002671 # Git worktrees don't use symlinks to share at all.
2672 if self.use_git_worktrees:
2673 return
2674
Mike Frysingerd33dce02021-12-20 18:16:33 -05002675 for name in self.shareable_dirs:
Mike Frysingered4f2112020-02-11 23:06:29 -05002676 # Try to self-heal a bit in simple cases.
2677 dst_path = os.path.join(destdir, name)
2678 src_path = os.path.join(srcdir, name)
2679
Mike Frysingered4f2112020-02-11 23:06:29 -05002680 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002681 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002682 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002683 # Fail if the links are pointing to the wrong place
2684 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002685 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002686 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002687 'work tree. If you\'re comfortable with the '
2688 'possibility of losing the work tree\'s git metadata,'
2689 ' use `repo sync --force-sync {0}` to '
2690 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002691
Mike Frysingerc72bd842021-11-14 03:58:00 -05002692 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002693 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2694
2695 Args:
2696 gitdir: The bare git repository. Must already be initialized.
2697 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002698 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2699 This saves you the effort of initializing |dotgit| yourself.
2700 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002701 symlink_dirs = self.shareable_dirs[:]
Mike Frysingerd33dce02021-12-20 18:16:33 -05002702 to_symlink = symlink_dirs
David James8d201162013-10-11 17:03:19 -07002703
2704 to_copy = []
2705 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002706 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002707
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002708 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002709 for name in set(to_copy).union(to_symlink):
2710 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002711 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002712 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002713
Kevin Degi384b3c52014-10-16 16:02:58 -06002714 if os.path.lexists(dst):
2715 continue
David James8d201162013-10-11 17:03:19 -07002716
2717 # If the source dir doesn't exist, create an empty dir.
2718 if name in symlink_dirs and not os.path.lexists(src):
2719 os.makedirs(src)
2720
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002721 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002722 platform_utils.symlink(
2723 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002724 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002725 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002726 shutil.copytree(src, dst)
2727 elif os.path.isfile(src):
2728 shutil.copy(src, dst)
2729
David James8d201162013-10-11 17:03:19 -07002730 except OSError as e:
2731 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002732 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002733 else:
2734 raise
2735
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002736 def _InitGitWorktree(self):
2737 """Init the project using git worktrees."""
2738 self.bare_git.worktree('prune')
2739 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2740 self.worktree, self.GetRevisionId())
2741
2742 # Rewrite the internal state files to use relative paths between the
2743 # checkouts & worktrees.
2744 dotgit = os.path.join(self.worktree, '.git')
2745 with open(dotgit, 'r') as fp:
2746 # Figure out the checkout->worktree path.
2747 setting = fp.read()
2748 assert setting.startswith('gitdir:')
2749 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002750 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2751 # of file permissions. Delete it and recreate it from scratch to avoid.
2752 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002753 # Use relative path from checkout->worktree & maintain Unix line endings
2754 # on all OS's to match git behavior.
2755 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002756 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2757 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002758 # Use relative path from worktree->checkout & maintain Unix line endings
2759 # on all OS's to match git behavior.
2760 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002761 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2762
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002763 self._InitMRef()
2764
Martin Kellye4e94d22017-03-21 16:05:12 -07002765 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002766 """Setup the worktree .git path.
2767
2768 This is the user-visible path like src/foo/.git/.
2769
2770 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2771 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2772
2773 Older checkouts had .git/ directories. If we see that, migrate it.
2774
2775 This also handles changes in the manifest. Maybe this project was backed
2776 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2777 the path we point to under .repo/projects/ to match.
2778 """
2779 dotgit = os.path.join(self.worktree, '.git')
2780
2781 # If using an old layout style (a directory), migrate it.
2782 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2783 self._MigrateOldWorkTreeGitDir(dotgit)
2784
2785 init_dotgit = not os.path.exists(dotgit)
2786 if self.use_git_worktrees:
2787 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002788 self._InitGitWorktree()
2789 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002790 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002791 if not init_dotgit:
2792 # See if the project has changed.
2793 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2794 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002795
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002796 if init_dotgit or not os.path.exists(dotgit):
2797 os.makedirs(self.worktree, exist_ok=True)
2798 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002799
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002800 if init_dotgit:
2801 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002802
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002803 # Finish checking out the worktree.
2804 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2805 if GitCommand(self, cmd).Wait() != 0:
2806 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002807
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002808 if submodules:
2809 self._SyncSubmodules(quiet=True)
2810 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002811
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002812 @classmethod
2813 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2814 """Migrate the old worktree .git/ dir style to a symlink.
2815
2816 This logic specifically only uses state from |dotgit| to figure out where to
2817 move content and not |self|. This way if the backing project also changed
2818 places, we only do the .git/ dir to .git symlink migration here. The path
2819 updates will happen independently.
2820 """
2821 # Figure out where in .repo/projects/ it's pointing to.
2822 if not os.path.islink(os.path.join(dotgit, 'refs')):
2823 raise GitError(f'{dotgit}: unsupported checkout state')
2824 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2825
2826 # Remove known symlink paths that exist in .repo/projects/.
2827 KNOWN_LINKS = {
2828 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2829 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2830 }
2831 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2832 SAFE_TO_CLOBBER = {
Mike Frysinger8e912482022-01-26 04:03:34 -05002833 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gc.log', 'gitk.cache', 'index',
2834 'ORIG_HEAD',
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002835 }
2836
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002837 # First see if we'd succeed before starting the migration.
2838 unknown_paths = []
2839 for name in platform_utils.listdir(dotgit):
2840 # Ignore all temporary/backup names. These are common with vim & emacs.
2841 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2842 continue
2843
2844 dotgit_path = os.path.join(dotgit, name)
2845 if name in KNOWN_LINKS:
2846 if not platform_utils.islink(dotgit_path):
2847 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2848 else:
2849 gitdir_path = os.path.join(gitdir, name)
2850 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2851 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2852 if unknown_paths:
2853 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2854
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002855 # Now walk the paths and sync the .git/ to .repo/projects/.
2856 for name in platform_utils.listdir(dotgit):
2857 dotgit_path = os.path.join(dotgit, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002858
2859 # Ignore all temporary/backup names. These are common with vim & emacs.
2860 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2861 platform_utils.remove(dotgit_path)
2862 elif name in KNOWN_LINKS:
2863 platform_utils.remove(dotgit_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002864 else:
2865 gitdir_path = os.path.join(gitdir, name)
Mike Frysinger89ed8ac2022-01-06 05:42:24 -05002866 platform_utils.remove(gitdir_path, missing_ok=True)
2867 platform_utils.rename(dotgit_path, gitdir_path)
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002868
2869 # Now that the dir should be empty, clear it out, and symlink it over.
2870 platform_utils.rmdir(dotgit)
2871 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002872
Renaud Paquay788e9622017-01-27 11:41:12 -08002873 def _get_symlink_error_message(self):
2874 if platform_utils.isWindows():
2875 return ('Unable to create symbolic link. Please re-run the command as '
2876 'Administrator, or see '
2877 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2878 'for other options.')
2879 return 'filesystem must support symlinks'
2880
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002881 def _revlist(self, *args, **kw):
2882 a = []
2883 a.extend(args)
2884 a.append('--')
2885 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002886
2887 @property
2888 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002889 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002890
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002891 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002892 """Get logs between two revisions of this project."""
2893 comp = '..'
2894 if rev1:
2895 revs = [rev1]
2896 if rev2:
2897 revs.extend([comp, rev2])
2898 cmd = ['log', ''.join(revs)]
2899 out = DiffColoring(self.config)
2900 if out.is_on and color:
2901 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002902 if pretty_format is not None:
2903 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002904 if oneline:
2905 cmd.append('--oneline')
2906
2907 try:
2908 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2909 if log.Wait() == 0:
2910 return log.stdout
2911 except GitError:
2912 # worktree may not exist if groups changed for example. In that case,
2913 # try in gitdir instead.
2914 if not os.path.exists(self.worktree):
2915 return self.bare_git.log(*cmd[1:])
2916 else:
2917 raise
2918 return None
2919
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002920 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2921 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002922 """Get the list of logs from this revision to given revisionId"""
2923 logs = {}
2924 selfId = self.GetRevisionId(self._allrefs)
2925 toId = toProject.GetRevisionId(toProject._allrefs)
2926
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002927 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2928 pretty_format=pretty_format)
2929 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2930 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002931 return logs
2932
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002933 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002934
David James8d201162013-10-11 17:03:19 -07002935 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002936 self._project = project
2937 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002938 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002939
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002940 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2941 def __getstate__(self):
2942 return (self._project, self._bare, self._gitdir)
2943
2944 def __setstate__(self, state):
2945 self._project, self._bare, self._gitdir = state
2946
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002947 def LsOthers(self):
2948 p = GitCommand(self._project,
2949 ['ls-files',
2950 '-z',
2951 '--others',
2952 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002953 bare=False,
David James8d201162013-10-11 17:03:19 -07002954 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002955 capture_stdout=True,
2956 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002957 if p.Wait() == 0:
2958 out = p.stdout
2959 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002960 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002961 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002962 return []
2963
2964 def DiffZ(self, name, *args):
2965 cmd = [name]
2966 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002967 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002968 cmd.extend(args)
2969 p = GitCommand(self._project,
2970 cmd,
David James8d201162013-10-11 17:03:19 -07002971 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002972 bare=False,
2973 capture_stdout=True,
2974 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002975 p.Wait()
2976 r = {}
2977 out = p.stdout
2978 if out:
2979 out = iter(out[:-1].split('\0'))
2980 while out:
2981 try:
2982 info = next(out)
2983 path = next(out)
2984 except StopIteration:
2985 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002986
Mike Frysinger84230002021-02-16 17:08:35 -05002987 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002988
Mike Frysinger84230002021-02-16 17:08:35 -05002989 def __init__(self, path, omode, nmode, oid, nid, state):
2990 self.path = path
2991 self.src_path = None
2992 self.old_mode = omode
2993 self.new_mode = nmode
2994 self.old_id = oid
2995 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002996
Mike Frysinger84230002021-02-16 17:08:35 -05002997 if len(state) == 1:
2998 self.status = state
2999 self.level = None
3000 else:
3001 self.status = state[:1]
3002 self.level = state[1:]
3003 while self.level.startswith('0'):
3004 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003005
Mike Frysinger84230002021-02-16 17:08:35 -05003006 info = info[1:].split(' ')
3007 info = _Info(path, *info)
3008 if info.status in ('R', 'C'):
3009 info.src_path = info.path
3010 info.path = next(out)
3011 r[info.path] = info
3012 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003013
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003014 def GetDotgitPath(self, subpath=None):
3015 """Return the full path to the .git dir.
3016
3017 As a convenience, append |subpath| if provided.
3018 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003019 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003020 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003021 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003022 dotgit = os.path.join(self._project.worktree, '.git')
3023 if os.path.isfile(dotgit):
3024 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3025 with open(dotgit) as fp:
3026 setting = fp.read()
3027 assert setting.startswith('gitdir:')
3028 gitdir = setting.split(':', 1)[1].strip()
3029 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3030
3031 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3032
3033 def GetHead(self):
3034 """Return the ref that HEAD points to."""
3035 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003036 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003037 with open(path) as fd:
3038 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003039 except IOError as e:
3040 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003041 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303042 line = line.decode()
3043 except AttributeError:
3044 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003045 if line.startswith('ref: '):
3046 return line[5:-1]
3047 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003048
3049 def SetHead(self, ref, message=None):
3050 cmdv = []
3051 if message is not None:
3052 cmdv.extend(['-m', message])
3053 cmdv.append(HEAD)
3054 cmdv.append(ref)
3055 self.symbolic_ref(*cmdv)
3056
3057 def DetachHead(self, new, message=None):
3058 cmdv = ['--no-deref']
3059 if message is not None:
3060 cmdv.extend(['-m', message])
3061 cmdv.append(HEAD)
3062 cmdv.append(new)
3063 self.update_ref(*cmdv)
3064
3065 def UpdateRef(self, name, new, old=None,
3066 message=None,
3067 detach=False):
3068 cmdv = []
3069 if message is not None:
3070 cmdv.extend(['-m', message])
3071 if detach:
3072 cmdv.append('--no-deref')
3073 cmdv.append(name)
3074 cmdv.append(new)
3075 if old is not None:
3076 cmdv.append(old)
3077 self.update_ref(*cmdv)
3078
3079 def DeleteRef(self, name, old=None):
3080 if not old:
3081 old = self.rev_parse(name)
3082 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003083 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003084
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003085 def rev_list(self, *args, **kw):
3086 if 'format' in kw:
3087 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3088 else:
3089 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003090 cmdv.extend(args)
3091 p = GitCommand(self._project,
3092 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003093 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003094 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003095 capture_stdout=True,
3096 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003097 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003098 raise GitError('%s rev-list %s: %s' %
3099 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003100 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003101
3102 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003103 """Allow arbitrary git commands using pythonic syntax.
3104
3105 This allows you to do things like:
3106 git_obj.rev_parse('HEAD')
3107
3108 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3109 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003110 Any other positional arguments will be passed to the git command, and the
3111 following keyword arguments are supported:
3112 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003113
3114 Args:
3115 name: The name of the git command to call. Any '_' characters will
3116 be replaced with '-'.
3117
3118 Returns:
3119 A callable object that will try to call git with the named command.
3120 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003121 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003122
Dave Borowitz091f8932012-10-23 17:01:04 -07003123 def runner(*args, **kwargs):
3124 cmdv = []
3125 config = kwargs.pop('config', None)
3126 for k in kwargs:
3127 raise TypeError('%s() got an unexpected keyword argument %r'
3128 % (name, k))
3129 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303130 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003131 cmdv.append('-c')
3132 cmdv.append('%s=%s' % (k, v))
3133 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003134 cmdv.extend(args)
3135 p = GitCommand(self._project,
3136 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003137 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003138 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003139 capture_stdout=True,
3140 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003141 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003142 raise GitError('%s %s: %s' %
3143 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003144 r = p.stdout
3145 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3146 return r[:-1]
3147 return r
3148 return runner
3149
3150
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003151class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003152
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003153 def __str__(self):
3154 return 'prior sync failed; rebase still in progress'
3155
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003156
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003157class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003158
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003159 def __str__(self):
3160 return 'contains uncommitted changes'
3161
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003162
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003163class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003164
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003165 def __init__(self, project, text):
3166 self.project = project
3167 self.text = text
3168
3169 def Print(self, syncbuf):
3170 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3171 syncbuf.out.nl()
3172
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003173
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003174class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003175
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003176 def __init__(self, project, why):
3177 self.project = project
3178 self.why = why
3179
3180 def Print(self, syncbuf):
3181 syncbuf.out.fail('error: %s/: %s',
3182 self.project.relpath,
3183 str(self.why))
3184 syncbuf.out.nl()
3185
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003186
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003187class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003188
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003189 def __init__(self, project, action):
3190 self.project = project
3191 self.action = action
3192
3193 def Run(self, syncbuf):
3194 out = syncbuf.out
3195 out.project('project %s/', self.project.relpath)
3196 out.nl()
3197 try:
3198 self.action()
3199 out.nl()
3200 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003201 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003202 out.nl()
3203 return False
3204
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003205
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003206class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003207
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003208 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003209 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003210 self.project = self.printer('header', attr='bold')
3211 self.info = self.printer('info')
3212 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003213
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003214
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003215class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003216
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003217 def __init__(self, config, detach_head=False):
3218 self._messages = []
3219 self._failures = []
3220 self._later_queue1 = []
3221 self._later_queue2 = []
3222
3223 self.out = _SyncColoring(config)
3224 self.out.redirect(sys.stderr)
3225
3226 self.detach_head = detach_head
3227 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003228 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003229
3230 def info(self, project, fmt, *args):
3231 self._messages.append(_InfoMessage(project, fmt % args))
3232
3233 def fail(self, project, err=None):
3234 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003235 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003236
3237 def later1(self, project, what):
3238 self._later_queue1.append(_Later(project, what))
3239
3240 def later2(self, project, what):
3241 self._later_queue2.append(_Later(project, what))
3242
3243 def Finish(self):
3244 self._PrintMessages()
3245 self._RunLater()
3246 self._PrintMessages()
3247 return self.clean
3248
David Rileye0684ad2017-04-05 00:02:59 -07003249 def Recently(self):
3250 recent_clean = self.recent_clean
3251 self.recent_clean = True
3252 return recent_clean
3253
3254 def _MarkUnclean(self):
3255 self.clean = False
3256 self.recent_clean = False
3257
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003258 def _RunLater(self):
3259 for q in ['_later_queue1', '_later_queue2']:
3260 if not self._RunQueue(q):
3261 return
3262
3263 def _RunQueue(self, queue):
3264 for m in getattr(self, queue):
3265 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003266 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003267 return False
3268 setattr(self, queue, [])
3269 return True
3270
3271 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003272 if self._messages or self._failures:
3273 if os.isatty(2):
3274 self.out.write(progress.CSI_ERASE_LINE)
3275 self.out.write('\r')
3276
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003277 for m in self._messages:
3278 m.Print(self)
3279 for m in self._failures:
3280 m.Print(self)
3281
3282 self._messages = []
3283 self._failures = []
3284
3285
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003286class MetaProject(Project):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003287 """A special project housed under .repo."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003288
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003289 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003290 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003291 manifest=manifest,
3292 name=name,
3293 gitdir=gitdir,
3294 objdir=gitdir,
3295 worktree=worktree,
3296 remote=RemoteSpec('origin'),
3297 relpath='.repo/%s' % name,
3298 revisionExpr='refs/heads/master',
3299 revisionId=None,
3300 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003301
3302 def PreSync(self):
3303 if self.Exists:
3304 cb = self.CurrentBranch
3305 if cb:
3306 base = self.GetBranch(cb).merge
3307 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003308 self.revisionExpr = base
3309 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003310
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003311 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003312 def HasChanges(self):
LaMont Jones9b72cf22022-03-29 21:54:22 +00003313 """Has the remote received new commits not yet checked out?"""
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003314 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003315 return False
3316
David Pursehouse8a68ff92012-09-24 12:15:13 +09003317 all_refs = self.bare_ref.all
3318 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003319 head = self.work_git.GetHead()
3320 if head.startswith(R_HEADS):
3321 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003322 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003323 except KeyError:
3324 head = None
3325
3326 if revid == head:
3327 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003328 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003329 return True
3330 return False
LaMont Jones9b72cf22022-03-29 21:54:22 +00003331
3332
3333class RepoProject(MetaProject):
3334 """The MetaProject for repo itself."""
3335
3336 @property
3337 def LastFetch(self):
3338 try:
3339 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3340 return os.path.getmtime(fh)
3341 except OSError:
3342 return 0
3343
3344class ManifestProject(MetaProject):
3345 """The MetaProject for manifests."""
3346
3347 def MetaBranchSwitch(self, submodules=False):
3348 """Prepare for manifest branch switch."""
3349
3350 # detach and delete manifest branch, allowing a new
3351 # branch to take over
3352 syncbuf = SyncBuffer(self.config, detach_head=True)
3353 self.Sync_LocalHalf(syncbuf, submodules=submodules)
3354 syncbuf.Finish()
3355
3356 return GitCommand(self,
3357 ['update-ref', '-d', 'refs/heads/default'],
3358 capture_stdout=True,
3359 capture_stderr=True).Wait() == 0