blob: db44cde5bb8d05889cb4f8213272bb49ffa1b5a0 [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.
460 shareable_files = ['description', 'info']
Mike Frysinger41289c62021-12-20 17:30:33 -0500461 shareable_dirs = ['hooks', 'objects', 'rr-cache']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700462
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700463 def __init__(self,
464 manifest,
465 name,
466 remote,
467 gitdir,
David James8d201162013-10-11 17:03:19 -0700468 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700469 worktree,
470 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700471 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800472 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100473 rebase=True,
474 groups=None,
475 sync_c=False,
476 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900477 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100478 clone_depth=None,
479 upstream=None,
480 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500481 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100482 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900483 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700484 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600485 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700486 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800487 """Init a Project object.
488
489 Args:
490 manifest: The XmlManifest object.
491 name: The `name` attribute of manifest.xml's project element.
492 remote: RemoteSpec object specifying its remote's properties.
493 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700494 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800495 worktree: Absolute path of git working tree.
496 relpath: Relative path of git working tree to repo's top directory.
497 revisionExpr: The `revision` attribute of manifest.xml's project element.
498 revisionId: git commit id for checking out.
499 rebase: The `rebase` attribute of manifest.xml's project element.
500 groups: The `groups` attribute of manifest.xml's project element.
501 sync_c: The `sync-c` attribute of manifest.xml's project element.
502 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900503 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800504 upstream: The `upstream` attribute of manifest.xml's project element.
505 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500506 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800507 is_derived: False if the project was explicitly defined in the manifest;
508 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400509 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900510 optimized_fetch: If True, when a project is set to a sha1 revision, only
511 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600512 retry_fetches: Retry remote fetches n times upon receiving transient error
513 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700514 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800515 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400516 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700517 self.name = name
518 self.remote = remote
Michael Kelly37c21c22020-06-13 02:10:40 -0700519 self.UpdatePaths(relpath, worktree, gitdir, objdir)
Michael Kelly2f3c3312020-07-21 19:40:38 -0700520 self.SetRevision(revisionExpr, revisionId=revisionId)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700521
Mike Pontillod3153822012-02-28 11:53:24 -0800522 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700523 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700524 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800525 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900526 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900527 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700528 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800529 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500530 # NB: Do not use this setting in __init__ to change behavior so that the
531 # manifest.git checkout can inspect & change it after instantiating. See
532 # the XmlManifest init code for more info.
533 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800534 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900535 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600536 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800537 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800538
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700539 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500541 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500542 self.annotations = []
Bryan Jacobsf609f912013-05-06 13:36:24 -0400543 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700544 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700545
Doug Anderson37282b42011-03-04 11:54:18 -0800546 # This will be filled in if a project is later identified to be the
547 # project containing repo hooks.
548 self.enabled_repo_hooks = []
549
Michael Kelly2f3c3312020-07-21 19:40:38 -0700550 def SetRevision(self, revisionExpr, revisionId=None):
551 """Set revisionId based on revision expression and id"""
552 self.revisionExpr = revisionExpr
553 if revisionId is None and revisionExpr and IsId(revisionExpr):
554 self.revisionId = self.revisionExpr
555 else:
556 self.revisionId = revisionId
557
Michael Kelly37c21c22020-06-13 02:10:40 -0700558 def UpdatePaths(self, relpath, worktree, gitdir, objdir):
559 """Update paths used by this project"""
560 self.gitdir = gitdir.replace('\\', '/')
561 self.objdir = objdir.replace('\\', '/')
562 if worktree:
563 self.worktree = os.path.normpath(worktree).replace('\\', '/')
564 else:
565 self.worktree = None
566 self.relpath = relpath
567
568 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
569 defaults=self.manifest.globalConfig)
570
571 if self.worktree:
572 self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
573 else:
574 self.work_git = None
575 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
576 self.bare_ref = GitRefs(self.gitdir)
577 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
578
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700579 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800580 def Derived(self):
581 return self.is_derived
582
583 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700584 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700585 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700586
587 @property
588 def CurrentBranch(self):
589 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400590
591 The branch name omits the 'refs/heads/' prefix.
592 None is returned if the project is on a detached HEAD, or if the work_git is
593 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700594 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400595 try:
596 b = self.work_git.GetHead()
597 except NoManifestException:
598 # If the local checkout is in a bad state, don't barf. Let the callers
599 # process this like the head is unreadable.
600 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700601 if b.startswith(R_HEADS):
602 return b[len(R_HEADS):]
603 return None
604
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700605 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500606 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
607 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
608 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200609
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700610 def IsDirty(self, consider_untracked=True):
611 """Is the working directory modified in some way?
612 """
613 self.work_git.update_index('-q',
614 '--unmerged',
615 '--ignore-missing',
616 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900617 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700618 return True
619 if self.work_git.DiffZ('diff-files'):
620 return True
621 if consider_untracked and self.work_git.LsOthers():
622 return True
623 return False
624
625 _userident_name = None
626 _userident_email = None
627
628 @property
629 def UserName(self):
630 """Obtain the user's personal name.
631 """
632 if self._userident_name is None:
633 self._LoadUserIdentity()
634 return self._userident_name
635
636 @property
637 def UserEmail(self):
638 """Obtain the user's email address. This is very likely
639 to be their Gerrit login.
640 """
641 if self._userident_email is None:
642 self._LoadUserIdentity()
643 return self._userident_email
644
645 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900646 u = self.bare_git.var('GIT_COMMITTER_IDENT')
647 m = re.compile("^(.*) <([^>]*)> ").match(u)
648 if m:
649 self._userident_name = m.group(1)
650 self._userident_email = m.group(2)
651 else:
652 self._userident_name = ''
653 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654
655 def GetRemote(self, name):
656 """Get the configuration for a single remote.
657 """
658 return self.config.GetRemote(name)
659
660 def GetBranch(self, name):
661 """Get the configuration for a single branch.
662 """
663 return self.config.GetBranch(name)
664
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700665 def GetBranches(self):
666 """Get all existing local branches.
667 """
668 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900669 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700670 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700671
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530672 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700673 if name.startswith(R_HEADS):
674 name = name[len(R_HEADS):]
675 b = self.GetBranch(name)
676 b.current = name == current
677 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900678 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700679 heads[name] = b
680
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530681 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700682 if name.startswith(R_PUB):
683 name = name[len(R_PUB):]
684 b = heads.get(name)
685 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900686 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700687
688 return heads
689
Colin Cross5acde752012-03-28 20:15:45 -0700690 def MatchesGroups(self, manifest_groups):
691 """Returns true if the manifest groups specified at init should cause
692 this project to be synced.
693 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700694 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700695
696 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700697 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700698 manifest_groups: "-group1,group2"
699 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500700
701 The special manifest group "default" will match any project that
702 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700703 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500704 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700705 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700706 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500707 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700708
Conley Owens971de8e2012-04-16 10:36:08 -0700709 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700710 for group in expanded_manifest_groups:
711 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700712 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700713 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700714 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700715
Conley Owens971de8e2012-04-16 10:36:08 -0700716 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700718# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700719 def UncommitedFiles(self, get_all=True):
720 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700721
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700722 Args:
723 get_all: a boolean, if True - get information about all different
724 uncommitted files. If False - return as soon as any kind of
725 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500726 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700727 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500728 self.work_git.update_index('-q',
729 '--unmerged',
730 '--ignore-missing',
731 '--refresh')
732 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700733 details.append("rebase in progress")
734 if not get_all:
735 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500736
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700737 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
738 if changes:
739 details.extend(changes)
740 if not get_all:
741 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500742
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700743 changes = self.work_git.DiffZ('diff-files').keys()
744 if changes:
745 details.extend(changes)
746 if not get_all:
747 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500748
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700749 changes = self.work_git.LsOthers()
750 if changes:
751 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500752
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700753 return details
754
755 def HasChanges(self):
756 """Returns true if there are uncommitted changes.
757 """
758 if self.UncommitedFiles(get_all=False):
759 return True
760 else:
761 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500762
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600763 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700764 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200765
766 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200767 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600768 quiet: If True then only print the project name. Do not print
769 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700771 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700772 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200773 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700774 print(file=output_redir)
775 print('project %s/' % self.relpath, file=output_redir)
776 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700777 return
778
779 self.work_git.update_index('-q',
780 '--unmerged',
781 '--ignore-missing',
782 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700783 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
785 df = self.work_git.DiffZ('diff-files')
786 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100787 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700788 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700789
790 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700791 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200792 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700793 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600795 if quiet:
796 out.nl()
797 return 'DIRTY'
798
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700799 branch = self.CurrentBranch
800 if branch is None:
801 out.nobranch('(*** NO BRANCH ***)')
802 else:
803 out.branch('branch %s', branch)
804 out.nl()
805
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700806 if rb:
807 out.important('prior sync failed; rebase still in progress')
808 out.nl()
809
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810 paths = list()
811 paths.extend(di.keys())
812 paths.extend(df.keys())
813 paths.extend(do)
814
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530815 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900816 try:
817 i = di[p]
818 except KeyError:
819 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700820
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900821 try:
822 f = df[p]
823 except KeyError:
824 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200825
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900826 if i:
827 i_status = i.status.upper()
828 else:
829 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900831 if f:
832 f_status = f.status.lower()
833 else:
834 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835
836 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800837 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700838 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839 else:
840 line = ' %s%s\t%s' % (i_status, f_status, p)
841
842 if i and not f:
843 out.added('%s', line)
844 elif (i and f) or (not i and f):
845 out.changed('%s', line)
846 elif not i and not f:
847 out.untracked('%s', line)
848 else:
849 out.write('%s', line)
850 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200851
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700852 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500854 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700855 """Prints the status of the repository to stdout.
856 """
857 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500858 if output_redir:
859 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700860 cmd = ['diff']
861 if out.is_on:
862 cmd.append('--color')
863 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300864 if absolute_paths:
865 cmd.append('--src-prefix=a/%s/' % self.relpath)
866 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400868 try:
869 p = GitCommand(self,
870 cmd,
871 capture_stdout=True,
872 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500873 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400874 except GitError as e:
875 out.nl()
876 out.project('project %s/' % self.relpath)
877 out.nl()
878 out.fail('%s', str(e))
879 out.nl()
880 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500881 if p.stdout:
882 out.nl()
883 out.project('project %s/' % self.relpath)
884 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500885 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400886 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700887
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700888# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900889 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890 """Was the branch published (uploaded) for code review?
891 If so, returns the SHA-1 hash of the last published
892 state for the branch.
893 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700894 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900895 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700896 try:
897 return self.bare_git.rev_parse(key)
898 except GitError:
899 return None
900 else:
901 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900902 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700903 except KeyError:
904 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905
David Pursehouse8a68ff92012-09-24 12:15:13 +0900906 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700907 """Prunes any stale published refs.
908 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900909 if all_refs is None:
910 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911 heads = set()
912 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530913 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914 if name.startswith(R_HEADS):
915 heads.add(name)
916 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900917 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530919 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920 n = name[len(R_PUB):]
921 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900922 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700924 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925 """List any branches which can be uploaded for review.
926 """
927 heads = {}
928 pubed = {}
929
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530930 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900932 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900934 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700935
936 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530937 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900938 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700940 if selected_branch and branch != selected_branch:
941 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800943 rb = self.GetUploadableBranch(branch)
944 if rb:
945 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946 return ready
947
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800948 def GetUploadableBranch(self, branch_name):
949 """Get a single uploadable branch, or None.
950 """
951 branch = self.GetBranch(branch_name)
952 base = branch.LocalMerge
953 if branch.LocalMerge:
954 rb = ReviewableBranch(self, branch, base)
955 if rb.commits:
956 return rb
957 return None
958
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700959 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100960 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500961 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700962 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500963 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500964 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200965 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700966 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200967 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200968 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800969 validate_certs=True,
970 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700971 """Uploads the named branch for code review.
972 """
973 if branch is None:
974 branch = self.CurrentBranch
975 if branch is None:
976 raise GitError('not currently on a branch')
977
978 branch = self.GetBranch(branch)
979 if not branch.LocalMerge:
980 raise GitError('branch %s does not track a remote' % branch.name)
981 if not branch.remote.review:
982 raise GitError('remote %s has no review url' % branch.remote.name)
983
Bryan Jacobsf609f912013-05-06 13:36:24 -0400984 if dest_branch is None:
985 dest_branch = self.dest_branch
986 if dest_branch is None:
987 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700988 if not dest_branch.startswith(R_HEADS):
989 dest_branch = R_HEADS + dest_branch
990
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800991 if not branch.remote.projectname:
992 branch.remote.projectname = self.name
993 branch.remote.Save()
994
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200995 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800996 if url is None:
997 raise UploadError('review not configured')
998 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -0500999 if dryrun:
1000 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001001
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001002 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001003 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001004
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001005 for push_option in (push_options or []):
1006 cmd.append('-o')
1007 cmd.append(push_option)
1008
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001009 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001010
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001011 if dest_branch.startswith(R_HEADS):
1012 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001013
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001014 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001015 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001016 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001017 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001018 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001019 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001020
David Pursehousef25a3702018-11-14 19:01:22 -08001021 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001022 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001023 if notify:
1024 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001025 if private:
1026 opts += ['private']
1027 if wip:
1028 opts += ['wip']
1029 if opts:
1030 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001031 cmd.append(ref_spec)
1032
Anthony King7bdac712014-07-16 12:56:40 +01001033 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001034 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001035
Mike Frysingerd7f86832020-11-19 19:18:46 -05001036 if not dryrun:
1037 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1038 self.bare_git.UpdateRef(R_PUB + branch.name,
1039 R_HEADS + branch.name,
1040 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001042# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001043 def _ExtractArchive(self, tarpath, path=None):
1044 """Extract the given tar on its current location
1045
1046 Args:
1047 - tarpath: The path to the actual tar file
1048
1049 """
1050 try:
1051 with tarfile.open(tarpath, 'r') as tar:
1052 tar.extractall(path=path)
1053 return True
1054 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001055 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001056 return False
1057
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001058 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001059 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001060 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001061 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001062 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001063 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001064 force_sync=False,
1065 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001066 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001067 archive=False,
1068 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001069 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001070 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001071 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001072 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001073 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001074 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075 """Perform only the network IO portion of the sync process.
1076 Local working directory/branch state is not affected.
1077 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001078 if archive and not isinstance(self, MetaProject):
1079 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001080 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001081 return False
1082
1083 name = self.relpath.replace('\\', '/')
1084 name = name.replace('/', '_')
1085 tarpath = '%s.tar' % name
1086 topdir = self.manifest.topdir
1087
1088 try:
1089 self._FetchArchive(tarpath, cwd=topdir)
1090 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001091 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001092 return False
1093
1094 # From now on, we only need absolute tarpath
1095 tarpath = os.path.join(topdir, tarpath)
1096
1097 if not self._ExtractArchive(tarpath, path=topdir):
1098 return False
1099 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001100 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001101 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001102 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001103 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001104 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001105
1106 # If the shared object dir already exists, don't try to rebootstrap with a
1107 # clone bundle download. We should have the majority of objects already.
1108 if clone_bundle and os.path.exists(self.objdir):
1109 clone_bundle = False
1110
Raman Tennetif32f2432021-04-12 20:57:25 -07001111 if self.name in partial_clone_exclude:
1112 clone_bundle = True
1113 clone_filter = None
1114
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001115 if is_new is None:
1116 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001117 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001118 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001119 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001120 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001121 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001122
1123 if is_new:
1124 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1125 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001126 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001127 # This works for both absolute and relative alternate directories.
1128 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001129 except IOError:
1130 alt_dir = None
1131 else:
1132 alt_dir = None
1133
Mike Frysingere50b6a72020-02-19 01:45:48 -05001134 if (clone_bundle
1135 and alt_dir is None
1136 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001137 is_new = False
1138
Mike Frysinger73561142021-05-03 01:10:09 -04001139 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001140 if self.sync_c:
1141 current_branch_only = True
1142 elif not self.manifest._loaded:
1143 # Manifest cannot check defaults until it syncs.
1144 current_branch_only = False
1145 elif self.manifest.default.sync_c:
1146 current_branch_only = True
1147
Mike Frysingerd68ed632021-05-03 01:21:35 -04001148 if tags is None:
1149 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001150
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001151 if self.clone_depth:
1152 depth = self.clone_depth
1153 else:
1154 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1155
Mike Frysinger521d01b2020-02-17 01:51:49 -05001156 # See if we can skip the network fetch entirely.
1157 if not (optimized_fetch and
1158 (ID_RE.match(self.revisionExpr) and
1159 self._CheckForImmutableRevision())):
1160 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001161 initial=is_new,
1162 quiet=quiet, verbose=verbose, output_redir=output_redir,
1163 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001164 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001165 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001166 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001167 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001168 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001169
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001170 mp = self.manifest.manifestProject
1171 dissociate = mp.config.GetBoolean('repo.dissociate')
1172 if dissociate:
1173 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1174 if os.path.exists(alternates_file):
1175 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001176 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1177 merge_output=bool(output_redir))
1178 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001179 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001180 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001181 return False
1182 platform_utils.remove(alternates_file)
1183
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001184 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001185 self._InitMRef()
1186 else:
1187 self._InitMirrorHead()
Mike Frysinger9d96f582021-09-28 11:27:24 -04001188 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
1189 missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001191
1192 def PostRepoUpgrade(self):
1193 self._InitHooks()
1194
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001195 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001196 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001197 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001198 for copyfile in self.copyfiles:
1199 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001200 for linkfile in self.linkfiles:
1201 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001202
Julien Camperguedd654222014-01-09 16:21:37 +01001203 def GetCommitRevisionId(self):
1204 """Get revisionId of a commit.
1205
1206 Use this method instead of GetRevisionId to get the id of the commit rather
1207 than the id of the current git object (for example, a tag)
1208
1209 """
1210 if not self.revisionExpr.startswith(R_TAGS):
1211 return self.GetRevisionId(self._allrefs)
1212
1213 try:
1214 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1215 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001216 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1217 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001218
David Pursehouse8a68ff92012-09-24 12:15:13 +09001219 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001220 if self.revisionId:
1221 return self.revisionId
1222
1223 rem = self.GetRemote(self.remote.name)
1224 rev = rem.ToLocal(self.revisionExpr)
1225
David Pursehouse8a68ff92012-09-24 12:15:13 +09001226 if all_refs is not None and rev in all_refs:
1227 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001228
1229 try:
1230 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1231 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001232 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1233 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001234
Raman Tenneti6a872c92021-01-14 19:17:50 -08001235 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001236 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001237 self.upstream = self.revisionExpr
1238
Raman Tenneti6a872c92021-01-14 19:17:50 -08001239 self.revisionId = revisionId
1240
Martin Kellye4e94d22017-03-21 16:05:12 -07001241 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242 """Perform only the local IO portion of the sync process.
1243 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001244 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001245 if not os.path.exists(self.gitdir):
1246 syncbuf.fail(self,
1247 'Cannot checkout %s due to missing network sync; Run '
1248 '`repo sync -n %s` first.' %
1249 (self.name, self.name))
1250 return
1251
Martin Kellye4e94d22017-03-21 16:05:12 -07001252 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001253 all_refs = self.bare_ref.all
1254 self.CleanPublishedCache(all_refs)
1255 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001256
Mike Frysinger0458faa2021-03-10 23:35:44 -05001257 # Special case the root of the repo client checkout. Make sure it doesn't
1258 # contain files being checked out to dirs we don't allow.
1259 if self.relpath == '.':
1260 PROTECTED_PATHS = {'.repo'}
1261 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1262 bad_paths = paths & PROTECTED_PATHS
1263 if bad_paths:
1264 syncbuf.fail(self,
1265 'Refusing to checkout project that writes to protected '
1266 'paths: %s' % (', '.join(bad_paths),))
1267 return
1268
David Pursehouse1d947b32012-10-25 12:23:11 +09001269 def _doff():
1270 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001271 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001272
Martin Kellye4e94d22017-03-21 16:05:12 -07001273 def _dosubmodules():
1274 self._SyncSubmodules(quiet=True)
1275
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001276 head = self.work_git.GetHead()
1277 if head.startswith(R_HEADS):
1278 branch = head[len(R_HEADS):]
1279 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001280 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001281 except KeyError:
1282 head = None
1283 else:
1284 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001285
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001286 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001287 # Currently on a detached HEAD. The user is assumed to
1288 # not have any local modifications worth worrying about.
1289 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001290 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001291 syncbuf.fail(self, _PriorSyncFailedError())
1292 return
1293
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001294 if head == revid:
1295 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001296 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001297 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001298 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001299 # The copy/linkfile config may have changed.
1300 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001301 return
1302 else:
1303 lost = self._revlist(not_rev(revid), HEAD)
1304 if lost:
1305 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001306
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001308 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001309 if submodules:
1310 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001311 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001312 syncbuf.fail(self, e)
1313 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001314 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001315 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001317 if head == revid:
1318 # No changes; don't do anything further.
1319 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001320 # The copy/linkfile config may have changed.
1321 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001322 return
1323
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001325
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001326 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001328 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001330 syncbuf.info(self,
1331 "leaving %s; does not track upstream",
1332 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001333 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001334 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001335 if submodules:
1336 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001337 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001338 syncbuf.fail(self, e)
1339 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001340 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001341 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001343 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001344
1345 # See if we can perform a fast forward merge. This can happen if our
1346 # branch isn't in the exact same state as we last published.
1347 try:
1348 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1349 # Skip the published logic.
1350 pub = False
1351 except GitError:
1352 pub = self.WasPublished(branch.name, all_refs)
1353
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001354 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001355 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001356 if not_merged:
1357 if upstream_gain:
1358 # The user has published this branch and some of those
1359 # commits are not yet merged upstream. We do not want
1360 # to rewrite the published commits so we punt.
1361 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001362 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001363 "branch %s is published (but not merged) and is now "
1364 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001365 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001366 elif pub == head:
1367 # All published commits are merged, and thus we are a
1368 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001369 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001370 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001371 if submodules:
1372 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001373 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001374
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001375 # Examine the local commits not in the remote. Find the
1376 # last one attributed to this user, if any.
1377 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001378 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001379 last_mine = None
1380 cnt_mine = 0
1381 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001382 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001383 if committer_email == self.UserEmail:
1384 last_mine = commit_id
1385 cnt_mine += 1
1386
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001387 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001388 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389
1390 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001391 syncbuf.fail(self, _DirtyError())
1392 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001394 # If the upstream switched on us, warn the user.
1395 #
1396 if branch.merge != self.revisionExpr:
1397 if branch.merge and self.revisionExpr:
1398 syncbuf.info(self,
1399 'manifest switched %s...%s',
1400 branch.merge,
1401 self.revisionExpr)
1402 elif branch.merge:
1403 syncbuf.info(self,
1404 'manifest no longer tracks %s',
1405 branch.merge)
1406
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001407 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001408 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001409 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001410 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001411 syncbuf.info(self,
1412 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001413 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001414
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001415 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001416 if not ID_RE.match(self.revisionExpr):
1417 # in case of manifest sync the revisionExpr might be a SHA1
1418 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001419 if not branch.merge.startswith('refs/'):
1420 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001421 branch.Save()
1422
Mike Pontillod3153822012-02-28 11:53:24 -08001423 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001424 def _docopyandlink():
1425 self._CopyAndLinkFiles()
1426
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001427 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001428 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001429 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001430 if submodules:
1431 syncbuf.later2(self, _dosubmodules)
1432 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001433 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001434 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001435 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001436 if submodules:
1437 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001438 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001439 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001440 syncbuf.fail(self, e)
1441 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001442 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001443 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001444 if submodules:
1445 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001446
Mike Frysingere6a202f2019-08-02 15:57:57 -04001447 def AddCopyFile(self, src, dest, topdir):
1448 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001449
Mike Frysingere6a202f2019-08-02 15:57:57 -04001450 No filesystem changes occur here. Actual copying happens later on.
1451
1452 Paths should have basic validation run on them before being queued.
1453 Further checking will be handled when the actual copy happens.
1454 """
1455 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1456
1457 def AddLinkFile(self, src, dest, topdir):
1458 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1459
1460 No filesystem changes occur here. Actual linking happens later on.
1461
1462 Paths should have basic validation run on them before being queued.
1463 Further checking will be handled when the actual link happens.
1464 """
1465 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001466
James W. Mills24c13082012-04-12 15:04:13 -05001467 def AddAnnotation(self, name, value, keep):
Jack Neus6ea0cae2021-07-20 20:52:33 +00001468 self.annotations.append(Annotation(name, value, keep))
James W. Mills24c13082012-04-12 15:04:13 -05001469
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001470 def DownloadPatchSet(self, change_id, patch_id):
1471 """Download a single patch set of a single change to FETCH_HEAD.
1472 """
1473 remote = self.GetRemote(self.remote.name)
1474
1475 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001476 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001477 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001478 if GitCommand(self, cmd, bare=True).Wait() != 0:
1479 return None
1480 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001481 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001482 change_id,
1483 patch_id,
1484 self.bare_git.rev_parse('FETCH_HEAD'))
1485
Mike Frysingerc0d18662020-02-19 19:19:18 -05001486 def DeleteWorktree(self, quiet=False, force=False):
1487 """Delete the source checkout and any other housekeeping tasks.
1488
1489 This currently leaves behind the internal .repo/ cache state. This helps
1490 when switching branches or manifest changes get reverted as we don't have
1491 to redownload all the git objects. But we should do some GC at some point.
1492
1493 Args:
1494 quiet: Whether to hide normal messages.
1495 force: Always delete tree even if dirty.
1496
1497 Returns:
1498 True if the worktree was completely cleaned out.
1499 """
1500 if self.IsDirty():
1501 if force:
1502 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1503 (self.relpath,), file=sys.stderr)
1504 else:
1505 print('error: %s: Cannot remove project: uncommitted changes are '
1506 'present.\n' % (self.relpath,), file=sys.stderr)
1507 return False
1508
1509 if not quiet:
1510 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1511
1512 # Unlock and delink from the main worktree. We don't use git's worktree
1513 # remove because it will recursively delete projects -- we handle that
1514 # ourselves below. https://crbug.com/git/48
1515 if self.use_git_worktrees:
1516 needle = platform_utils.realpath(self.gitdir)
1517 # Find the git worktree commondir under .repo/worktrees/.
1518 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1519 assert output.startswith('worktree '), output
1520 commondir = output[9:]
1521 # Walk each of the git worktrees to see where they point.
1522 configs = os.path.join(commondir, 'worktrees')
1523 for name in os.listdir(configs):
1524 gitdir = os.path.join(configs, name, 'gitdir')
1525 with open(gitdir) as fp:
1526 relpath = fp.read().strip()
1527 # Resolve the checkout path and see if it matches this project.
1528 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1529 if fullpath == needle:
1530 platform_utils.rmtree(os.path.join(configs, name))
1531
1532 # Delete the .git directory first, so we're less likely to have a partially
1533 # working git repository around. There shouldn't be any git projects here,
1534 # so rmtree works.
1535
1536 # Try to remove plain files first in case of git worktrees. If this fails
1537 # for any reason, we'll fall back to rmtree, and that'll display errors if
1538 # it can't remove things either.
1539 try:
1540 platform_utils.remove(self.gitdir)
1541 except OSError:
1542 pass
1543 try:
1544 platform_utils.rmtree(self.gitdir)
1545 except OSError as e:
1546 if e.errno != errno.ENOENT:
1547 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1548 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1549 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1550 return False
1551
1552 # Delete everything under the worktree, except for directories that contain
1553 # another git project.
1554 dirs_to_remove = []
1555 failed = False
1556 for root, dirs, files in platform_utils.walk(self.worktree):
1557 for f in files:
1558 path = os.path.join(root, f)
1559 try:
1560 platform_utils.remove(path)
1561 except OSError as e:
1562 if e.errno != errno.ENOENT:
1563 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1564 failed = True
1565 dirs[:] = [d for d in dirs
1566 if not os.path.lexists(os.path.join(root, d, '.git'))]
1567 dirs_to_remove += [os.path.join(root, d) for d in dirs
1568 if os.path.join(root, d) not in dirs_to_remove]
1569 for d in reversed(dirs_to_remove):
1570 if platform_utils.islink(d):
1571 try:
1572 platform_utils.remove(d)
1573 except OSError as e:
1574 if e.errno != errno.ENOENT:
1575 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1576 failed = True
1577 elif not platform_utils.listdir(d):
1578 try:
1579 platform_utils.rmdir(d)
1580 except OSError as e:
1581 if e.errno != errno.ENOENT:
1582 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1583 failed = True
1584 if failed:
1585 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1586 file=sys.stderr)
1587 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1588 return False
1589
1590 # Try deleting parent dirs if they are empty.
1591 path = self.worktree
1592 while path != self.manifest.topdir:
1593 try:
1594 platform_utils.rmdir(path)
1595 except OSError as e:
1596 if e.errno != errno.ENOENT:
1597 break
1598 path = os.path.dirname(path)
1599
1600 return True
1601
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001602# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001603 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001604 """Create a new branch off the manifest's revision.
1605 """
Simran Basib9a1b732015-08-20 12:19:28 -07001606 if not branch_merge:
1607 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001608 head = self.work_git.GetHead()
1609 if head == (R_HEADS + name):
1610 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001611
David Pursehouse8a68ff92012-09-24 12:15:13 +09001612 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001613 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001614 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001615 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001616 capture_stdout=True,
1617 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001618
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001619 branch = self.GetBranch(name)
1620 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001621 branch.merge = branch_merge
1622 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1623 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001624
1625 if revision is None:
1626 revid = self.GetRevisionId(all_refs)
1627 else:
1628 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001629
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001630 if head.startswith(R_HEADS):
1631 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001632 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001633 except KeyError:
1634 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001635 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001636 ref = R_HEADS + name
1637 self.work_git.update_ref(ref, revid)
1638 self.work_git.symbolic_ref(HEAD, ref)
1639 branch.Save()
1640 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001641
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001642 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001643 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001644 capture_stdout=True,
1645 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001646 branch.Save()
1647 return True
1648 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001649
Wink Saville02d79452009-04-10 13:01:24 -07001650 def CheckoutBranch(self, name):
1651 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001652
1653 Args:
1654 name: The name of the branch to checkout.
1655
1656 Returns:
1657 True if the checkout succeeded; False if it didn't; None if the branch
1658 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001659 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001660 rev = R_HEADS + name
1661 head = self.work_git.GetHead()
1662 if head == rev:
1663 # Already on the branch
1664 #
1665 return True
Wink Saville02d79452009-04-10 13:01:24 -07001666
David Pursehouse8a68ff92012-09-24 12:15:13 +09001667 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001668 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001669 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001670 except KeyError:
1671 # Branch does not exist in this project
1672 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001673 return None
Wink Saville02d79452009-04-10 13:01:24 -07001674
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001675 if head.startswith(R_HEADS):
1676 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001677 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001678 except KeyError:
1679 head = None
1680
1681 if head == revid:
1682 # Same revision; just update HEAD to point to the new
1683 # target branch, but otherwise take no other action.
1684 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001685 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1686 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001687 return True
1688
1689 return GitCommand(self,
1690 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001691 capture_stdout=True,
1692 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001693
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001694 def AbandonBranch(self, name):
1695 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001696
1697 Args:
1698 name: The name of the branch to abandon.
1699
1700 Returns:
1701 True if the abandon succeeded; False if it didn't; None if the branch
1702 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001703 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001704 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001705 all_refs = self.bare_ref.all
1706 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001707 # Doesn't exist
1708 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001709
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001710 head = self.work_git.GetHead()
1711 if head == rev:
1712 # We can't destroy the branch while we are sitting
1713 # on it. Switch to a detached HEAD.
1714 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001715 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001716
David Pursehouse8a68ff92012-09-24 12:15:13 +09001717 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001718 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001719 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001720 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001721 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001722
1723 return GitCommand(self,
1724 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001725 capture_stdout=True,
1726 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001727
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001728 def PruneHeads(self):
1729 """Prune any topic branches already merged into upstream.
1730 """
1731 cb = self.CurrentBranch
1732 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001733 left = self._allrefs
1734 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001735 if name.startswith(R_HEADS):
1736 name = name[len(R_HEADS):]
1737 if cb is None or name != cb:
1738 kill.append(name)
1739
Mike Frysingera3794e92021-03-11 23:24:01 -05001740 # Minor optimization: If there's nothing to prune, then don't try to read
1741 # any project state.
1742 if not kill and not cb:
1743 return []
1744
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001745 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001746 if cb is not None \
1747 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001748 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001749 self.work_git.DetachHead(HEAD)
1750 kill.append(cb)
1751
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001752 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001753 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001754
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001755 try:
1756 self.bare_git.DetachHead(rev)
1757
1758 b = ['branch', '-d']
1759 b.extend(kill)
1760 b = GitCommand(self, b, bare=True,
1761 capture_stdout=True,
1762 capture_stderr=True)
1763 b.Wait()
1764 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001765 if ID_RE.match(old):
1766 self.bare_git.DetachHead(old)
1767 else:
1768 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001769 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001770
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001771 for branch in kill:
1772 if (R_HEADS + branch) not in left:
1773 self.CleanPublishedCache()
1774 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001775
1776 if cb and cb not in kill:
1777 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001778 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001779
1780 kept = []
1781 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001782 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001783 branch = self.GetBranch(branch)
1784 base = branch.LocalMerge
1785 if not base:
1786 base = rev
1787 kept.append(ReviewableBranch(self, branch, base))
1788 return kept
1789
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001790# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001791 def GetRegisteredSubprojects(self):
1792 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001793
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001794 def rec(subprojects):
1795 if not subprojects:
1796 return
1797 result.extend(subprojects)
1798 for p in subprojects:
1799 rec(p.subprojects)
1800 rec(self.subprojects)
1801 return result
1802
1803 def _GetSubmodules(self):
1804 # Unfortunately we cannot call `git submodule status --recursive` here
1805 # because the working tree might not exist yet, and it cannot be used
1806 # without a working tree in its current implementation.
1807
1808 def get_submodules(gitdir, rev):
1809 # Parse .gitmodules for submodule sub_paths and sub_urls
1810 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1811 if not sub_paths:
1812 return []
1813 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1814 # revision of submodule repository
1815 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1816 submodules = []
1817 for sub_path, sub_url in zip(sub_paths, sub_urls):
1818 try:
1819 sub_rev = sub_revs[sub_path]
1820 except KeyError:
1821 # Ignore non-exist submodules
1822 continue
1823 submodules.append((sub_rev, sub_path, sub_url))
1824 return submodules
1825
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001826 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1827 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001828
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001829 def parse_gitmodules(gitdir, rev):
1830 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1831 try:
Anthony King7bdac712014-07-16 12:56:40 +01001832 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1833 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001834 except GitError:
1835 return [], []
1836 if p.Wait() != 0:
1837 return [], []
1838
1839 gitmodules_lines = []
1840 fd, temp_gitmodules_path = tempfile.mkstemp()
1841 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001842 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001843 os.close(fd)
1844 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001845 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1846 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001847 if p.Wait() != 0:
1848 return [], []
1849 gitmodules_lines = p.stdout.split('\n')
1850 except GitError:
1851 return [], []
1852 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001853 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001854
1855 names = set()
1856 paths = {}
1857 urls = {}
1858 for line in gitmodules_lines:
1859 if not line:
1860 continue
1861 m = re_path.match(line)
1862 if m:
1863 names.add(m.group(1))
1864 paths[m.group(1)] = m.group(2)
1865 continue
1866 m = re_url.match(line)
1867 if m:
1868 names.add(m.group(1))
1869 urls[m.group(1)] = m.group(2)
1870 continue
1871 names = sorted(names)
1872 return ([paths.get(name, '') for name in names],
1873 [urls.get(name, '') for name in names])
1874
1875 def git_ls_tree(gitdir, rev, paths):
1876 cmd = ['ls-tree', rev, '--']
1877 cmd.extend(paths)
1878 try:
Anthony King7bdac712014-07-16 12:56:40 +01001879 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1880 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001881 except GitError:
1882 return []
1883 if p.Wait() != 0:
1884 return []
1885 objects = {}
1886 for line in p.stdout.split('\n'):
1887 if not line.strip():
1888 continue
1889 object_rev, object_path = line.split()[2:4]
1890 objects[object_path] = object_rev
1891 return objects
1892
1893 try:
1894 rev = self.GetRevisionId()
1895 except GitError:
1896 return []
1897 return get_submodules(self.gitdir, rev)
1898
1899 def GetDerivedSubprojects(self):
1900 result = []
1901 if not self.Exists:
1902 # If git repo does not exist yet, querying its submodules will
1903 # mess up its states; so return here.
1904 return result
1905 for rev, path, url in self._GetSubmodules():
1906 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001907 relpath, worktree, gitdir, objdir = \
1908 self.manifest.GetSubprojectPaths(self, name, path)
1909 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001910 if project:
1911 result.extend(project.GetDerivedSubprojects())
1912 continue
David James8d201162013-10-11 17:03:19 -07001913
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001914 if url.startswith('..'):
1915 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001916 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001917 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001918 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001919 review=self.remote.review,
1920 revision=self.remote.revision)
1921 subproject = Project(manifest=self.manifest,
1922 name=name,
1923 remote=remote,
1924 gitdir=gitdir,
1925 objdir=objdir,
1926 worktree=worktree,
1927 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001928 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001929 revisionId=rev,
1930 rebase=self.rebase,
1931 groups=self.groups,
1932 sync_c=self.sync_c,
1933 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001934 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001935 parent=self,
1936 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001937 result.append(subproject)
1938 result.extend(subproject.GetDerivedSubprojects())
1939 return result
1940
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001941# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001942 def EnableRepositoryExtension(self, key, value='true', version=1):
1943 """Enable git repository extension |key| with |value|.
1944
1945 Args:
1946 key: The extension to enabled. Omit the "extensions." prefix.
1947 value: The value to use for the extension.
1948 version: The minimum git repository version needed.
1949 """
1950 # Make sure the git repo version is new enough already.
1951 found_version = self.config.GetInt('core.repositoryFormatVersion')
1952 if found_version is None:
1953 found_version = 0
1954 if found_version < version:
1955 self.config.SetString('core.repositoryFormatVersion', str(version))
1956
1957 # Enable the extension!
1958 self.config.SetString('extensions.%s' % (key,), value)
1959
Mike Frysinger50a81de2020-09-06 15:51:21 -04001960 def ResolveRemoteHead(self, name=None):
1961 """Find out what the default branch (HEAD) points to.
1962
1963 Normally this points to refs/heads/master, but projects are moving to main.
1964 Support whatever the server uses rather than hardcoding "master" ourselves.
1965 """
1966 if name is None:
1967 name = self.remote.name
1968
1969 # The output will look like (NB: tabs are separators):
1970 # ref: refs/heads/master HEAD
1971 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1972 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1973
1974 for line in output.splitlines():
1975 lhs, rhs = line.split('\t', 1)
1976 if rhs == 'HEAD' and lhs.startswith('ref:'):
1977 return lhs[4:].strip()
1978
1979 return None
1980
Zac Livingstone4332262017-06-16 08:56:09 -06001981 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001982 try:
1983 # if revision (sha or tag) is not present then following function
1984 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08001985 self.bare_git.rev_list('-1', '--missing=allow-any',
1986 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00001987 if self.upstream:
1988 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
1989 self.bare_git.rev_list('-1', '--missing=allow-any',
1990 '%s^0' % rev, '--')
Xin Li8e983bb2021-07-20 20:15:30 +00001991 self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
Chris AtLee2fb64662014-01-16 21:32:33 -05001992 return True
1993 except GitError:
1994 # There is no such persistent revision. We have to fetch it.
1995 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001996
Julien Campergue335f5ef2013-10-16 11:02:35 +02001997 def _FetchArchive(self, tarpath, cwd=None):
1998 cmd = ['archive', '-v', '-o', tarpath]
1999 cmd.append('--remote=%s' % self.remote.url)
2000 cmd.append('--prefix=%s/' % self.relpath)
2001 cmd.append(self.revisionExpr)
2002
2003 command = GitCommand(self, cmd, cwd=cwd,
2004 capture_stdout=True,
2005 capture_stderr=True)
2006
2007 if command.Wait() != 0:
2008 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2009
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002010 def _RemoteFetch(self, name=None,
2011 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002012 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002013 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002014 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002015 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002016 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002017 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002018 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002019 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002020 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002021 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002022 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002023 clone_filter=None,
2024 retry_fetches=2,
2025 retry_sleep_initial_sec=4.0,
2026 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002027 is_sha1 = False
2028 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002029 # The depth should not be used when fetching to a mirror because
2030 # it will result in a shallow repository that cannot be cloned or
2031 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002032 # The repo project should also never be synced with partial depth.
2033 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2034 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002035
Shawn Pearce69e04d82014-01-29 12:48:54 -08002036 if depth:
2037 current_branch_only = True
2038
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002039 if ID_RE.match(self.revisionExpr) is not None:
2040 is_sha1 = True
2041
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002042 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002043 if self.revisionExpr.startswith(R_TAGS):
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002044 # This is a tag and its commit id should never change.
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002045 tag_name = self.revisionExpr[len(R_TAGS):]
Robin Schneider9c1fc5b2021-11-13 22:55:32 +01002046 elif self.upstream and self.upstream.startswith(R_TAGS):
2047 # This is a tag and its commit id should never change.
2048 tag_name = self.upstream[len(R_TAGS):]
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002049
2050 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002051 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002052 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002053 print('Skipped fetching project %s (already have persistent ref)'
2054 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002055 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002056 if is_sha1 and not depth:
2057 # When syncing a specific commit and --depth is not set:
2058 # * if upstream is explicitly specified and is not a sha1, fetch only
2059 # upstream as users expect only upstream to be fetch.
2060 # Note: The commit might not be in upstream in which case the sync
2061 # will fail.
2062 # * otherwise, fetch all branches to make sure we end up with the
2063 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002064 if self.upstream:
2065 current_branch_only = not ID_RE.match(self.upstream)
2066 else:
2067 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002068
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002069 if not name:
2070 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002071
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002072 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002073 if not remote.PreConnectFetch(ssh_proxy):
2074 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002075
Shawn O. Pearce88443382010-10-08 10:02:09 +02002076 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002077 if alt_dir and 'objects' == os.path.basename(alt_dir):
2078 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002079 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002080
David Pursehouse8a68ff92012-09-24 12:15:13 +09002081 all_refs = self.bare_ref.all
2082 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002083 tmp = set()
2084
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302085 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002086 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002087 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002088 all_refs[r] = ref_id
2089 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002090 continue
2091
David Pursehouse8a68ff92012-09-24 12:15:13 +09002092 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002093 continue
2094
David Pursehouse8a68ff92012-09-24 12:15:13 +09002095 r = 'refs/_alt/%s' % ref_id
2096 all_refs[r] = ref_id
2097 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002098 tmp.add(r)
2099
heping3d7bbc92017-04-12 19:51:47 +08002100 tmp_packed_lines = []
2101 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002102
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302103 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002104 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002105 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002106 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002107 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002108
heping3d7bbc92017-04-12 19:51:47 +08002109 tmp_packed = ''.join(tmp_packed_lines)
2110 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002111 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002112 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002113 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002114
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002115 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002116
Xin Li745be2e2019-06-03 11:24:30 -07002117 if clone_filter:
2118 git_require((2, 19, 0), fail=True, msg='partial clones')
2119 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002120 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002121
Conley Owensf97e8382015-01-21 11:12:46 -08002122 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002123 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002124 else:
2125 # If this repo has shallow objects, then we don't know which refs have
2126 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2127 # do this with projects that don't have shallow objects, since it is less
2128 # efficient.
2129 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2130 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002131
Mike Frysinger4847e052020-02-22 00:07:35 -05002132 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002133 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002134 if not quiet and sys.stdout.isatty():
2135 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002136 if not self.worktree:
2137 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002138 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002139
Mike Frysingere57f1142019-03-18 21:27:54 -04002140 if force_sync:
2141 cmd.append('--force')
2142
David Pursehouse74cfd272015-10-14 10:50:15 +09002143 if prune:
2144 cmd.append('--prune')
2145
Martin Kellye4e94d22017-03-21 16:05:12 -07002146 if submodules:
2147 cmd.append('--recurse-submodules=on-demand')
2148
Kuang-che Wu6856f982019-11-25 12:37:55 +08002149 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002150 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002151 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002152 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002153 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002154 spec.append('tag')
2155 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002156
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302157 if self.manifest.IsMirror and not current_branch_only:
2158 branch = None
2159 else:
2160 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002161 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002162 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002163 # Shallow checkout of a specific commit, fetch from that commit and not
2164 # the heads only as the commit might be deeper in the history.
2165 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002166 if self.upstream:
2167 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002168 else:
2169 if is_sha1:
2170 branch = self.upstream
2171 if branch is not None and branch.strip():
2172 if not branch.startswith('refs/'):
2173 branch = R_HEADS + branch
2174 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2175
2176 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2177 # whole repo.
2178 if self.manifest.IsMirror and not spec:
2179 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2180
2181 # If using depth then we should not get all the tags since they may
2182 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002183 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002184 cmd.append('--no-tags')
2185 else:
2186 cmd.append('--tags')
2187 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2188
Conley Owens80b87fe2014-05-09 17:13:44 -07002189 cmd.extend(spec)
2190
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002191 # At least one retry minimum due to git remote prune.
2192 retry_fetches = max(retry_fetches, 2)
2193 retry_cur_sleep = retry_sleep_initial_sec
2194 ok = prune_tried = False
2195 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002196 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002197 merge_output=True, capture_stdout=quiet or bool(output_redir))
2198 if gitcmd.stdout and not quiet and output_redir:
2199 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002200 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002201 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002202 ok = True
2203 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002204
2205 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002206 elif (gitcmd.stdout and
2207 'error:' in gitcmd.stdout and
2208 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002209 # Fallthru to sleep+retry logic at the bottom.
2210 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002211
Mike Frysinger6823bc22021-04-15 02:06:28 -04002212 # Try to prune remote branches once in case there are conflicts.
2213 # For example, if the remote had refs/heads/upstream, but deleted that and
2214 # now has refs/heads/upstream/foo.
2215 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002216 'error:' in gitcmd.stdout and
2217 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002218 not prune_tried):
2219 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002220 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002221 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002222 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002223 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002224 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002225 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002226 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002227 continue
Brian Harring14a66742012-09-28 20:21:57 -07002228 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002229 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2230 # in sha1 mode, we just tried sync'ing from the upstream field; it
2231 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002232 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002233 elif ret < 0:
2234 # Git died with a signal, exit immediately
2235 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002236
2237 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002238 if not verbose and gitcmd.stdout:
2239 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002240 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002241 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2242 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002243 time.sleep(retry_cur_sleep)
2244 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2245 MAXIMUM_RETRY_SLEEP_SEC)
2246 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2247 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002248
2249 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002250 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002251 if old_packed != '':
2252 _lwrite(packed_refs, old_packed)
2253 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002254 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002255 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002256
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002257 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002258 # We just synced the upstream given branch; verify we
2259 # got what we wanted, else trigger a second run of all
2260 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002261 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002262 # Sync the current branch only with depth set to None.
2263 # We always pass depth=None down to avoid infinite recursion.
2264 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002265 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002266 current_branch_only=current_branch_only and depth,
2267 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002268 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002269
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002270 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002271
Mike Frysingere50b6a72020-02-19 01:45:48 -05002272 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002273 if initial and \
2274 (self.manifest.manifestProject.config.GetString('repo.depth') or
2275 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002276 return False
2277
2278 remote = self.GetRemote(self.remote.name)
2279 bundle_url = remote.url + '/clone.bundle'
2280 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002281 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2282 'persistent-http',
2283 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002284 return False
2285
2286 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2287 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2288
2289 exist_dst = os.path.exists(bundle_dst)
2290 exist_tmp = os.path.exists(bundle_tmp)
2291
2292 if not initial and not exist_dst and not exist_tmp:
2293 return False
2294
2295 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002296 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2297 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002298 if not exist_dst:
2299 return False
2300
2301 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002302 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002303 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002304 if not quiet and sys.stdout.isatty():
2305 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002306 if not self.worktree:
2307 cmd.append('--update-head-ok')
2308 cmd.append(bundle_dst)
2309 for f in remote.fetch:
2310 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002311 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002312
2313 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Mike Frysinger9d96f582021-09-28 11:27:24 -04002314 platform_utils.remove(bundle_dst, missing_ok=True)
2315 platform_utils.remove(bundle_tmp, missing_ok=True)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002316 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002317
Mike Frysingere50b6a72020-02-19 01:45:48 -05002318 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002319 platform_utils.remove(dstPath, missing_ok=True)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002320
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002321 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002322 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002323 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002324 if os.path.exists(tmpPath):
2325 size = os.stat(tmpPath).st_size
2326 if size >= 1024:
2327 cmd += ['--continue-at', '%d' % (size,)]
2328 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002329 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002330 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002331 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002332 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002333 if proxy:
2334 cmd += ['--proxy', proxy]
2335 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2336 cmd += ['--proxy', os.environ['http_proxy']]
2337 if srcUrl.startswith('persistent-https'):
2338 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2339 elif srcUrl.startswith('persistent-http'):
2340 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002341 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002342
Dave Borowitz137d0132015-01-02 11:12:54 -08002343 if IsTrace():
2344 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002345 if verbose:
2346 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2347 stdout = None if verbose else subprocess.PIPE
2348 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002349 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002350 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002351 except OSError:
2352 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002353
Mike Frysingere50b6a72020-02-19 01:45:48 -05002354 (output, _) = proc.communicate()
2355 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002356
Dave Borowitz137d0132015-01-02 11:12:54 -08002357 if curlret == 22:
2358 # From curl man page:
2359 # 22: HTTP page not retrieved. The requested url was not found or
2360 # returned another error with the HTTP error code being 400 or above.
2361 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002362 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002363 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2364 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002365 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002366 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002367 elif curlret and not verbose and output:
2368 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002369
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002370 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002371 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002372 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002373 return True
2374 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002375 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002376 return False
2377 else:
2378 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002379
Kris Giesingc8d882a2014-12-23 13:02:32 -08002380 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002381 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002382 with open(path, 'rb') as f:
2383 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002384 return True
2385 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002386 if not quiet:
2387 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002388 return False
2389 except OSError:
2390 return False
2391
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002392 def _Checkout(self, rev, quiet=False):
2393 cmd = ['checkout']
2394 if quiet:
2395 cmd.append('-q')
2396 cmd.append(rev)
2397 cmd.append('--')
2398 if GitCommand(self, cmd).Wait() != 0:
2399 if self._allrefs:
2400 raise GitError('%s checkout %s ' % (self.name, rev))
2401
Mike Frysinger915fda12020-03-22 12:15:20 -04002402 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002403 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002404 if ffonly:
2405 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002406 if record_origin:
2407 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002408 cmd.append(rev)
2409 cmd.append('--')
2410 if GitCommand(self, cmd).Wait() != 0:
2411 if self._allrefs:
2412 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2413
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302414 def _LsRemote(self, refs):
2415 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302416 p = GitCommand(self, cmd, capture_stdout=True)
2417 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002418 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302419 return None
2420
Anthony King7bdac712014-07-16 12:56:40 +01002421 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002422 cmd = ['revert']
2423 cmd.append('--no-edit')
2424 cmd.append(rev)
2425 cmd.append('--')
2426 if GitCommand(self, cmd).Wait() != 0:
2427 if self._allrefs:
2428 raise GitError('%s revert %s ' % (self.name, rev))
2429
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002430 def _ResetHard(self, rev, quiet=True):
2431 cmd = ['reset', '--hard']
2432 if quiet:
2433 cmd.append('-q')
2434 cmd.append(rev)
2435 if GitCommand(self, cmd).Wait() != 0:
2436 raise GitError('%s reset --hard %s ' % (self.name, rev))
2437
Martin Kellye4e94d22017-03-21 16:05:12 -07002438 def _SyncSubmodules(self, quiet=True):
2439 cmd = ['submodule', 'update', '--init', '--recursive']
2440 if quiet:
2441 cmd.append('-q')
2442 if GitCommand(self, cmd).Wait() != 0:
LaMont Jones7b9b2512021-11-03 20:48:27 +00002443 raise GitError('%s submodule update --init --recursive ' % self.name)
Martin Kellye4e94d22017-03-21 16:05:12 -07002444
Anthony King7bdac712014-07-16 12:56:40 +01002445 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002446 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002447 if onto is not None:
2448 cmd.extend(['--onto', onto])
2449 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002450 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002451 raise GitError('%s rebase %s ' % (self.name, upstream))
2452
Pierre Tardy3d125942012-05-04 12:18:12 +02002453 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002454 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002455 if ffonly:
2456 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002457 if GitCommand(self, cmd).Wait() != 0:
2458 raise GitError('%s merge %s ' % (self.name, head))
2459
David Pursehousee8ace262020-02-13 12:41:15 +09002460 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002461 init_git_dir = not os.path.exists(self.gitdir)
2462 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002463 try:
2464 # Initialize the bare repository, which contains all of the objects.
2465 if init_obj_dir:
2466 os.makedirs(self.objdir)
2467 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002468
Mike Frysinger333c0a42021-11-15 12:39:00 -05002469 self._UpdateHooks(quiet=quiet)
2470
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002471 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002472 # Enable per-worktree config file support if possible. This is more a
2473 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002474 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002475 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002476
Kevin Degib1a07b82015-07-27 13:33:43 -06002477 # If we have a separate directory to hold refs, initialize it as well.
2478 if self.objdir != self.gitdir:
2479 if init_git_dir:
2480 os.makedirs(self.gitdir)
2481
2482 if init_obj_dir or init_git_dir:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002483 self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002484 try:
Mike Frysingerc72bd842021-11-14 03:58:00 -05002485 self._CheckDirReference(self.objdir, self.gitdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002486 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002487 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002488 print("Retrying clone after deleting %s" %
2489 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002490 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002491 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2492 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002493 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002494 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002495 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2496 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002497 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002498 raise e
2499 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002500
Kevin Degi384b3c52014-10-16 16:02:58 -06002501 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002502 mp = self.manifest.manifestProject
2503 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002504
Kevin Degib1a07b82015-07-27 13:33:43 -06002505 if ref_dir or mirror_git:
2506 if not mirror_git:
2507 mirror_git = os.path.join(ref_dir, self.name + '.git')
2508 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2509 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002510 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2511 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002512
Kevin Degib1a07b82015-07-27 13:33:43 -06002513 if os.path.exists(mirror_git):
2514 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002515 elif os.path.exists(repo_git):
2516 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002517 elif os.path.exists(worktrees_git):
2518 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002519 else:
2520 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002521
Kevin Degib1a07b82015-07-27 13:33:43 -06002522 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002523 if not os.path.isabs(ref_dir):
2524 # The alternate directory is relative to the object database.
2525 ref_dir = os.path.relpath(ref_dir,
2526 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002527 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2528 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002529
Kevin Degib1a07b82015-07-27 13:33:43 -06002530 m = self.manifest.manifestProject.config
2531 for key in ['user.name', 'user.email']:
2532 if m.Has(key, include_defaults=False):
2533 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002534 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002535 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002536 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002537 except Exception:
2538 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002539 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002540 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002541 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002542 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002543
David Pursehousee8ace262020-02-13 12:41:15 +09002544 def _UpdateHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002545 if os.path.exists(self.objdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002546 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002547
David Pursehousee8ace262020-02-13 12:41:15 +09002548 def _InitHooks(self, quiet=False):
Mike Frysinger333c0a42021-11-15 12:39:00 -05002549 hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002550 if not os.path.exists(hooks):
2551 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002552 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002553 name = os.path.basename(stock_hook)
2554
Victor Boivie65e0f352011-04-18 11:23:29 +02002555 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002556 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002557 # Don't install a Gerrit Code Review hook if this
2558 # project does not appear to use it for reviews.
2559 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002560 # Since the manifest project is one of those, but also
2561 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002562 continue
2563
2564 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002565 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002566 continue
2567 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002568 # If the files are the same, we'll leave it alone. We create symlinks
2569 # below by default but fallback to hardlinks if the OS blocks them.
2570 # So if we're here, it's probably because we made a hardlink below.
2571 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002572 if not quiet:
2573 _warn("%s: Not replacing locally modified %s hook",
2574 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002575 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002576 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002577 platform_utils.symlink(
2578 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002579 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002580 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002581 try:
2582 os.link(stock_hook, dst)
2583 except OSError:
2584 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002585 else:
2586 raise
2587
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002588 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002589 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002590 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002591 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002592 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002593 remote.review = self.remote.review
2594 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002595
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002596 if self.worktree:
2597 remote.ResetFetch(mirror=False)
2598 else:
2599 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002600 remote.Save()
2601
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002602 def _InitMRef(self):
2603 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002604 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002605 # Set up the m/ space to point to the worktree-specific ref space.
2606 # We'll update the worktree-specific ref space on each checkout.
2607 ref = R_M + self.manifest.branch
2608 if not self.bare_ref.symref(ref):
2609 self.bare_git.symbolic_ref(
2610 '-m', 'redirecting to worktree scope',
2611 ref, R_WORKTREE_M + self.manifest.branch)
2612
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002613 # We can't update this ref with git worktrees until it exists.
2614 # We'll wait until the initial checkout to set it.
2615 if not os.path.exists(self.worktree):
2616 return
2617
2618 base = R_WORKTREE_M
2619 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002620
2621 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002622 else:
2623 base = R_M
2624 active_git = self.bare_git
2625
2626 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002627
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002628 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002629 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002630
Remy Böhmer1469c282020-12-15 18:49:02 +01002631 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002632 cur = self.bare_ref.symref(ref)
2633
2634 if self.revisionId:
2635 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2636 msg = 'manifest set to %s' % self.revisionId
2637 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002638 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002639 else:
2640 remote = self.GetRemote(self.remote.name)
2641 dst = remote.ToLocal(self.revisionExpr)
2642 if cur != dst:
2643 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002644 if detach:
2645 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2646 else:
2647 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002648
Mike Frysingerc72bd842021-11-14 03:58:00 -05002649 def _CheckDirReference(self, srcdir, destdir):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002650 # Git worktrees don't use symlinks to share at all.
2651 if self.use_git_worktrees:
2652 return
2653
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002654 symlink_files = self.shareable_files[:]
2655 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002656 to_symlink = symlink_files + symlink_dirs
2657 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002658 # Try to self-heal a bit in simple cases.
2659 dst_path = os.path.join(destdir, name)
2660 src_path = os.path.join(srcdir, name)
2661
Mike Frysingered4f2112020-02-11 23:06:29 -05002662 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002663 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002664 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002665 # Fail if the links are pointing to the wrong place
2666 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002667 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002668 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002669 'work tree. If you\'re comfortable with the '
2670 'possibility of losing the work tree\'s git metadata,'
2671 ' use `repo sync --force-sync {0}` to '
2672 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002673
Mike Frysingerc72bd842021-11-14 03:58:00 -05002674 def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
David James8d201162013-10-11 17:03:19 -07002675 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2676
2677 Args:
2678 gitdir: The bare git repository. Must already be initialized.
2679 dotgit: The repository you would like to initialize.
David James8d201162013-10-11 17:03:19 -07002680 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2681 This saves you the effort of initializing |dotgit| yourself.
2682 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002683 symlink_files = self.shareable_files[:]
2684 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002685 to_symlink = symlink_files + symlink_dirs
2686
2687 to_copy = []
2688 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002689 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002690
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002691 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002692 for name in set(to_copy).union(to_symlink):
2693 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002694 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002695 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002696
Kevin Degi384b3c52014-10-16 16:02:58 -06002697 if os.path.lexists(dst):
2698 continue
David James8d201162013-10-11 17:03:19 -07002699
2700 # If the source dir doesn't exist, create an empty dir.
2701 if name in symlink_dirs and not os.path.lexists(src):
2702 os.makedirs(src)
2703
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002704 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002705 platform_utils.symlink(
2706 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002707 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002708 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002709 shutil.copytree(src, dst)
2710 elif os.path.isfile(src):
2711 shutil.copy(src, dst)
2712
Conley Owens80b87fe2014-05-09 17:13:44 -07002713 # If the source file doesn't exist, ensure the destination
2714 # file doesn't either.
2715 if name in symlink_files and not os.path.lexists(src):
Mike Frysinger9d96f582021-09-28 11:27:24 -04002716 platform_utils.remove(dst, missing_ok=True)
Conley Owens80b87fe2014-05-09 17:13:44 -07002717
David James8d201162013-10-11 17:03:19 -07002718 except OSError as e:
2719 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002720 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002721 else:
2722 raise
2723
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002724 def _InitGitWorktree(self):
2725 """Init the project using git worktrees."""
2726 self.bare_git.worktree('prune')
2727 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2728 self.worktree, self.GetRevisionId())
2729
2730 # Rewrite the internal state files to use relative paths between the
2731 # checkouts & worktrees.
2732 dotgit = os.path.join(self.worktree, '.git')
2733 with open(dotgit, 'r') as fp:
2734 # Figure out the checkout->worktree path.
2735 setting = fp.read()
2736 assert setting.startswith('gitdir:')
2737 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002738 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2739 # of file permissions. Delete it and recreate it from scratch to avoid.
2740 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002741 # Use relative path from checkout->worktree & maintain Unix line endings
2742 # on all OS's to match git behavior.
2743 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002744 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2745 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002746 # Use relative path from worktree->checkout & maintain Unix line endings
2747 # on all OS's to match git behavior.
2748 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002749 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2750
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002751 self._InitMRef()
2752
Martin Kellye4e94d22017-03-21 16:05:12 -07002753 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002754 """Setup the worktree .git path.
2755
2756 This is the user-visible path like src/foo/.git/.
2757
2758 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2759 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2760
2761 Older checkouts had .git/ directories. If we see that, migrate it.
2762
2763 This also handles changes in the manifest. Maybe this project was backed
2764 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2765 the path we point to under .repo/projects/ to match.
2766 """
2767 dotgit = os.path.join(self.worktree, '.git')
2768
2769 # If using an old layout style (a directory), migrate it.
2770 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2771 self._MigrateOldWorkTreeGitDir(dotgit)
2772
2773 init_dotgit = not os.path.exists(dotgit)
2774 if self.use_git_worktrees:
2775 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002776 self._InitGitWorktree()
2777 self._CopyAndLinkFiles()
Mike Frysingerf4545122019-11-11 04:34:16 -05002778 else:
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002779 if not init_dotgit:
2780 # See if the project has changed.
2781 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2782 platform_utils.remove(dotgit)
Mike Frysingerf4545122019-11-11 04:34:16 -05002783
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002784 if init_dotgit or not os.path.exists(dotgit):
2785 os.makedirs(self.worktree, exist_ok=True)
2786 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002787
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002788 if init_dotgit:
2789 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002790
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002791 # Finish checking out the worktree.
2792 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2793 if GitCommand(self, cmd).Wait() != 0:
2794 raise GitError('Cannot initialize work tree for ' + self.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002795
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002796 if submodules:
2797 self._SyncSubmodules(quiet=True)
2798 self._CopyAndLinkFiles()
Victor Boivie0960b5b2010-11-26 13:42:13 +01002799
Mike Frysinger2a089cf2021-11-13 23:29:42 -05002800 @classmethod
2801 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2802 """Migrate the old worktree .git/ dir style to a symlink.
2803
2804 This logic specifically only uses state from |dotgit| to figure out where to
2805 move content and not |self|. This way if the backing project also changed
2806 places, we only do the .git/ dir to .git symlink migration here. The path
2807 updates will happen independently.
2808 """
2809 # Figure out where in .repo/projects/ it's pointing to.
2810 if not os.path.islink(os.path.join(dotgit, 'refs')):
2811 raise GitError(f'{dotgit}: unsupported checkout state')
2812 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2813
2814 # Remove known symlink paths that exist in .repo/projects/.
2815 KNOWN_LINKS = {
2816 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2817 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2818 }
2819 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2820 SAFE_TO_CLOBBER = {
2821 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'index', 'ORIG_HEAD',
2822 }
2823
2824 # Now walk the paths and sync the .git/ to .repo/projects/.
2825 for name in platform_utils.listdir(dotgit):
2826 dotgit_path = os.path.join(dotgit, name)
2827 if name in KNOWN_LINKS:
2828 if platform_utils.islink(dotgit_path):
2829 platform_utils.remove(dotgit_path)
2830 else:
2831 raise GitError(f'{dotgit_path}: should be a symlink')
2832 else:
2833 gitdir_path = os.path.join(gitdir, name)
2834 if name in SAFE_TO_CLOBBER or not os.path.exists(gitdir_path):
2835 platform_utils.remove(gitdir_path, missing_ok=True)
2836 platform_utils.rename(dotgit_path, gitdir_path)
2837 else:
2838 raise GitError(f'{dotgit_path}: unknown file; please file a bug')
2839
2840 # Now that the dir should be empty, clear it out, and symlink it over.
2841 platform_utils.rmdir(dotgit)
2842 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002843
Renaud Paquay788e9622017-01-27 11:41:12 -08002844 def _get_symlink_error_message(self):
2845 if platform_utils.isWindows():
2846 return ('Unable to create symbolic link. Please re-run the command as '
2847 'Administrator, or see '
2848 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2849 'for other options.')
2850 return 'filesystem must support symlinks'
2851
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002852 def _revlist(self, *args, **kw):
2853 a = []
2854 a.extend(args)
2855 a.append('--')
2856 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002857
2858 @property
2859 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002860 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002861
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002862 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002863 """Get logs between two revisions of this project."""
2864 comp = '..'
2865 if rev1:
2866 revs = [rev1]
2867 if rev2:
2868 revs.extend([comp, rev2])
2869 cmd = ['log', ''.join(revs)]
2870 out = DiffColoring(self.config)
2871 if out.is_on and color:
2872 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002873 if pretty_format is not None:
2874 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002875 if oneline:
2876 cmd.append('--oneline')
2877
2878 try:
2879 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2880 if log.Wait() == 0:
2881 return log.stdout
2882 except GitError:
2883 # worktree may not exist if groups changed for example. In that case,
2884 # try in gitdir instead.
2885 if not os.path.exists(self.worktree):
2886 return self.bare_git.log(*cmd[1:])
2887 else:
2888 raise
2889 return None
2890
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002891 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2892 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002893 """Get the list of logs from this revision to given revisionId"""
2894 logs = {}
2895 selfId = self.GetRevisionId(self._allrefs)
2896 toId = toProject.GetRevisionId(toProject._allrefs)
2897
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002898 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2899 pretty_format=pretty_format)
2900 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2901 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002902 return logs
2903
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002904 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002905
David James8d201162013-10-11 17:03:19 -07002906 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002907 self._project = project
2908 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002909 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002910
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002911 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2912 def __getstate__(self):
2913 return (self._project, self._bare, self._gitdir)
2914
2915 def __setstate__(self, state):
2916 self._project, self._bare, self._gitdir = state
2917
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002918 def LsOthers(self):
2919 p = GitCommand(self._project,
2920 ['ls-files',
2921 '-z',
2922 '--others',
2923 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002924 bare=False,
David James8d201162013-10-11 17:03:19 -07002925 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002926 capture_stdout=True,
2927 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002928 if p.Wait() == 0:
2929 out = p.stdout
2930 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002931 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002932 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002933 return []
2934
2935 def DiffZ(self, name, *args):
2936 cmd = [name]
2937 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002938 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002939 cmd.extend(args)
2940 p = GitCommand(self._project,
2941 cmd,
David James8d201162013-10-11 17:03:19 -07002942 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002943 bare=False,
2944 capture_stdout=True,
2945 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002946 p.Wait()
2947 r = {}
2948 out = p.stdout
2949 if out:
2950 out = iter(out[:-1].split('\0'))
2951 while out:
2952 try:
2953 info = next(out)
2954 path = next(out)
2955 except StopIteration:
2956 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002957
Mike Frysinger84230002021-02-16 17:08:35 -05002958 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002959
Mike Frysinger84230002021-02-16 17:08:35 -05002960 def __init__(self, path, omode, nmode, oid, nid, state):
2961 self.path = path
2962 self.src_path = None
2963 self.old_mode = omode
2964 self.new_mode = nmode
2965 self.old_id = oid
2966 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002967
Mike Frysinger84230002021-02-16 17:08:35 -05002968 if len(state) == 1:
2969 self.status = state
2970 self.level = None
2971 else:
2972 self.status = state[:1]
2973 self.level = state[1:]
2974 while self.level.startswith('0'):
2975 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002976
Mike Frysinger84230002021-02-16 17:08:35 -05002977 info = info[1:].split(' ')
2978 info = _Info(path, *info)
2979 if info.status in ('R', 'C'):
2980 info.src_path = info.path
2981 info.path = next(out)
2982 r[info.path] = info
2983 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002984
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002985 def GetDotgitPath(self, subpath=None):
2986 """Return the full path to the .git dir.
2987
2988 As a convenience, append |subpath| if provided.
2989 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002990 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002991 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002992 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002993 dotgit = os.path.join(self._project.worktree, '.git')
2994 if os.path.isfile(dotgit):
2995 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
2996 with open(dotgit) as fp:
2997 setting = fp.read()
2998 assert setting.startswith('gitdir:')
2999 gitdir = setting.split(':', 1)[1].strip()
3000 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3001
3002 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3003
3004 def GetHead(self):
3005 """Return the ref that HEAD points to."""
3006 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003007 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003008 with open(path) as fd:
3009 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003010 except IOError as e:
3011 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003012 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303013 line = line.decode()
3014 except AttributeError:
3015 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003016 if line.startswith('ref: '):
3017 return line[5:-1]
3018 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003019
3020 def SetHead(self, ref, message=None):
3021 cmdv = []
3022 if message is not None:
3023 cmdv.extend(['-m', message])
3024 cmdv.append(HEAD)
3025 cmdv.append(ref)
3026 self.symbolic_ref(*cmdv)
3027
3028 def DetachHead(self, new, message=None):
3029 cmdv = ['--no-deref']
3030 if message is not None:
3031 cmdv.extend(['-m', message])
3032 cmdv.append(HEAD)
3033 cmdv.append(new)
3034 self.update_ref(*cmdv)
3035
3036 def UpdateRef(self, name, new, old=None,
3037 message=None,
3038 detach=False):
3039 cmdv = []
3040 if message is not None:
3041 cmdv.extend(['-m', message])
3042 if detach:
3043 cmdv.append('--no-deref')
3044 cmdv.append(name)
3045 cmdv.append(new)
3046 if old is not None:
3047 cmdv.append(old)
3048 self.update_ref(*cmdv)
3049
3050 def DeleteRef(self, name, old=None):
3051 if not old:
3052 old = self.rev_parse(name)
3053 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003054 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003055
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003056 def rev_list(self, *args, **kw):
3057 if 'format' in kw:
3058 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3059 else:
3060 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003061 cmdv.extend(args)
3062 p = GitCommand(self._project,
3063 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003064 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003065 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003066 capture_stdout=True,
3067 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003068 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003069 raise GitError('%s rev-list %s: %s' %
3070 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003071 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003072
3073 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003074 """Allow arbitrary git commands using pythonic syntax.
3075
3076 This allows you to do things like:
3077 git_obj.rev_parse('HEAD')
3078
3079 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3080 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003081 Any other positional arguments will be passed to the git command, and the
3082 following keyword arguments are supported:
3083 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003084
3085 Args:
3086 name: The name of the git command to call. Any '_' characters will
3087 be replaced with '-'.
3088
3089 Returns:
3090 A callable object that will try to call git with the named command.
3091 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003092 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003093
Dave Borowitz091f8932012-10-23 17:01:04 -07003094 def runner(*args, **kwargs):
3095 cmdv = []
3096 config = kwargs.pop('config', None)
3097 for k in kwargs:
3098 raise TypeError('%s() got an unexpected keyword argument %r'
3099 % (name, k))
3100 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303101 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003102 cmdv.append('-c')
3103 cmdv.append('%s=%s' % (k, v))
3104 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003105 cmdv.extend(args)
3106 p = GitCommand(self._project,
3107 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003108 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003109 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003110 capture_stdout=True,
3111 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003112 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003113 raise GitError('%s %s: %s' %
3114 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003115 r = p.stdout
3116 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3117 return r[:-1]
3118 return r
3119 return runner
3120
3121
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003122class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003123
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003124 def __str__(self):
3125 return 'prior sync failed; rebase still in progress'
3126
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003127
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003128class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003129
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003130 def __str__(self):
3131 return 'contains uncommitted changes'
3132
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003133
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003134class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003135
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003136 def __init__(self, project, text):
3137 self.project = project
3138 self.text = text
3139
3140 def Print(self, syncbuf):
3141 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3142 syncbuf.out.nl()
3143
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003144
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003145class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003146
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003147 def __init__(self, project, why):
3148 self.project = project
3149 self.why = why
3150
3151 def Print(self, syncbuf):
3152 syncbuf.out.fail('error: %s/: %s',
3153 self.project.relpath,
3154 str(self.why))
3155 syncbuf.out.nl()
3156
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003157
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003158class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003159
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003160 def __init__(self, project, action):
3161 self.project = project
3162 self.action = action
3163
3164 def Run(self, syncbuf):
3165 out = syncbuf.out
3166 out.project('project %s/', self.project.relpath)
3167 out.nl()
3168 try:
3169 self.action()
3170 out.nl()
3171 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003172 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003173 out.nl()
3174 return False
3175
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003176
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003177class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003178
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003179 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003180 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003181 self.project = self.printer('header', attr='bold')
3182 self.info = self.printer('info')
3183 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003184
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003185
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003186class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003187
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003188 def __init__(self, config, detach_head=False):
3189 self._messages = []
3190 self._failures = []
3191 self._later_queue1 = []
3192 self._later_queue2 = []
3193
3194 self.out = _SyncColoring(config)
3195 self.out.redirect(sys.stderr)
3196
3197 self.detach_head = detach_head
3198 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003199 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003200
3201 def info(self, project, fmt, *args):
3202 self._messages.append(_InfoMessage(project, fmt % args))
3203
3204 def fail(self, project, err=None):
3205 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003206 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003207
3208 def later1(self, project, what):
3209 self._later_queue1.append(_Later(project, what))
3210
3211 def later2(self, project, what):
3212 self._later_queue2.append(_Later(project, what))
3213
3214 def Finish(self):
3215 self._PrintMessages()
3216 self._RunLater()
3217 self._PrintMessages()
3218 return self.clean
3219
David Rileye0684ad2017-04-05 00:02:59 -07003220 def Recently(self):
3221 recent_clean = self.recent_clean
3222 self.recent_clean = True
3223 return recent_clean
3224
3225 def _MarkUnclean(self):
3226 self.clean = False
3227 self.recent_clean = False
3228
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003229 def _RunLater(self):
3230 for q in ['_later_queue1', '_later_queue2']:
3231 if not self._RunQueue(q):
3232 return
3233
3234 def _RunQueue(self, queue):
3235 for m in getattr(self, queue):
3236 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003237 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003238 return False
3239 setattr(self, queue, [])
3240 return True
3241
3242 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003243 if self._messages or self._failures:
3244 if os.isatty(2):
3245 self.out.write(progress.CSI_ERASE_LINE)
3246 self.out.write('\r')
3247
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003248 for m in self._messages:
3249 m.Print(self)
3250 for m in self._failures:
3251 m.Print(self)
3252
3253 self._messages = []
3254 self._failures = []
3255
3256
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003257class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003258
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003259 """A special project housed under .repo.
3260 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003261
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003262 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003263 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003264 manifest=manifest,
3265 name=name,
3266 gitdir=gitdir,
3267 objdir=gitdir,
3268 worktree=worktree,
3269 remote=RemoteSpec('origin'),
3270 relpath='.repo/%s' % name,
3271 revisionExpr='refs/heads/master',
3272 revisionId=None,
3273 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003274
3275 def PreSync(self):
3276 if self.Exists:
3277 cb = self.CurrentBranch
3278 if cb:
3279 base = self.GetBranch(cb).merge
3280 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003281 self.revisionExpr = base
3282 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003283
Martin Kelly224a31a2017-07-10 14:46:25 -07003284 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003285 """ Prepare MetaProject for manifest branch switch
3286 """
3287
3288 # detach and delete manifest branch, allowing a new
3289 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003290 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003291 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003292 syncbuf.Finish()
3293
3294 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003295 ['update-ref', '-d', 'refs/heads/default'],
3296 capture_stdout=True,
3297 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003298
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003299 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003300 def LastFetch(self):
3301 try:
3302 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3303 return os.path.getmtime(fh)
3304 except OSError:
3305 return 0
3306
3307 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003308 def HasChanges(self):
3309 """Has the remote received new commits not yet checked out?
3310 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003311 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003312 return False
3313
David Pursehouse8a68ff92012-09-24 12:15:13 +09003314 all_refs = self.bare_ref.all
3315 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003316 head = self.work_git.GetHead()
3317 if head.startswith(R_HEADS):
3318 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003319 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003320 except KeyError:
3321 head = None
3322
3323 if revid == head:
3324 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003325 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003326 return True
3327 return False