blob: e777dbd2bd2c4119cf754552e413023e53ac9545 [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
Anthony King7bdac712014-07-16 12:56:40 +0100254class _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
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700261
Mike Frysingere6a202f2019-08-02 15:57:57 -0400262def _SafeExpandPath(base, subpath, skipfinal=False):
263 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700264
Mike Frysingere6a202f2019-08-02 15:57:57 -0400265 We make sure no intermediate symlinks are traversed, and that the final path
266 is not a special file (e.g. not a socket or fifo).
267
268 NB: We rely on a number of paths already being filtered out while parsing the
269 manifest. See the validation logic in manifest_xml.py for more details.
270 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500271 # Split up the path by its components. We can't use os.path.sep exclusively
272 # as some platforms (like Windows) will convert / to \ and that bypasses all
273 # our constructed logic here. Especially since manifest authors only use
274 # / in their paths.
275 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
276 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400277 if skipfinal:
278 # Whether the caller handles the final component itself.
279 finalpart = components.pop()
280
281 path = base
282 for part in components:
283 if part in {'.', '..'}:
284 raise ManifestInvalidPathError(
285 '%s: "%s" not allowed in paths' % (subpath, part))
286
287 path = os.path.join(path, part)
288 if platform_utils.islink(path):
289 raise ManifestInvalidPathError(
290 '%s: traversing symlinks not allow' % (path,))
291
292 if os.path.exists(path):
293 if not os.path.isfile(path) and not platform_utils.isdir(path):
294 raise ManifestInvalidPathError(
295 '%s: only regular files & directories allowed' % (path,))
296
297 if skipfinal:
298 path = os.path.join(path, finalpart)
299
300 return path
301
302
303class _CopyFile(object):
304 """Container for <copyfile> manifest element."""
305
306 def __init__(self, git_worktree, src, topdir, dest):
307 """Register a <copyfile> request.
308
309 Args:
310 git_worktree: Absolute path to the git project checkout.
311 src: Relative path under |git_worktree| of file to read.
312 topdir: Absolute path to the top of the repo client checkout.
313 dest: Relative path under |topdir| of file to write.
314 """
315 self.git_worktree = git_worktree
316 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700317 self.src = src
318 self.dest = dest
319
320 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400321 src = _SafeExpandPath(self.git_worktree, self.src)
322 dest = _SafeExpandPath(self.topdir, self.dest)
323
324 if platform_utils.isdir(src):
325 raise ManifestInvalidPathError(
326 '%s: copying from directory not supported' % (self.src,))
327 if platform_utils.isdir(dest):
328 raise ManifestInvalidPathError(
329 '%s: copying to directory not allowed' % (self.dest,))
330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700331 # copy file if it does not exist or is out of date
332 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
333 try:
334 # remove existing file first, since it might be read-only
335 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800336 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400337 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200338 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700339 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200340 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700341 shutil.copy(src, dest)
342 # make the file read-only
343 mode = os.stat(dest)[stat.ST_MODE]
344 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
345 os.chmod(dest, mode)
346 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700347 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700349
Anthony King7bdac712014-07-16 12:56:40 +0100350class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400351 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700352
Mike Frysingere6a202f2019-08-02 15:57:57 -0400353 def __init__(self, git_worktree, src, topdir, dest):
354 """Register a <linkfile> request.
355
356 Args:
357 git_worktree: Absolute path to the git project checkout.
358 src: Target of symlink relative to path under |git_worktree|.
359 topdir: Absolute path to the top of the repo client checkout.
360 dest: Relative path under |topdir| of symlink to create.
361 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700362 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400363 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500364 self.src = src
365 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500366
Wink Saville4c426ef2015-06-03 08:05:17 -0700367 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500368 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700369 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500370 try:
371 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800372 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800373 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500374 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700375 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700376 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500377 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700378 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500379 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700380 _error('Cannot link file %s to %s', relSrc, absDest)
381
382 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400383 """Link the self.src & self.dest paths.
384
385 Handles wild cards on the src linking all of the files in the source in to
386 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700387 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500388 # Some people use src="." to create stable links to projects. Lets allow
389 # that but reject all other uses of "." to keep things simple.
390 if self.src == '.':
391 src = self.git_worktree
392 else:
393 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400394
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300395 if not glob.has_magic(src):
396 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400397 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
398 # dest & src are absolute paths at this point. Make sure the target of
399 # the symlink is relative in the context of the repo client checkout.
400 relpath = os.path.relpath(src, os.path.dirname(dest))
401 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700402 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400403 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300404 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400405 if os.path.exists(dest) and not platform_utils.isdir(dest):
406 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700407 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400408 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700409 # Create a releative path from source dir to destination dir
410 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400411 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700412
413 # Get the source file name
414 srcFile = os.path.basename(absSrcFile)
415
416 # Now form the final full paths to srcFile. They will be
417 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400418 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700419 relSrc = os.path.join(relSrcDir, srcFile)
420 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500421
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700422
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700423class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700424
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700425 def __init__(self,
426 name,
Anthony King7bdac712014-07-16 12:56:40 +0100427 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700428 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100429 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700430 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700431 orig_name=None,
432 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700433 self.name = name
434 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700435 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700436 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100437 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700438 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700439 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700440
Ian Kasprzak0286e312021-02-05 10:06:18 -0800441
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700442class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600443 # These objects can be shared between several working trees.
444 shareable_files = ['description', 'info']
445 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
446 # These objects can only be used by a single working tree.
447 working_tree_files = ['config', 'packed-refs', 'shallow']
448 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700449
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700450 def __init__(self,
451 manifest,
452 name,
453 remote,
454 gitdir,
David James8d201162013-10-11 17:03:19 -0700455 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700456 worktree,
457 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700458 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800459 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100460 rebase=True,
461 groups=None,
462 sync_c=False,
463 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900464 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100465 clone_depth=None,
466 upstream=None,
467 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500468 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100469 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900470 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700471 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600472 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700473 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800474 """Init a Project object.
475
476 Args:
477 manifest: The XmlManifest object.
478 name: The `name` attribute of manifest.xml's project element.
479 remote: RemoteSpec object specifying its remote's properties.
480 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700481 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800482 worktree: Absolute path of git working tree.
483 relpath: Relative path of git working tree to repo's top directory.
484 revisionExpr: The `revision` attribute of manifest.xml's project element.
485 revisionId: git commit id for checking out.
486 rebase: The `rebase` attribute of manifest.xml's project element.
487 groups: The `groups` attribute of manifest.xml's project element.
488 sync_c: The `sync-c` attribute of manifest.xml's project element.
489 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900490 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800491 upstream: The `upstream` attribute of manifest.xml's project element.
492 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500493 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800494 is_derived: False if the project was explicitly defined in the manifest;
495 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400496 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900497 optimized_fetch: If True, when a project is set to a sha1 revision, only
498 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600499 retry_fetches: Retry remote fetches n times upon receiving transient error
500 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700501 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800502 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400503 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700504 self.name = name
505 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800506 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700507 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800508 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700509 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800510 else:
511 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700513 self.revisionExpr = revisionExpr
514
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700515 if revisionId is None \
516 and revisionExpr \
517 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700518 self.revisionId = revisionExpr
519 else:
520 self.revisionId = revisionId
521
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 = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700543 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400544 defaults=self.client.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700545
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800546 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700547 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800548 else:
549 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700550 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700551 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700552 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400553 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700554 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700555
Doug Anderson37282b42011-03-04 11:54:18 -0800556 # This will be filled in if a project is later identified to be the
557 # project containing repo hooks.
558 self.enabled_repo_hooks = []
559
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700560 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800561 def Derived(self):
562 return self.is_derived
563
564 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700565 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700566 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567
568 @property
569 def CurrentBranch(self):
570 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400571
572 The branch name omits the 'refs/heads/' prefix.
573 None is returned if the project is on a detached HEAD, or if the work_git is
574 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700575 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400576 try:
577 b = self.work_git.GetHead()
578 except NoManifestException:
579 # If the local checkout is in a bad state, don't barf. Let the callers
580 # process this like the head is unreadable.
581 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700582 if b.startswith(R_HEADS):
583 return b[len(R_HEADS):]
584 return None
585
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700586 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500587 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
588 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
589 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200590
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700591 def IsDirty(self, consider_untracked=True):
592 """Is the working directory modified in some way?
593 """
594 self.work_git.update_index('-q',
595 '--unmerged',
596 '--ignore-missing',
597 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900598 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700599 return True
600 if self.work_git.DiffZ('diff-files'):
601 return True
602 if consider_untracked and self.work_git.LsOthers():
603 return True
604 return False
605
606 _userident_name = None
607 _userident_email = None
608
609 @property
610 def UserName(self):
611 """Obtain the user's personal name.
612 """
613 if self._userident_name is None:
614 self._LoadUserIdentity()
615 return self._userident_name
616
617 @property
618 def UserEmail(self):
619 """Obtain the user's email address. This is very likely
620 to be their Gerrit login.
621 """
622 if self._userident_email is None:
623 self._LoadUserIdentity()
624 return self._userident_email
625
626 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900627 u = self.bare_git.var('GIT_COMMITTER_IDENT')
628 m = re.compile("^(.*) <([^>]*)> ").match(u)
629 if m:
630 self._userident_name = m.group(1)
631 self._userident_email = m.group(2)
632 else:
633 self._userident_name = ''
634 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635
636 def GetRemote(self, name):
637 """Get the configuration for a single remote.
638 """
639 return self.config.GetRemote(name)
640
641 def GetBranch(self, name):
642 """Get the configuration for a single branch.
643 """
644 return self.config.GetBranch(name)
645
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700646 def GetBranches(self):
647 """Get all existing local branches.
648 """
649 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900650 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700651 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700652
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530653 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700654 if name.startswith(R_HEADS):
655 name = name[len(R_HEADS):]
656 b = self.GetBranch(name)
657 b.current = name == current
658 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900659 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700660 heads[name] = b
661
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530662 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700663 if name.startswith(R_PUB):
664 name = name[len(R_PUB):]
665 b = heads.get(name)
666 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900667 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700668
669 return heads
670
Colin Cross5acde752012-03-28 20:15:45 -0700671 def MatchesGroups(self, manifest_groups):
672 """Returns true if the manifest groups specified at init should cause
673 this project to be synced.
674 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700675 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700676
677 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700678 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700679 manifest_groups: "-group1,group2"
680 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500681
682 The special manifest group "default" will match any project that
683 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700684 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500685 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700686 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700687 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500688 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700689
Conley Owens971de8e2012-04-16 10:36:08 -0700690 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700691 for group in expanded_manifest_groups:
692 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700693 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700694 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700695 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700696
Conley Owens971de8e2012-04-16 10:36:08 -0700697 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700698
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700699# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700700 def UncommitedFiles(self, get_all=True):
701 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700703 Args:
704 get_all: a boolean, if True - get information about all different
705 uncommitted files. If False - return as soon as any kind of
706 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500707 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700708 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500709 self.work_git.update_index('-q',
710 '--unmerged',
711 '--ignore-missing',
712 '--refresh')
713 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700714 details.append("rebase in progress")
715 if not get_all:
716 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500717
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700718 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
719 if changes:
720 details.extend(changes)
721 if not get_all:
722 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500723
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700724 changes = self.work_git.DiffZ('diff-files').keys()
725 if changes:
726 details.extend(changes)
727 if not get_all:
728 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500729
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700730 changes = self.work_git.LsOthers()
731 if changes:
732 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500733
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700734 return details
735
736 def HasChanges(self):
737 """Returns true if there are uncommitted changes.
738 """
739 if self.UncommitedFiles(get_all=False):
740 return True
741 else:
742 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500743
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600744 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700745 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200746
747 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200748 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600749 quiet: If True then only print the project name. Do not print
750 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700752 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700753 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200754 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700755 print(file=output_redir)
756 print('project %s/' % self.relpath, file=output_redir)
757 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700758 return
759
760 self.work_git.update_index('-q',
761 '--unmerged',
762 '--ignore-missing',
763 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700764 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700765 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
766 df = self.work_git.DiffZ('diff-files')
767 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100768 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700769 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770
771 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700772 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200773 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700774 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600776 if quiet:
777 out.nl()
778 return 'DIRTY'
779
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780 branch = self.CurrentBranch
781 if branch is None:
782 out.nobranch('(*** NO BRANCH ***)')
783 else:
784 out.branch('branch %s', branch)
785 out.nl()
786
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700787 if rb:
788 out.important('prior sync failed; rebase still in progress')
789 out.nl()
790
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791 paths = list()
792 paths.extend(di.keys())
793 paths.extend(df.keys())
794 paths.extend(do)
795
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530796 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900797 try:
798 i = di[p]
799 except KeyError:
800 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700801
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900802 try:
803 f = df[p]
804 except KeyError:
805 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200806
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900807 if i:
808 i_status = i.status.upper()
809 else:
810 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900812 if f:
813 f_status = f.status.lower()
814 else:
815 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816
817 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800818 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700819 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700820 else:
821 line = ' %s%s\t%s' % (i_status, f_status, p)
822
823 if i and not f:
824 out.added('%s', line)
825 elif (i and f) or (not i and f):
826 out.changed('%s', line)
827 elif not i and not f:
828 out.untracked('%s', line)
829 else:
830 out.write('%s', line)
831 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200832
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700833 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500835 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 """Prints the status of the repository to stdout.
837 """
838 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500839 if output_redir:
840 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841 cmd = ['diff']
842 if out.is_on:
843 cmd.append('--color')
844 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300845 if absolute_paths:
846 cmd.append('--src-prefix=a/%s/' % self.relpath)
847 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400849 try:
850 p = GitCommand(self,
851 cmd,
852 capture_stdout=True,
853 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500854 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400855 except GitError as e:
856 out.nl()
857 out.project('project %s/' % self.relpath)
858 out.nl()
859 out.fail('%s', str(e))
860 out.nl()
861 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500862 if p.stdout:
863 out.nl()
864 out.project('project %s/' % self.relpath)
865 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500866 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400867 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700869# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900870 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871 """Was the branch published (uploaded) for code review?
872 If so, returns the SHA-1 hash of the last published
873 state for the branch.
874 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700875 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900876 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700877 try:
878 return self.bare_git.rev_parse(key)
879 except GitError:
880 return None
881 else:
882 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900883 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700884 except KeyError:
885 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886
David Pursehouse8a68ff92012-09-24 12:15:13 +0900887 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888 """Prunes any stale published refs.
889 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900890 if all_refs is None:
891 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892 heads = set()
893 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530894 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700895 if name.startswith(R_HEADS):
896 heads.add(name)
897 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900898 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530900 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901 n = name[len(R_PUB):]
902 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900903 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700905 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906 """List any branches which can be uploaded for review.
907 """
908 heads = {}
909 pubed = {}
910
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530911 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700912 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900913 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900915 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916
917 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530918 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900919 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700921 if selected_branch and branch != selected_branch:
922 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800924 rb = self.GetUploadableBranch(branch)
925 if rb:
926 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 return ready
928
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800929 def GetUploadableBranch(self, branch_name):
930 """Get a single uploadable branch, or None.
931 """
932 branch = self.GetBranch(branch_name)
933 base = branch.LocalMerge
934 if branch.LocalMerge:
935 rb = ReviewableBranch(self, branch, base)
936 if rb.commits:
937 return rb
938 return None
939
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700940 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100941 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500942 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700943 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500944 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500945 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200946 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700947 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200948 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200949 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800950 validate_certs=True,
951 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 """Uploads the named branch for code review.
953 """
954 if branch is None:
955 branch = self.CurrentBranch
956 if branch is None:
957 raise GitError('not currently on a branch')
958
959 branch = self.GetBranch(branch)
960 if not branch.LocalMerge:
961 raise GitError('branch %s does not track a remote' % branch.name)
962 if not branch.remote.review:
963 raise GitError('remote %s has no review url' % branch.remote.name)
964
Bryan Jacobsf609f912013-05-06 13:36:24 -0400965 if dest_branch is None:
966 dest_branch = self.dest_branch
967 if dest_branch is None:
968 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 if not dest_branch.startswith(R_HEADS):
970 dest_branch = R_HEADS + dest_branch
971
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800972 if not branch.remote.projectname:
973 branch.remote.projectname = self.name
974 branch.remote.Save()
975
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200976 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800977 if url is None:
978 raise UploadError('review not configured')
979 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -0500980 if dryrun:
981 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800982
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800983 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -0800984 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700985
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800986 for push_option in (push_options or []):
987 cmd.append('-o')
988 cmd.append(push_option)
989
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800990 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800991
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800992 if dest_branch.startswith(R_HEADS):
993 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700994
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -0500995 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -0800996 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800997 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -0800998 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -0500999 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001000 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001001
David Pursehousef25a3702018-11-14 19:01:22 -08001002 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001003 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001004 if notify:
1005 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001006 if private:
1007 opts += ['private']
1008 if wip:
1009 opts += ['wip']
1010 if opts:
1011 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001012 cmd.append(ref_spec)
1013
Anthony King7bdac712014-07-16 12:56:40 +01001014 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001015 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016
Mike Frysingerd7f86832020-11-19 19:18:46 -05001017 if not dryrun:
1018 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1019 self.bare_git.UpdateRef(R_PUB + branch.name,
1020 R_HEADS + branch.name,
1021 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001023# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001024 def _ExtractArchive(self, tarpath, path=None):
1025 """Extract the given tar on its current location
1026
1027 Args:
1028 - tarpath: The path to the actual tar file
1029
1030 """
1031 try:
1032 with tarfile.open(tarpath, 'r') as tar:
1033 tar.extractall(path=path)
1034 return True
1035 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001036 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001037 return False
1038
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001039 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001040 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001041 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001042 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001043 is_new=None,
Mike Frysinger73561142021-05-03 01:10:09 -04001044 current_branch_only=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001045 force_sync=False,
1046 clone_bundle=True,
Mike Frysingerd68ed632021-05-03 01:21:35 -04001047 tags=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001048 archive=False,
1049 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001050 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001051 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001052 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001053 ssh_proxy=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001054 clone_filter=None,
Raman Tenneticd89ec12021-04-22 09:18:14 -07001055 partial_clone_exclude=set()):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 """Perform only the network IO portion of the sync process.
1057 Local working directory/branch state is not affected.
1058 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001059 if archive and not isinstance(self, MetaProject):
1060 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001061 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001062 return False
1063
1064 name = self.relpath.replace('\\', '/')
1065 name = name.replace('/', '_')
1066 tarpath = '%s.tar' % name
1067 topdir = self.manifest.topdir
1068
1069 try:
1070 self._FetchArchive(tarpath, cwd=topdir)
1071 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001072 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001073 return False
1074
1075 # From now on, we only need absolute tarpath
1076 tarpath = os.path.join(topdir, tarpath)
1077
1078 if not self._ExtractArchive(tarpath, path=topdir):
1079 return False
1080 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001081 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001082 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001083 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001084 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001085 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001086
1087 # If the shared object dir already exists, don't try to rebootstrap with a
1088 # clone bundle download. We should have the majority of objects already.
1089 if clone_bundle and os.path.exists(self.objdir):
1090 clone_bundle = False
1091
Raman Tennetif32f2432021-04-12 20:57:25 -07001092 if self.name in partial_clone_exclude:
1093 clone_bundle = True
1094 clone_filter = None
1095
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001096 if is_new is None:
1097 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001098 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001099 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001100 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001101 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001103
1104 if is_new:
1105 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1106 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001107 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001108 # This works for both absolute and relative alternate directories.
1109 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001110 except IOError:
1111 alt_dir = None
1112 else:
1113 alt_dir = None
1114
Mike Frysingere50b6a72020-02-19 01:45:48 -05001115 if (clone_bundle
1116 and alt_dir is None
1117 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001118 is_new = False
1119
Mike Frysinger73561142021-05-03 01:10:09 -04001120 if current_branch_only is None:
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001121 if self.sync_c:
1122 current_branch_only = True
1123 elif not self.manifest._loaded:
1124 # Manifest cannot check defaults until it syncs.
1125 current_branch_only = False
1126 elif self.manifest.default.sync_c:
1127 current_branch_only = True
1128
Mike Frysingerd68ed632021-05-03 01:21:35 -04001129 if tags is None:
1130 tags = self.sync_tags
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001131
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001132 if self.clone_depth:
1133 depth = self.clone_depth
1134 else:
1135 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1136
Mike Frysinger521d01b2020-02-17 01:51:49 -05001137 # See if we can skip the network fetch entirely.
1138 if not (optimized_fetch and
1139 (ID_RE.match(self.revisionExpr) and
1140 self._CheckForImmutableRevision())):
1141 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001142 initial=is_new,
1143 quiet=quiet, verbose=verbose, output_redir=output_redir,
1144 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001145 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001146 submodules=submodules, force_sync=force_sync,
Mike Frysinger19e409c2021-05-05 19:44:35 -04001147 ssh_proxy=ssh_proxy,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001148 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001149 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001150
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001151 mp = self.manifest.manifestProject
1152 dissociate = mp.config.GetBoolean('repo.dissociate')
1153 if dissociate:
1154 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1155 if os.path.exists(alternates_file):
1156 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001157 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1158 merge_output=bool(output_redir))
1159 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001160 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001161 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001162 return False
1163 platform_utils.remove(alternates_file)
1164
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001165 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001166 self._InitMRef()
1167 else:
1168 self._InitMirrorHead()
1169 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001170 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001171 except OSError:
1172 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001173 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001174
1175 def PostRepoUpgrade(self):
1176 self._InitHooks()
1177
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001178 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001179 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001180 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001181 for copyfile in self.copyfiles:
1182 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001183 for linkfile in self.linkfiles:
1184 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001185
Julien Camperguedd654222014-01-09 16:21:37 +01001186 def GetCommitRevisionId(self):
1187 """Get revisionId of a commit.
1188
1189 Use this method instead of GetRevisionId to get the id of the commit rather
1190 than the id of the current git object (for example, a tag)
1191
1192 """
1193 if not self.revisionExpr.startswith(R_TAGS):
1194 return self.GetRevisionId(self._allrefs)
1195
1196 try:
1197 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1198 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001199 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1200 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001201
David Pursehouse8a68ff92012-09-24 12:15:13 +09001202 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001203 if self.revisionId:
1204 return self.revisionId
1205
1206 rem = self.GetRemote(self.remote.name)
1207 rev = rem.ToLocal(self.revisionExpr)
1208
David Pursehouse8a68ff92012-09-24 12:15:13 +09001209 if all_refs is not None and rev in all_refs:
1210 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001211
1212 try:
1213 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1214 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001215 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1216 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001217
Raman Tenneti6a872c92021-01-14 19:17:50 -08001218 def SetRevisionId(self, revisionId):
Xin Li0e776a52021-06-29 21:42:34 +00001219 if self.revisionExpr:
Xin Liaabf79d2021-04-29 01:50:38 -07001220 self.upstream = self.revisionExpr
1221
Raman Tenneti6a872c92021-01-14 19:17:50 -08001222 self.revisionId = revisionId
1223
Martin Kellye4e94d22017-03-21 16:05:12 -07001224 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001225 """Perform only the local IO portion of the sync process.
1226 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001228 if not os.path.exists(self.gitdir):
1229 syncbuf.fail(self,
1230 'Cannot checkout %s due to missing network sync; Run '
1231 '`repo sync -n %s` first.' %
1232 (self.name, self.name))
1233 return
1234
Martin Kellye4e94d22017-03-21 16:05:12 -07001235 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001236 all_refs = self.bare_ref.all
1237 self.CleanPublishedCache(all_refs)
1238 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001239
Mike Frysinger0458faa2021-03-10 23:35:44 -05001240 # Special case the root of the repo client checkout. Make sure it doesn't
1241 # contain files being checked out to dirs we don't allow.
1242 if self.relpath == '.':
1243 PROTECTED_PATHS = {'.repo'}
1244 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1245 bad_paths = paths & PROTECTED_PATHS
1246 if bad_paths:
1247 syncbuf.fail(self,
1248 'Refusing to checkout project that writes to protected '
1249 'paths: %s' % (', '.join(bad_paths),))
1250 return
1251
David Pursehouse1d947b32012-10-25 12:23:11 +09001252 def _doff():
1253 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001254 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001255
Martin Kellye4e94d22017-03-21 16:05:12 -07001256 def _dosubmodules():
1257 self._SyncSubmodules(quiet=True)
1258
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001259 head = self.work_git.GetHead()
1260 if head.startswith(R_HEADS):
1261 branch = head[len(R_HEADS):]
1262 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001263 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001264 except KeyError:
1265 head = None
1266 else:
1267 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001268
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001269 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001270 # Currently on a detached HEAD. The user is assumed to
1271 # not have any local modifications worth worrying about.
1272 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001273 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001274 syncbuf.fail(self, _PriorSyncFailedError())
1275 return
1276
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001277 if head == revid:
1278 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001279 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001280 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001281 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001282 # The copy/linkfile config may have changed.
1283 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001284 return
1285 else:
1286 lost = self._revlist(not_rev(revid), HEAD)
1287 if lost:
1288 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001289
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001290 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001291 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001292 if submodules:
1293 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001294 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001295 syncbuf.fail(self, e)
1296 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001297 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001298 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001300 if head == revid:
1301 # No changes; don't do anything further.
1302 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001303 # The copy/linkfile config may have changed.
1304 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001305 return
1306
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001309 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001311 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001312 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001313 syncbuf.info(self,
1314 "leaving %s; does not track upstream",
1315 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001317 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001318 if submodules:
1319 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001320 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001321 syncbuf.fail(self, e)
1322 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001323 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001324 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001325
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001326 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001327
1328 # See if we can perform a fast forward merge. This can happen if our
1329 # branch isn't in the exact same state as we last published.
1330 try:
1331 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1332 # Skip the published logic.
1333 pub = False
1334 except GitError:
1335 pub = self.WasPublished(branch.name, all_refs)
1336
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001338 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001339 if not_merged:
1340 if upstream_gain:
1341 # The user has published this branch and some of those
1342 # commits are not yet merged upstream. We do not want
1343 # to rewrite the published commits so we punt.
1344 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001345 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001346 "branch %s is published (but not merged) and is now "
1347 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001348 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001349 elif pub == head:
1350 # All published commits are merged, and thus we are a
1351 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001352 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001353 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001354 if submodules:
1355 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001356 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001358 # Examine the local commits not in the remote. Find the
1359 # last one attributed to this user, if any.
1360 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001361 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001362 last_mine = None
1363 cnt_mine = 0
1364 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001365 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001366 if committer_email == self.UserEmail:
1367 last_mine = commit_id
1368 cnt_mine += 1
1369
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001370 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001371 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001372
1373 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001374 syncbuf.fail(self, _DirtyError())
1375 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001377 # If the upstream switched on us, warn the user.
1378 #
1379 if branch.merge != self.revisionExpr:
1380 if branch.merge and self.revisionExpr:
1381 syncbuf.info(self,
1382 'manifest switched %s...%s',
1383 branch.merge,
1384 self.revisionExpr)
1385 elif branch.merge:
1386 syncbuf.info(self,
1387 'manifest no longer tracks %s',
1388 branch.merge)
1389
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001390 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001392 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001393 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001394 syncbuf.info(self,
1395 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001396 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001397
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001398 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001399 if not ID_RE.match(self.revisionExpr):
1400 # in case of manifest sync the revisionExpr might be a SHA1
1401 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001402 if not branch.merge.startswith('refs/'):
1403 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001404 branch.Save()
1405
Mike Pontillod3153822012-02-28 11:53:24 -08001406 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001407 def _docopyandlink():
1408 self._CopyAndLinkFiles()
1409
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001410 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001411 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001412 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001413 if submodules:
1414 syncbuf.later2(self, _dosubmodules)
1415 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001416 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001417 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001418 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001419 if submodules:
1420 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001421 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001422 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001423 syncbuf.fail(self, e)
1424 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001425 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001426 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001427 if submodules:
1428 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001429
Mike Frysingere6a202f2019-08-02 15:57:57 -04001430 def AddCopyFile(self, src, dest, topdir):
1431 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001432
Mike Frysingere6a202f2019-08-02 15:57:57 -04001433 No filesystem changes occur here. Actual copying happens later on.
1434
1435 Paths should have basic validation run on them before being queued.
1436 Further checking will be handled when the actual copy happens.
1437 """
1438 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1439
1440 def AddLinkFile(self, src, dest, topdir):
1441 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1442
1443 No filesystem changes occur here. Actual linking happens later on.
1444
1445 Paths should have basic validation run on them before being queued.
1446 Further checking will be handled when the actual link happens.
1447 """
1448 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001449
James W. Mills24c13082012-04-12 15:04:13 -05001450 def AddAnnotation(self, name, value, keep):
1451 self.annotations.append(_Annotation(name, value, keep))
1452
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001453 def DownloadPatchSet(self, change_id, patch_id):
1454 """Download a single patch set of a single change to FETCH_HEAD.
1455 """
1456 remote = self.GetRemote(self.remote.name)
1457
1458 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001459 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001460 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001461 if GitCommand(self, cmd, bare=True).Wait() != 0:
1462 return None
1463 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001464 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001465 change_id,
1466 patch_id,
1467 self.bare_git.rev_parse('FETCH_HEAD'))
1468
Mike Frysingerc0d18662020-02-19 19:19:18 -05001469 def DeleteWorktree(self, quiet=False, force=False):
1470 """Delete the source checkout and any other housekeeping tasks.
1471
1472 This currently leaves behind the internal .repo/ cache state. This helps
1473 when switching branches or manifest changes get reverted as we don't have
1474 to redownload all the git objects. But we should do some GC at some point.
1475
1476 Args:
1477 quiet: Whether to hide normal messages.
1478 force: Always delete tree even if dirty.
1479
1480 Returns:
1481 True if the worktree was completely cleaned out.
1482 """
1483 if self.IsDirty():
1484 if force:
1485 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1486 (self.relpath,), file=sys.stderr)
1487 else:
1488 print('error: %s: Cannot remove project: uncommitted changes are '
1489 'present.\n' % (self.relpath,), file=sys.stderr)
1490 return False
1491
1492 if not quiet:
1493 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1494
1495 # Unlock and delink from the main worktree. We don't use git's worktree
1496 # remove because it will recursively delete projects -- we handle that
1497 # ourselves below. https://crbug.com/git/48
1498 if self.use_git_worktrees:
1499 needle = platform_utils.realpath(self.gitdir)
1500 # Find the git worktree commondir under .repo/worktrees/.
1501 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1502 assert output.startswith('worktree '), output
1503 commondir = output[9:]
1504 # Walk each of the git worktrees to see where they point.
1505 configs = os.path.join(commondir, 'worktrees')
1506 for name in os.listdir(configs):
1507 gitdir = os.path.join(configs, name, 'gitdir')
1508 with open(gitdir) as fp:
1509 relpath = fp.read().strip()
1510 # Resolve the checkout path and see if it matches this project.
1511 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1512 if fullpath == needle:
1513 platform_utils.rmtree(os.path.join(configs, name))
1514
1515 # Delete the .git directory first, so we're less likely to have a partially
1516 # working git repository around. There shouldn't be any git projects here,
1517 # so rmtree works.
1518
1519 # Try to remove plain files first in case of git worktrees. If this fails
1520 # for any reason, we'll fall back to rmtree, and that'll display errors if
1521 # it can't remove things either.
1522 try:
1523 platform_utils.remove(self.gitdir)
1524 except OSError:
1525 pass
1526 try:
1527 platform_utils.rmtree(self.gitdir)
1528 except OSError as e:
1529 if e.errno != errno.ENOENT:
1530 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1531 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1532 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1533 return False
1534
1535 # Delete everything under the worktree, except for directories that contain
1536 # another git project.
1537 dirs_to_remove = []
1538 failed = False
1539 for root, dirs, files in platform_utils.walk(self.worktree):
1540 for f in files:
1541 path = os.path.join(root, f)
1542 try:
1543 platform_utils.remove(path)
1544 except OSError as e:
1545 if e.errno != errno.ENOENT:
1546 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1547 failed = True
1548 dirs[:] = [d for d in dirs
1549 if not os.path.lexists(os.path.join(root, d, '.git'))]
1550 dirs_to_remove += [os.path.join(root, d) for d in dirs
1551 if os.path.join(root, d) not in dirs_to_remove]
1552 for d in reversed(dirs_to_remove):
1553 if platform_utils.islink(d):
1554 try:
1555 platform_utils.remove(d)
1556 except OSError as e:
1557 if e.errno != errno.ENOENT:
1558 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1559 failed = True
1560 elif not platform_utils.listdir(d):
1561 try:
1562 platform_utils.rmdir(d)
1563 except OSError as e:
1564 if e.errno != errno.ENOENT:
1565 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1566 failed = True
1567 if failed:
1568 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1569 file=sys.stderr)
1570 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1571 return False
1572
1573 # Try deleting parent dirs if they are empty.
1574 path = self.worktree
1575 while path != self.manifest.topdir:
1576 try:
1577 platform_utils.rmdir(path)
1578 except OSError as e:
1579 if e.errno != errno.ENOENT:
1580 break
1581 path = os.path.dirname(path)
1582
1583 return True
1584
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001585# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001586 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001587 """Create a new branch off the manifest's revision.
1588 """
Simran Basib9a1b732015-08-20 12:19:28 -07001589 if not branch_merge:
1590 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001591 head = self.work_git.GetHead()
1592 if head == (R_HEADS + name):
1593 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001594
David Pursehouse8a68ff92012-09-24 12:15:13 +09001595 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001596 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001597 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001598 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001599 capture_stdout=True,
1600 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001601
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001602 branch = self.GetBranch(name)
1603 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001604 branch.merge = branch_merge
1605 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1606 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001607
1608 if revision is None:
1609 revid = self.GetRevisionId(all_refs)
1610 else:
1611 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001612
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001613 if head.startswith(R_HEADS):
1614 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001615 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001616 except KeyError:
1617 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001618 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001619 ref = R_HEADS + name
1620 self.work_git.update_ref(ref, revid)
1621 self.work_git.symbolic_ref(HEAD, ref)
1622 branch.Save()
1623 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001624
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001625 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001626 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001627 capture_stdout=True,
1628 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001629 branch.Save()
1630 return True
1631 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001632
Wink Saville02d79452009-04-10 13:01:24 -07001633 def CheckoutBranch(self, name):
1634 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001635
1636 Args:
1637 name: The name of the branch to checkout.
1638
1639 Returns:
1640 True if the checkout succeeded; False if it didn't; None if the branch
1641 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001642 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001643 rev = R_HEADS + name
1644 head = self.work_git.GetHead()
1645 if head == rev:
1646 # Already on the branch
1647 #
1648 return True
Wink Saville02d79452009-04-10 13:01:24 -07001649
David Pursehouse8a68ff92012-09-24 12:15:13 +09001650 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001651 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001652 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001653 except KeyError:
1654 # Branch does not exist in this project
1655 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001656 return None
Wink Saville02d79452009-04-10 13:01:24 -07001657
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001658 if head.startswith(R_HEADS):
1659 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001660 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001661 except KeyError:
1662 head = None
1663
1664 if head == revid:
1665 # Same revision; just update HEAD to point to the new
1666 # target branch, but otherwise take no other action.
1667 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001668 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1669 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001670 return True
1671
1672 return GitCommand(self,
1673 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001674 capture_stdout=True,
1675 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001676
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001677 def AbandonBranch(self, name):
1678 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001679
1680 Args:
1681 name: The name of the branch to abandon.
1682
1683 Returns:
1684 True if the abandon succeeded; False if it didn't; None if the branch
1685 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001686 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001687 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001688 all_refs = self.bare_ref.all
1689 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001690 # Doesn't exist
1691 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001692
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001693 head = self.work_git.GetHead()
1694 if head == rev:
1695 # We can't destroy the branch while we are sitting
1696 # on it. Switch to a detached HEAD.
1697 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001698 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001699
David Pursehouse8a68ff92012-09-24 12:15:13 +09001700 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001701 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001702 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001703 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001704 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001705
1706 return GitCommand(self,
1707 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001708 capture_stdout=True,
1709 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001710
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001711 def PruneHeads(self):
1712 """Prune any topic branches already merged into upstream.
1713 """
1714 cb = self.CurrentBranch
1715 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001716 left = self._allrefs
1717 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001718 if name.startswith(R_HEADS):
1719 name = name[len(R_HEADS):]
1720 if cb is None or name != cb:
1721 kill.append(name)
1722
Mike Frysingera3794e92021-03-11 23:24:01 -05001723 # Minor optimization: If there's nothing to prune, then don't try to read
1724 # any project state.
1725 if not kill and not cb:
1726 return []
1727
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001728 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001729 if cb is not None \
1730 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001731 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001732 self.work_git.DetachHead(HEAD)
1733 kill.append(cb)
1734
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001735 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001736 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001737
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001738 try:
1739 self.bare_git.DetachHead(rev)
1740
1741 b = ['branch', '-d']
1742 b.extend(kill)
1743 b = GitCommand(self, b, bare=True,
1744 capture_stdout=True,
1745 capture_stderr=True)
1746 b.Wait()
1747 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001748 if ID_RE.match(old):
1749 self.bare_git.DetachHead(old)
1750 else:
1751 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001752 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001753
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001754 for branch in kill:
1755 if (R_HEADS + branch) not in left:
1756 self.CleanPublishedCache()
1757 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758
1759 if cb and cb not in kill:
1760 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001761 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001762
1763 kept = []
1764 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001765 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766 branch = self.GetBranch(branch)
1767 base = branch.LocalMerge
1768 if not base:
1769 base = rev
1770 kept.append(ReviewableBranch(self, branch, base))
1771 return kept
1772
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001773# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001774 def GetRegisteredSubprojects(self):
1775 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001776
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001777 def rec(subprojects):
1778 if not subprojects:
1779 return
1780 result.extend(subprojects)
1781 for p in subprojects:
1782 rec(p.subprojects)
1783 rec(self.subprojects)
1784 return result
1785
1786 def _GetSubmodules(self):
1787 # Unfortunately we cannot call `git submodule status --recursive` here
1788 # because the working tree might not exist yet, and it cannot be used
1789 # without a working tree in its current implementation.
1790
1791 def get_submodules(gitdir, rev):
1792 # Parse .gitmodules for submodule sub_paths and sub_urls
1793 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1794 if not sub_paths:
1795 return []
1796 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1797 # revision of submodule repository
1798 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1799 submodules = []
1800 for sub_path, sub_url in zip(sub_paths, sub_urls):
1801 try:
1802 sub_rev = sub_revs[sub_path]
1803 except KeyError:
1804 # Ignore non-exist submodules
1805 continue
1806 submodules.append((sub_rev, sub_path, sub_url))
1807 return submodules
1808
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001809 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1810 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001811
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001812 def parse_gitmodules(gitdir, rev):
1813 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1814 try:
Anthony King7bdac712014-07-16 12:56:40 +01001815 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1816 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001817 except GitError:
1818 return [], []
1819 if p.Wait() != 0:
1820 return [], []
1821
1822 gitmodules_lines = []
1823 fd, temp_gitmodules_path = tempfile.mkstemp()
1824 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001825 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001826 os.close(fd)
1827 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001828 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1829 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001830 if p.Wait() != 0:
1831 return [], []
1832 gitmodules_lines = p.stdout.split('\n')
1833 except GitError:
1834 return [], []
1835 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001836 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001837
1838 names = set()
1839 paths = {}
1840 urls = {}
1841 for line in gitmodules_lines:
1842 if not line:
1843 continue
1844 m = re_path.match(line)
1845 if m:
1846 names.add(m.group(1))
1847 paths[m.group(1)] = m.group(2)
1848 continue
1849 m = re_url.match(line)
1850 if m:
1851 names.add(m.group(1))
1852 urls[m.group(1)] = m.group(2)
1853 continue
1854 names = sorted(names)
1855 return ([paths.get(name, '') for name in names],
1856 [urls.get(name, '') for name in names])
1857
1858 def git_ls_tree(gitdir, rev, paths):
1859 cmd = ['ls-tree', rev, '--']
1860 cmd.extend(paths)
1861 try:
Anthony King7bdac712014-07-16 12:56:40 +01001862 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1863 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001864 except GitError:
1865 return []
1866 if p.Wait() != 0:
1867 return []
1868 objects = {}
1869 for line in p.stdout.split('\n'):
1870 if not line.strip():
1871 continue
1872 object_rev, object_path = line.split()[2:4]
1873 objects[object_path] = object_rev
1874 return objects
1875
1876 try:
1877 rev = self.GetRevisionId()
1878 except GitError:
1879 return []
1880 return get_submodules(self.gitdir, rev)
1881
1882 def GetDerivedSubprojects(self):
1883 result = []
1884 if not self.Exists:
1885 # If git repo does not exist yet, querying its submodules will
1886 # mess up its states; so return here.
1887 return result
1888 for rev, path, url in self._GetSubmodules():
1889 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001890 relpath, worktree, gitdir, objdir = \
1891 self.manifest.GetSubprojectPaths(self, name, path)
1892 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001893 if project:
1894 result.extend(project.GetDerivedSubprojects())
1895 continue
David James8d201162013-10-11 17:03:19 -07001896
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001897 if url.startswith('..'):
1898 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001899 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001900 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001901 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001902 review=self.remote.review,
1903 revision=self.remote.revision)
1904 subproject = Project(manifest=self.manifest,
1905 name=name,
1906 remote=remote,
1907 gitdir=gitdir,
1908 objdir=objdir,
1909 worktree=worktree,
1910 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001911 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001912 revisionId=rev,
1913 rebase=self.rebase,
1914 groups=self.groups,
1915 sync_c=self.sync_c,
1916 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001917 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001918 parent=self,
1919 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001920 result.append(subproject)
1921 result.extend(subproject.GetDerivedSubprojects())
1922 return result
1923
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001924# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001925 def EnableRepositoryExtension(self, key, value='true', version=1):
1926 """Enable git repository extension |key| with |value|.
1927
1928 Args:
1929 key: The extension to enabled. Omit the "extensions." prefix.
1930 value: The value to use for the extension.
1931 version: The minimum git repository version needed.
1932 """
1933 # Make sure the git repo version is new enough already.
1934 found_version = self.config.GetInt('core.repositoryFormatVersion')
1935 if found_version is None:
1936 found_version = 0
1937 if found_version < version:
1938 self.config.SetString('core.repositoryFormatVersion', str(version))
1939
1940 # Enable the extension!
1941 self.config.SetString('extensions.%s' % (key,), value)
1942
Mike Frysinger50a81de2020-09-06 15:51:21 -04001943 def ResolveRemoteHead(self, name=None):
1944 """Find out what the default branch (HEAD) points to.
1945
1946 Normally this points to refs/heads/master, but projects are moving to main.
1947 Support whatever the server uses rather than hardcoding "master" ourselves.
1948 """
1949 if name is None:
1950 name = self.remote.name
1951
1952 # The output will look like (NB: tabs are separators):
1953 # ref: refs/heads/master HEAD
1954 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1955 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1956
1957 for line in output.splitlines():
1958 lhs, rhs = line.split('\t', 1)
1959 if rhs == 'HEAD' and lhs.startswith('ref:'):
1960 return lhs[4:].strip()
1961
1962 return None
1963
Zac Livingstone4332262017-06-16 08:56:09 -06001964 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001965 try:
1966 # if revision (sha or tag) is not present then following function
1967 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08001968 self.bare_git.rev_list('-1', '--missing=allow-any',
1969 '%s^0' % self.revisionExpr, '--')
Xin Li0e776a52021-06-29 21:42:34 +00001970 if self.upstream:
1971 rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
1972 self.bare_git.rev_list('-1', '--missing=allow-any',
1973 '%s^0' % rev, '--')
Chris AtLee2fb64662014-01-16 21:32:33 -05001974 return True
1975 except GitError:
1976 # There is no such persistent revision. We have to fetch it.
1977 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001978
Julien Campergue335f5ef2013-10-16 11:02:35 +02001979 def _FetchArchive(self, tarpath, cwd=None):
1980 cmd = ['archive', '-v', '-o', tarpath]
1981 cmd.append('--remote=%s' % self.remote.url)
1982 cmd.append('--prefix=%s/' % self.relpath)
1983 cmd.append(self.revisionExpr)
1984
1985 command = GitCommand(self, cmd, cwd=cwd,
1986 capture_stdout=True,
1987 capture_stderr=True)
1988
1989 if command.Wait() != 0:
1990 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1991
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001992 def _RemoteFetch(self, name=None,
1993 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001994 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001995 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001996 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001997 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001998 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001999 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002000 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002001 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002002 submodules=False,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002003 ssh_proxy=None,
Xin Li745be2e2019-06-03 11:24:30 -07002004 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002005 clone_filter=None,
2006 retry_fetches=2,
2007 retry_sleep_initial_sec=4.0,
2008 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002009 is_sha1 = False
2010 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002011 # The depth should not be used when fetching to a mirror because
2012 # it will result in a shallow repository that cannot be cloned or
2013 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002014 # The repo project should also never be synced with partial depth.
2015 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2016 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002017
Shawn Pearce69e04d82014-01-29 12:48:54 -08002018 if depth:
2019 current_branch_only = True
2020
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002021 if ID_RE.match(self.revisionExpr) is not None:
2022 is_sha1 = True
2023
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002024 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002025 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002026 # this is a tag and its sha1 value should never change
2027 tag_name = self.revisionExpr[len(R_TAGS):]
2028
2029 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002030 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002031 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002032 print('Skipped fetching project %s (already have persistent ref)'
2033 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002034 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002035 if is_sha1 and not depth:
2036 # When syncing a specific commit and --depth is not set:
2037 # * if upstream is explicitly specified and is not a sha1, fetch only
2038 # upstream as users expect only upstream to be fetch.
2039 # Note: The commit might not be in upstream in which case the sync
2040 # will fail.
2041 # * otherwise, fetch all branches to make sure we end up with the
2042 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002043 if self.upstream:
2044 current_branch_only = not ID_RE.match(self.upstream)
2045 else:
2046 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002047
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002048 if not name:
2049 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002050
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002051 remote = self.GetRemote(name)
Mike Frysinger339f2df2021-05-06 00:44:42 -04002052 if not remote.PreConnectFetch(ssh_proxy):
2053 ssh_proxy = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002054
Shawn O. Pearce88443382010-10-08 10:02:09 +02002055 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002056 if alt_dir and 'objects' == os.path.basename(alt_dir):
2057 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002058 packed_refs = os.path.join(self.gitdir, 'packed-refs')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002059
David Pursehouse8a68ff92012-09-24 12:15:13 +09002060 all_refs = self.bare_ref.all
2061 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002062 tmp = set()
2063
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302064 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002065 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002066 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002067 all_refs[r] = ref_id
2068 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002069 continue
2070
David Pursehouse8a68ff92012-09-24 12:15:13 +09002071 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002072 continue
2073
David Pursehouse8a68ff92012-09-24 12:15:13 +09002074 r = 'refs/_alt/%s' % ref_id
2075 all_refs[r] = ref_id
2076 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002077 tmp.add(r)
2078
heping3d7bbc92017-04-12 19:51:47 +08002079 tmp_packed_lines = []
2080 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002081
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302082 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002083 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002084 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002085 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002086 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002087
heping3d7bbc92017-04-12 19:51:47 +08002088 tmp_packed = ''.join(tmp_packed_lines)
2089 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002090 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002091 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002092 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002093
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002094 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002095
Xin Li745be2e2019-06-03 11:24:30 -07002096 if clone_filter:
2097 git_require((2, 19, 0), fail=True, msg='partial clones')
2098 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002099 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002100
Conley Owensf97e8382015-01-21 11:12:46 -08002101 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002102 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002103 else:
2104 # If this repo has shallow objects, then we don't know which refs have
2105 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2106 # do this with projects that don't have shallow objects, since it is less
2107 # efficient.
2108 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2109 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002110
Mike Frysinger4847e052020-02-22 00:07:35 -05002111 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002112 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002113 if not quiet and sys.stdout.isatty():
2114 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002115 if not self.worktree:
2116 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002117 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002118
Mike Frysingere57f1142019-03-18 21:27:54 -04002119 if force_sync:
2120 cmd.append('--force')
2121
David Pursehouse74cfd272015-10-14 10:50:15 +09002122 if prune:
2123 cmd.append('--prune')
2124
Martin Kellye4e94d22017-03-21 16:05:12 -07002125 if submodules:
2126 cmd.append('--recurse-submodules=on-demand')
2127
Kuang-che Wu6856f982019-11-25 12:37:55 +08002128 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002129 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002130 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002131 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002132 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002133 spec.append('tag')
2134 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002135
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302136 if self.manifest.IsMirror and not current_branch_only:
2137 branch = None
2138 else:
2139 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002140 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002141 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002142 # Shallow checkout of a specific commit, fetch from that commit and not
2143 # the heads only as the commit might be deeper in the history.
2144 spec.append(branch)
Xin Liaabf79d2021-04-29 01:50:38 -07002145 if self.upstream:
2146 spec.append(self.upstream)
Kuang-che Wu6856f982019-11-25 12:37:55 +08002147 else:
2148 if is_sha1:
2149 branch = self.upstream
2150 if branch is not None and branch.strip():
2151 if not branch.startswith('refs/'):
2152 branch = R_HEADS + branch
2153 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2154
2155 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2156 # whole repo.
2157 if self.manifest.IsMirror and not spec:
2158 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2159
2160 # If using depth then we should not get all the tags since they may
2161 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002162 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002163 cmd.append('--no-tags')
2164 else:
2165 cmd.append('--tags')
2166 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2167
Conley Owens80b87fe2014-05-09 17:13:44 -07002168 cmd.extend(spec)
2169
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002170 # At least one retry minimum due to git remote prune.
2171 retry_fetches = max(retry_fetches, 2)
2172 retry_cur_sleep = retry_sleep_initial_sec
2173 ok = prune_tried = False
2174 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002175 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002176 merge_output=True, capture_stdout=quiet or bool(output_redir))
2177 if gitcmd.stdout and not quiet and output_redir:
2178 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002179 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002180 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002181 ok = True
2182 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002183
2184 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002185 elif (gitcmd.stdout and
2186 'error:' in gitcmd.stdout and
2187 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002188 # Fallthru to sleep+retry logic at the bottom.
2189 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002190
Mike Frysinger6823bc22021-04-15 02:06:28 -04002191 # Try to prune remote branches once in case there are conflicts.
2192 # For example, if the remote had refs/heads/upstream, but deleted that and
2193 # now has refs/heads/upstream/foo.
2194 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002195 'error:' in gitcmd.stdout and
2196 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002197 not prune_tried):
2198 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002199 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002200 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002201 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002202 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002203 break
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002204 print('retrying fetch after pruning remote branches', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002205 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002206 continue
Brian Harring14a66742012-09-28 20:21:57 -07002207 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002208 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2209 # in sha1 mode, we just tried sync'ing from the upstream field; it
2210 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002211 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002212 elif ret < 0:
2213 # Git died with a signal, exit immediately
2214 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002215
2216 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002217 if not verbose and gitcmd.stdout:
2218 print('\n%s:\n%s' % (self.name, gitcmd.stdout), end='', file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002219 if try_n < retry_fetches - 1:
Mike Frysingerb16b9d22021-05-20 01:48:17 -04002220 print('%s: sleeping %s seconds before retrying' % (self.name, retry_cur_sleep),
2221 file=output_redir)
Mike Frysinger6823bc22021-04-15 02:06:28 -04002222 time.sleep(retry_cur_sleep)
2223 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2224 MAXIMUM_RETRY_SLEEP_SEC)
2225 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2226 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002227
2228 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002229 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002230 if old_packed != '':
2231 _lwrite(packed_refs, old_packed)
2232 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002233 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002234 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002235
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002236 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002237 # We just synced the upstream given branch; verify we
2238 # got what we wanted, else trigger a second run of all
2239 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002240 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002241 # Sync the current branch only with depth set to None.
2242 # We always pass depth=None down to avoid infinite recursion.
2243 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002244 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002245 current_branch_only=current_branch_only and depth,
2246 initial=False, alt_dir=alt_dir,
Mike Frysinger19e409c2021-05-05 19:44:35 -04002247 depth=None, ssh_proxy=ssh_proxy, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002248
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002249 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002250
Mike Frysingere50b6a72020-02-19 01:45:48 -05002251 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002252 if initial and \
2253 (self.manifest.manifestProject.config.GetString('repo.depth') or
2254 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002255 return False
2256
2257 remote = self.GetRemote(self.remote.name)
2258 bundle_url = remote.url + '/clone.bundle'
2259 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002260 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2261 'persistent-http',
2262 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002263 return False
2264
2265 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2266 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2267
2268 exist_dst = os.path.exists(bundle_dst)
2269 exist_tmp = os.path.exists(bundle_tmp)
2270
2271 if not initial and not exist_dst and not exist_tmp:
2272 return False
2273
2274 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002275 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2276 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002277 if not exist_dst:
2278 return False
2279
2280 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002281 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002282 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002283 if not quiet and sys.stdout.isatty():
2284 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002285 if not self.worktree:
2286 cmd.append('--update-head-ok')
2287 cmd.append(bundle_dst)
2288 for f in remote.fetch:
2289 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002290 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002291
2292 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002293 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002294 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002295 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002296 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002297 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002298
Mike Frysingere50b6a72020-02-19 01:45:48 -05002299 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002300 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002301 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002302
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002303 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002304 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002305 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002306 if os.path.exists(tmpPath):
2307 size = os.stat(tmpPath).st_size
2308 if size >= 1024:
2309 cmd += ['--continue-at', '%d' % (size,)]
2310 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002311 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002312 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002313 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002314 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002315 if proxy:
2316 cmd += ['--proxy', proxy]
2317 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2318 cmd += ['--proxy', os.environ['http_proxy']]
2319 if srcUrl.startswith('persistent-https'):
2320 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2321 elif srcUrl.startswith('persistent-http'):
2322 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002323 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002324
Dave Borowitz137d0132015-01-02 11:12:54 -08002325 if IsTrace():
2326 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002327 if verbose:
2328 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2329 stdout = None if verbose else subprocess.PIPE
2330 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002331 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002332 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002333 except OSError:
2334 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002335
Mike Frysingere50b6a72020-02-19 01:45:48 -05002336 (output, _) = proc.communicate()
2337 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002338
Dave Borowitz137d0132015-01-02 11:12:54 -08002339 if curlret == 22:
2340 # From curl man page:
2341 # 22: HTTP page not retrieved. The requested url was not found or
2342 # returned another error with the HTTP error code being 400 or above.
2343 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002344 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002345 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2346 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002347 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002348 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002349 elif curlret and not verbose and output:
2350 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002351
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002352 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002353 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002354 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002355 return True
2356 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002357 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002358 return False
2359 else:
2360 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002361
Kris Giesingc8d882a2014-12-23 13:02:32 -08002362 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002363 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002364 with open(path, 'rb') as f:
2365 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002366 return True
2367 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002368 if not quiet:
2369 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002370 return False
2371 except OSError:
2372 return False
2373
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002374 def _Checkout(self, rev, quiet=False):
2375 cmd = ['checkout']
2376 if quiet:
2377 cmd.append('-q')
2378 cmd.append(rev)
2379 cmd.append('--')
2380 if GitCommand(self, cmd).Wait() != 0:
2381 if self._allrefs:
2382 raise GitError('%s checkout %s ' % (self.name, rev))
2383
Mike Frysinger915fda12020-03-22 12:15:20 -04002384 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002385 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002386 if ffonly:
2387 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002388 if record_origin:
2389 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002390 cmd.append(rev)
2391 cmd.append('--')
2392 if GitCommand(self, cmd).Wait() != 0:
2393 if self._allrefs:
2394 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2395
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302396 def _LsRemote(self, refs):
2397 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302398 p = GitCommand(self, cmd, capture_stdout=True)
2399 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002400 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302401 return None
2402
Anthony King7bdac712014-07-16 12:56:40 +01002403 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002404 cmd = ['revert']
2405 cmd.append('--no-edit')
2406 cmd.append(rev)
2407 cmd.append('--')
2408 if GitCommand(self, cmd).Wait() != 0:
2409 if self._allrefs:
2410 raise GitError('%s revert %s ' % (self.name, rev))
2411
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002412 def _ResetHard(self, rev, quiet=True):
2413 cmd = ['reset', '--hard']
2414 if quiet:
2415 cmd.append('-q')
2416 cmd.append(rev)
2417 if GitCommand(self, cmd).Wait() != 0:
2418 raise GitError('%s reset --hard %s ' % (self.name, rev))
2419
Martin Kellye4e94d22017-03-21 16:05:12 -07002420 def _SyncSubmodules(self, quiet=True):
2421 cmd = ['submodule', 'update', '--init', '--recursive']
2422 if quiet:
2423 cmd.append('-q')
2424 if GitCommand(self, cmd).Wait() != 0:
2425 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2426
Anthony King7bdac712014-07-16 12:56:40 +01002427 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002428 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002429 if onto is not None:
2430 cmd.extend(['--onto', onto])
2431 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002432 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002433 raise GitError('%s rebase %s ' % (self.name, upstream))
2434
Pierre Tardy3d125942012-05-04 12:18:12 +02002435 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002436 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002437 if ffonly:
2438 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002439 if GitCommand(self, cmd).Wait() != 0:
2440 raise GitError('%s merge %s ' % (self.name, head))
2441
David Pursehousee8ace262020-02-13 12:41:15 +09002442 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002443 init_git_dir = not os.path.exists(self.gitdir)
2444 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002445 try:
2446 # Initialize the bare repository, which contains all of the objects.
2447 if init_obj_dir:
2448 os.makedirs(self.objdir)
2449 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002450
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002451 if self.use_git_worktrees:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002452 # Enable per-worktree config file support if possible. This is more a
2453 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002454 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002455 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002456
Kevin Degib1a07b82015-07-27 13:33:43 -06002457 # If we have a separate directory to hold refs, initialize it as well.
2458 if self.objdir != self.gitdir:
2459 if init_git_dir:
2460 os.makedirs(self.gitdir)
2461
2462 if init_obj_dir or init_git_dir:
2463 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2464 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002465 try:
2466 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2467 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002468 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002469 print("Retrying clone after deleting %s" %
2470 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002471 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002472 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2473 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002474 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002475 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002476 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2477 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002478 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002479 raise e
2480 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002481
Kevin Degi384b3c52014-10-16 16:02:58 -06002482 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002483 mp = self.manifest.manifestProject
2484 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002485
Kevin Degib1a07b82015-07-27 13:33:43 -06002486 if ref_dir or mirror_git:
2487 if not mirror_git:
2488 mirror_git = os.path.join(ref_dir, self.name + '.git')
2489 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2490 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002491 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2492 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002493
Kevin Degib1a07b82015-07-27 13:33:43 -06002494 if os.path.exists(mirror_git):
2495 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002496 elif os.path.exists(repo_git):
2497 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002498 elif os.path.exists(worktrees_git):
2499 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002500 else:
2501 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002502
Kevin Degib1a07b82015-07-27 13:33:43 -06002503 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002504 if not os.path.isabs(ref_dir):
2505 # The alternate directory is relative to the object database.
2506 ref_dir = os.path.relpath(ref_dir,
2507 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002508 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2509 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002510
David Pursehousee8ace262020-02-13 12:41:15 +09002511 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002512
2513 m = self.manifest.manifestProject.config
2514 for key in ['user.name', 'user.email']:
2515 if m.Has(key, include_defaults=False):
2516 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002517 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002518 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002519 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002520 except Exception:
2521 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002522 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002523 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002524 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002525 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002526
David Pursehousee8ace262020-02-13 12:41:15 +09002527 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002528 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002529 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002530
David Pursehousee8ace262020-02-13 12:41:15 +09002531 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002532 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002533 if not os.path.exists(hooks):
2534 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002535 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002536 name = os.path.basename(stock_hook)
2537
Victor Boivie65e0f352011-04-18 11:23:29 +02002538 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002539 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002540 # Don't install a Gerrit Code Review hook if this
2541 # project does not appear to use it for reviews.
2542 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002543 # Since the manifest project is one of those, but also
2544 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002545 continue
2546
2547 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002548 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002549 continue
2550 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002551 # If the files are the same, we'll leave it alone. We create symlinks
2552 # below by default but fallback to hardlinks if the OS blocks them.
2553 # So if we're here, it's probably because we made a hardlink below.
2554 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002555 if not quiet:
2556 _warn("%s: Not replacing locally modified %s hook",
2557 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002558 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002559 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002560 platform_utils.symlink(
2561 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002562 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002563 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002564 try:
2565 os.link(stock_hook, dst)
2566 except OSError:
2567 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002568 else:
2569 raise
2570
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002571 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002572 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002573 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002574 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002575 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002576 remote.review = self.remote.review
2577 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002578
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002579 if self.worktree:
2580 remote.ResetFetch(mirror=False)
2581 else:
2582 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002583 remote.Save()
2584
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002585 def _InitMRef(self):
2586 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002587 if self.use_git_worktrees:
Mike Frysinger29626b42021-05-01 09:37:13 -04002588 # Set up the m/ space to point to the worktree-specific ref space.
2589 # We'll update the worktree-specific ref space on each checkout.
2590 ref = R_M + self.manifest.branch
2591 if not self.bare_ref.symref(ref):
2592 self.bare_git.symbolic_ref(
2593 '-m', 'redirecting to worktree scope',
2594 ref, R_WORKTREE_M + self.manifest.branch)
2595
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002596 # We can't update this ref with git worktrees until it exists.
2597 # We'll wait until the initial checkout to set it.
2598 if not os.path.exists(self.worktree):
2599 return
2600
2601 base = R_WORKTREE_M
2602 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002603
2604 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002605 else:
2606 base = R_M
2607 active_git = self.bare_git
2608
2609 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002610
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002611 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002612 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002613
Remy Böhmer1469c282020-12-15 18:49:02 +01002614 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002615 cur = self.bare_ref.symref(ref)
2616
2617 if self.revisionId:
2618 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2619 msg = 'manifest set to %s' % self.revisionId
2620 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002621 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002622 else:
2623 remote = self.GetRemote(self.remote.name)
2624 dst = remote.ToLocal(self.revisionExpr)
2625 if cur != dst:
2626 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002627 if detach:
2628 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2629 else:
2630 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002631
Kevin Degi384b3c52014-10-16 16:02:58 -06002632 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002633 # Git worktrees don't use symlinks to share at all.
2634 if self.use_git_worktrees:
2635 return
2636
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002637 symlink_files = self.shareable_files[:]
2638 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002639 if share_refs:
2640 symlink_files += self.working_tree_files
2641 symlink_dirs += self.working_tree_dirs
2642 to_symlink = symlink_files + symlink_dirs
2643 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002644 # Try to self-heal a bit in simple cases.
2645 dst_path = os.path.join(destdir, name)
2646 src_path = os.path.join(srcdir, name)
2647
2648 if name in self.working_tree_dirs:
2649 # If the dir is missing under .repo/projects/, create it.
2650 if not os.path.exists(src_path):
2651 os.makedirs(src_path)
2652
2653 elif name in self.working_tree_files:
2654 # If it's a file under the checkout .git/ and the .repo/projects/ has
2655 # nothing, move the file under the .repo/projects/ tree.
2656 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2657 platform_utils.rename(dst_path, src_path)
2658
2659 # If the path exists under the .repo/projects/ and there's no symlink
2660 # under the checkout .git/, recreate the symlink.
2661 if name in self.working_tree_dirs or name in self.working_tree_files:
2662 if os.path.exists(src_path) and not os.path.exists(dst_path):
2663 platform_utils.symlink(
2664 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2665
2666 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002667 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002668 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002669 # Fail if the links are pointing to the wrong place
2670 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002671 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002672 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002673 'work tree. If you\'re comfortable with the '
2674 'possibility of losing the work tree\'s git metadata,'
2675 ' use `repo sync --force-sync {0}` to '
2676 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002677
David James8d201162013-10-11 17:03:19 -07002678 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2679 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2680
2681 Args:
2682 gitdir: The bare git repository. Must already be initialized.
2683 dotgit: The repository you would like to initialize.
2684 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2685 Only one work tree can store refs under a given |gitdir|.
2686 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2687 This saves you the effort of initializing |dotgit| yourself.
2688 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002689 symlink_files = self.shareable_files[:]
2690 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002691 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002692 symlink_files += self.working_tree_files
2693 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002694 to_symlink = symlink_files + symlink_dirs
2695
2696 to_copy = []
2697 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002698 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002699
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002700 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002701 for name in set(to_copy).union(to_symlink):
2702 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002703 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002704 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002705
Kevin Degi384b3c52014-10-16 16:02:58 -06002706 if os.path.lexists(dst):
2707 continue
David James8d201162013-10-11 17:03:19 -07002708
2709 # If the source dir doesn't exist, create an empty dir.
2710 if name in symlink_dirs and not os.path.lexists(src):
2711 os.makedirs(src)
2712
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002713 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002714 platform_utils.symlink(
2715 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002716 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002717 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002718 shutil.copytree(src, dst)
2719 elif os.path.isfile(src):
2720 shutil.copy(src, dst)
2721
Conley Owens80b87fe2014-05-09 17:13:44 -07002722 # If the source file doesn't exist, ensure the destination
2723 # file doesn't either.
2724 if name in symlink_files and not os.path.lexists(src):
2725 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002726 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002727 except OSError:
2728 pass
2729
David James8d201162013-10-11 17:03:19 -07002730 except OSError as e:
2731 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002732 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002733 else:
2734 raise
2735
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002736 def _InitGitWorktree(self):
2737 """Init the project using git worktrees."""
2738 self.bare_git.worktree('prune')
2739 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2740 self.worktree, self.GetRevisionId())
2741
2742 # Rewrite the internal state files to use relative paths between the
2743 # checkouts & worktrees.
2744 dotgit = os.path.join(self.worktree, '.git')
2745 with open(dotgit, 'r') as fp:
2746 # Figure out the checkout->worktree path.
2747 setting = fp.read()
2748 assert setting.startswith('gitdir:')
2749 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002750 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2751 # of file permissions. Delete it and recreate it from scratch to avoid.
2752 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002753 # Use relative path from checkout->worktree & maintain Unix line endings
2754 # on all OS's to match git behavior.
2755 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002756 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2757 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002758 # Use relative path from worktree->checkout & maintain Unix line endings
2759 # on all OS's to match git behavior.
2760 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002761 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2762
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002763 self._InitMRef()
2764
Martin Kellye4e94d22017-03-21 16:05:12 -07002765 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002766 realdotgit = os.path.join(self.worktree, '.git')
2767 tmpdotgit = realdotgit + '.tmp'
2768 init_dotgit = not os.path.exists(realdotgit)
2769 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002770 if self.use_git_worktrees:
2771 self._InitGitWorktree()
2772 self._CopyAndLinkFiles()
2773 return
2774
Mike Frysingerf4545122019-11-11 04:34:16 -05002775 dotgit = tmpdotgit
2776 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2777 os.makedirs(tmpdotgit)
2778 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2779 copy_all=False)
2780 else:
2781 dotgit = realdotgit
2782
Kevin Degib1a07b82015-07-27 13:33:43 -06002783 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002784 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2785 except GitError as e:
2786 if force_sync and not init_dotgit:
2787 try:
2788 platform_utils.rmtree(dotgit)
2789 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002790 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002791 raise e
2792 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002793
Mike Frysingerf4545122019-11-11 04:34:16 -05002794 if init_dotgit:
2795 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002796
Mike Frysingerf4545122019-11-11 04:34:16 -05002797 # Now that the .git dir is fully set up, move it to its final home.
2798 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002799
Mike Frysingerf4545122019-11-11 04:34:16 -05002800 # Finish checking out the worktree.
2801 cmd = ['read-tree', '--reset', '-u']
2802 cmd.append('-v')
2803 cmd.append(HEAD)
2804 if GitCommand(self, cmd).Wait() != 0:
2805 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002806
Mike Frysingerf4545122019-11-11 04:34:16 -05002807 if submodules:
2808 self._SyncSubmodules(quiet=True)
2809 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002810
Renaud Paquay788e9622017-01-27 11:41:12 -08002811 def _get_symlink_error_message(self):
2812 if platform_utils.isWindows():
2813 return ('Unable to create symbolic link. Please re-run the command as '
2814 'Administrator, or see '
2815 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2816 'for other options.')
2817 return 'filesystem must support symlinks'
2818
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002819 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002820 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002821
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002822 def _revlist(self, *args, **kw):
2823 a = []
2824 a.extend(args)
2825 a.append('--')
2826 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002827
2828 @property
2829 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002830 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002831
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002832 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002833 """Get logs between two revisions of this project."""
2834 comp = '..'
2835 if rev1:
2836 revs = [rev1]
2837 if rev2:
2838 revs.extend([comp, rev2])
2839 cmd = ['log', ''.join(revs)]
2840 out = DiffColoring(self.config)
2841 if out.is_on and color:
2842 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002843 if pretty_format is not None:
2844 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002845 if oneline:
2846 cmd.append('--oneline')
2847
2848 try:
2849 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2850 if log.Wait() == 0:
2851 return log.stdout
2852 except GitError:
2853 # worktree may not exist if groups changed for example. In that case,
2854 # try in gitdir instead.
2855 if not os.path.exists(self.worktree):
2856 return self.bare_git.log(*cmd[1:])
2857 else:
2858 raise
2859 return None
2860
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002861 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2862 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002863 """Get the list of logs from this revision to given revisionId"""
2864 logs = {}
2865 selfId = self.GetRevisionId(self._allrefs)
2866 toId = toProject.GetRevisionId(toProject._allrefs)
2867
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002868 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2869 pretty_format=pretty_format)
2870 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2871 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002872 return logs
2873
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002874 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002875
David James8d201162013-10-11 17:03:19 -07002876 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002877 self._project = project
2878 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002879 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002880
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002881 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2882 def __getstate__(self):
2883 return (self._project, self._bare, self._gitdir)
2884
2885 def __setstate__(self, state):
2886 self._project, self._bare, self._gitdir = state
2887
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002888 def LsOthers(self):
2889 p = GitCommand(self._project,
2890 ['ls-files',
2891 '-z',
2892 '--others',
2893 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002894 bare=False,
David James8d201162013-10-11 17:03:19 -07002895 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002896 capture_stdout=True,
2897 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002898 if p.Wait() == 0:
2899 out = p.stdout
2900 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002901 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002902 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002903 return []
2904
2905 def DiffZ(self, name, *args):
2906 cmd = [name]
2907 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002908 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002909 cmd.extend(args)
2910 p = GitCommand(self._project,
2911 cmd,
David James8d201162013-10-11 17:03:19 -07002912 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002913 bare=False,
2914 capture_stdout=True,
2915 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002916 p.Wait()
2917 r = {}
2918 out = p.stdout
2919 if out:
2920 out = iter(out[:-1].split('\0'))
2921 while out:
2922 try:
2923 info = next(out)
2924 path = next(out)
2925 except StopIteration:
2926 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002927
Mike Frysinger84230002021-02-16 17:08:35 -05002928 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002929
Mike Frysinger84230002021-02-16 17:08:35 -05002930 def __init__(self, path, omode, nmode, oid, nid, state):
2931 self.path = path
2932 self.src_path = None
2933 self.old_mode = omode
2934 self.new_mode = nmode
2935 self.old_id = oid
2936 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002937
Mike Frysinger84230002021-02-16 17:08:35 -05002938 if len(state) == 1:
2939 self.status = state
2940 self.level = None
2941 else:
2942 self.status = state[:1]
2943 self.level = state[1:]
2944 while self.level.startswith('0'):
2945 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002946
Mike Frysinger84230002021-02-16 17:08:35 -05002947 info = info[1:].split(' ')
2948 info = _Info(path, *info)
2949 if info.status in ('R', 'C'):
2950 info.src_path = info.path
2951 info.path = next(out)
2952 r[info.path] = info
2953 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002954
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002955 def GetDotgitPath(self, subpath=None):
2956 """Return the full path to the .git dir.
2957
2958 As a convenience, append |subpath| if provided.
2959 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002960 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002961 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002962 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002963 dotgit = os.path.join(self._project.worktree, '.git')
2964 if os.path.isfile(dotgit):
2965 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
2966 with open(dotgit) as fp:
2967 setting = fp.read()
2968 assert setting.startswith('gitdir:')
2969 gitdir = setting.split(':', 1)[1].strip()
2970 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
2971
2972 return dotgit if subpath is None else os.path.join(dotgit, subpath)
2973
2974 def GetHead(self):
2975 """Return the ref that HEAD points to."""
2976 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002977 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05002978 with open(path) as fd:
2979 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04002980 except IOError as e:
2981 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002982 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302983 line = line.decode()
2984 except AttributeError:
2985 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002986 if line.startswith('ref: '):
2987 return line[5:-1]
2988 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002989
2990 def SetHead(self, ref, message=None):
2991 cmdv = []
2992 if message is not None:
2993 cmdv.extend(['-m', message])
2994 cmdv.append(HEAD)
2995 cmdv.append(ref)
2996 self.symbolic_ref(*cmdv)
2997
2998 def DetachHead(self, new, message=None):
2999 cmdv = ['--no-deref']
3000 if message is not None:
3001 cmdv.extend(['-m', message])
3002 cmdv.append(HEAD)
3003 cmdv.append(new)
3004 self.update_ref(*cmdv)
3005
3006 def UpdateRef(self, name, new, old=None,
3007 message=None,
3008 detach=False):
3009 cmdv = []
3010 if message is not None:
3011 cmdv.extend(['-m', message])
3012 if detach:
3013 cmdv.append('--no-deref')
3014 cmdv.append(name)
3015 cmdv.append(new)
3016 if old is not None:
3017 cmdv.append(old)
3018 self.update_ref(*cmdv)
3019
3020 def DeleteRef(self, name, old=None):
3021 if not old:
3022 old = self.rev_parse(name)
3023 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003024 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003025
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003026 def rev_list(self, *args, **kw):
3027 if 'format' in kw:
3028 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3029 else:
3030 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003031 cmdv.extend(args)
3032 p = GitCommand(self._project,
3033 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003034 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003035 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003036 capture_stdout=True,
3037 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003038 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003039 raise GitError('%s rev-list %s: %s' %
3040 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003041 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003042
3043 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003044 """Allow arbitrary git commands using pythonic syntax.
3045
3046 This allows you to do things like:
3047 git_obj.rev_parse('HEAD')
3048
3049 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3050 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003051 Any other positional arguments will be passed to the git command, and the
3052 following keyword arguments are supported:
3053 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003054
3055 Args:
3056 name: The name of the git command to call. Any '_' characters will
3057 be replaced with '-'.
3058
3059 Returns:
3060 A callable object that will try to call git with the named command.
3061 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003062 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003063
Dave Borowitz091f8932012-10-23 17:01:04 -07003064 def runner(*args, **kwargs):
3065 cmdv = []
3066 config = kwargs.pop('config', None)
3067 for k in kwargs:
3068 raise TypeError('%s() got an unexpected keyword argument %r'
3069 % (name, k))
3070 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303071 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003072 cmdv.append('-c')
3073 cmdv.append('%s=%s' % (k, v))
3074 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003075 cmdv.extend(args)
3076 p = GitCommand(self._project,
3077 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003078 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003079 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003080 capture_stdout=True,
3081 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003082 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003083 raise GitError('%s %s: %s' %
3084 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003085 r = p.stdout
3086 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3087 return r[:-1]
3088 return r
3089 return runner
3090
3091
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003092class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003093
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003094 def __str__(self):
3095 return 'prior sync failed; rebase still in progress'
3096
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003097
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003098class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003099
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003100 def __str__(self):
3101 return 'contains uncommitted changes'
3102
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003103
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003104class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003105
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003106 def __init__(self, project, text):
3107 self.project = project
3108 self.text = text
3109
3110 def Print(self, syncbuf):
3111 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3112 syncbuf.out.nl()
3113
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003114
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003115class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003116
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003117 def __init__(self, project, why):
3118 self.project = project
3119 self.why = why
3120
3121 def Print(self, syncbuf):
3122 syncbuf.out.fail('error: %s/: %s',
3123 self.project.relpath,
3124 str(self.why))
3125 syncbuf.out.nl()
3126
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003127
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003128class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003129
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003130 def __init__(self, project, action):
3131 self.project = project
3132 self.action = action
3133
3134 def Run(self, syncbuf):
3135 out = syncbuf.out
3136 out.project('project %s/', self.project.relpath)
3137 out.nl()
3138 try:
3139 self.action()
3140 out.nl()
3141 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003142 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003143 out.nl()
3144 return False
3145
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003146
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003147class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003148
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003149 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003150 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003151 self.project = self.printer('header', attr='bold')
3152 self.info = self.printer('info')
3153 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003154
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003155
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003156class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003157
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003158 def __init__(self, config, detach_head=False):
3159 self._messages = []
3160 self._failures = []
3161 self._later_queue1 = []
3162 self._later_queue2 = []
3163
3164 self.out = _SyncColoring(config)
3165 self.out.redirect(sys.stderr)
3166
3167 self.detach_head = detach_head
3168 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003169 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003170
3171 def info(self, project, fmt, *args):
3172 self._messages.append(_InfoMessage(project, fmt % args))
3173
3174 def fail(self, project, err=None):
3175 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003176 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003177
3178 def later1(self, project, what):
3179 self._later_queue1.append(_Later(project, what))
3180
3181 def later2(self, project, what):
3182 self._later_queue2.append(_Later(project, what))
3183
3184 def Finish(self):
3185 self._PrintMessages()
3186 self._RunLater()
3187 self._PrintMessages()
3188 return self.clean
3189
David Rileye0684ad2017-04-05 00:02:59 -07003190 def Recently(self):
3191 recent_clean = self.recent_clean
3192 self.recent_clean = True
3193 return recent_clean
3194
3195 def _MarkUnclean(self):
3196 self.clean = False
3197 self.recent_clean = False
3198
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003199 def _RunLater(self):
3200 for q in ['_later_queue1', '_later_queue2']:
3201 if not self._RunQueue(q):
3202 return
3203
3204 def _RunQueue(self, queue):
3205 for m in getattr(self, queue):
3206 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003207 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003208 return False
3209 setattr(self, queue, [])
3210 return True
3211
3212 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003213 if self._messages or self._failures:
3214 if os.isatty(2):
3215 self.out.write(progress.CSI_ERASE_LINE)
3216 self.out.write('\r')
3217
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003218 for m in self._messages:
3219 m.Print(self)
3220 for m in self._failures:
3221 m.Print(self)
3222
3223 self._messages = []
3224 self._failures = []
3225
3226
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003227class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003228
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003229 """A special project housed under .repo.
3230 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003231
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003232 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003233 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003234 manifest=manifest,
3235 name=name,
3236 gitdir=gitdir,
3237 objdir=gitdir,
3238 worktree=worktree,
3239 remote=RemoteSpec('origin'),
3240 relpath='.repo/%s' % name,
3241 revisionExpr='refs/heads/master',
3242 revisionId=None,
3243 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003244
3245 def PreSync(self):
3246 if self.Exists:
3247 cb = self.CurrentBranch
3248 if cb:
3249 base = self.GetBranch(cb).merge
3250 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003251 self.revisionExpr = base
3252 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003253
Martin Kelly224a31a2017-07-10 14:46:25 -07003254 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003255 """ Prepare MetaProject for manifest branch switch
3256 """
3257
3258 # detach and delete manifest branch, allowing a new
3259 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003260 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003261 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003262 syncbuf.Finish()
3263
3264 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003265 ['update-ref', '-d', 'refs/heads/default'],
3266 capture_stdout=True,
3267 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003268
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003269 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003270 def LastFetch(self):
3271 try:
3272 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3273 return os.path.getmtime(fh)
3274 except OSError:
3275 return 0
3276
3277 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003278 def HasChanges(self):
3279 """Has the remote received new commits not yet checked out?
3280 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003281 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003282 return False
3283
David Pursehouse8a68ff92012-09-24 12:15:13 +09003284 all_refs = self.bare_ref.all
3285 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003286 head = self.work_git.GetHead()
3287 if head.startswith(R_HEADS):
3288 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003289 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003290 except KeyError:
3291 head = None
3292
3293 if revid == head:
3294 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003295 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003296 return True
3297 return False