blob: 85be200e0a87887b9ac1b420968c0f9320fa7278 [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, \
Ningning Xiac2fbc782016-08-22 14:24:39 -070033 ID_RE, RefSpec
Remy Bohmer16c13282020-09-10 10:38:04 +020034from error import GitError, UploadError, DownloadError
Ningning Xiac2fbc782016-08-22 14:24:39 -070035from error import CacheApplyError
Mike Frysingere6a202f2019-08-02 15:57:57 -040036from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080037from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070038import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040039import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040040from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050042from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070043
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070044
George Engelbrecht9bc283e2020-04-02 12:36:09 -060045# Maximum sleep time allowed during retries.
46MAXIMUM_RETRY_SLEEP_SEC = 3600.0
47# +-10% random jitter is added to each Fetches retry sleep duration.
48RETRY_JITTER_PERCENT = 0.1
49
50
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070051def _lwrite(path, content):
52 lock = '%s.lock' % path
53
Remy Bohmer169b0212020-11-21 10:57:52 +010054 # Maintain Unix line endings on all OS's to match git behavior.
55 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070056 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070057
58 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070059 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070060 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080061 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 raise
63
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070064
Shawn O. Pearce48244782009-04-16 08:25:57 -070065def _error(fmt, *args):
66 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070067 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070068
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070069
David Pursehousef33929d2015-08-24 14:39:14 +090070def _warn(fmt, *args):
71 msg = fmt % args
72 print('warn: %s' % msg, file=sys.stderr)
73
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070074
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070075def not_rev(r):
76 return '^' + r
77
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070078
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080079def sq(r):
80 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080081
David Pursehouse819827a2020-02-12 15:20:19 +090082
Jonathan Nieder93719792015-03-17 11:29:58 -070083_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
85
Jonathan Nieder93719792015-03-17 11:29:58 -070086def _ProjectHooks():
87 """List the hooks present in the 'hooks' directory.
88
89 These hooks are project hooks and are copied to the '.git/hooks' directory
90 of all subprojects.
91
92 This function caches the list of hooks (based on the contents of the
93 'repo/hooks' directory) on the first call.
94
95 Returns:
96 A list of absolute paths to all of the files in the hooks directory.
97 """
98 global _project_hook_list
99 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700100 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700101 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700102 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700103 return _project_hook_list
104
105
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700106class DownloadedChange(object):
107 _commit_cache = None
108
109 def __init__(self, project, base, change_id, ps_id, commit):
110 self.project = project
111 self.base = base
112 self.change_id = change_id
113 self.ps_id = ps_id
114 self.commit = commit
115
116 @property
117 def commits(self):
118 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700119 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
120 '--abbrev-commit',
121 '--pretty=oneline',
122 '--reverse',
123 '--date-order',
124 not_rev(self.base),
125 self.commit,
126 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700127 return self._commit_cache
128
129
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700130class ReviewableBranch(object):
131 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400132 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700133
134 def __init__(self, project, branch, base):
135 self.project = project
136 self.branch = branch
137 self.base = base
138
139 @property
140 def name(self):
141 return self.branch.name
142
143 @property
144 def commits(self):
145 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400146 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
147 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
148 try:
149 self._commit_cache = self.project.bare_git.rev_list(*args)
150 except GitError:
151 # We weren't able to probe the commits for this branch. Was it tracking
152 # a branch that no longer exists? If so, return no commits. Otherwise,
153 # rethrow the error as we don't know what's going on.
154 if self.base_exists:
155 raise
156
157 self._commit_cache = []
158
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159 return self._commit_cache
160
161 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800162 def unabbrev_commits(self):
163 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700164 for commit in self.project.bare_git.rev_list(not_rev(self.base),
165 R_HEADS + self.name,
166 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800167 r[commit[0:8]] = commit
168 return r
169
170 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700172 return self.project.bare_git.log('--pretty=format:%cd',
173 '-n', '1',
174 R_HEADS + self.name,
175 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176
Mike Frysinger6da17752019-09-11 18:43:17 -0400177 @property
178 def base_exists(self):
179 """Whether the branch we're tracking exists.
180
181 Normally it should, but sometimes branches we track can get deleted.
182 """
183 if self._base_exists is None:
184 try:
185 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
186 # If we're still here, the base branch exists.
187 self._base_exists = True
188 except GitError:
189 # If we failed to verify, the base branch doesn't exist.
190 self._base_exists = False
191
192 return self._base_exists
193
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700194 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500195 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700196 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500197 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500198 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200199 private=False,
Vadim Bendebury75bcd242018-10-31 13:48:01 -0700200 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200201 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200202 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800203 validate_certs=True,
204 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500205 self.project.UploadForReview(branch=self.name,
206 people=people,
207 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700208 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500209 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500210 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200211 private=private,
Vadim Bendebury75bcd242018-10-31 13:48:01 -0700212 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200213 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200214 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800215 validate_certs=validate_certs,
216 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700218 def GetPublishedRefs(self):
219 refs = {}
220 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700221 self.branch.remote.SshReviewUrl(self.project.UserEmail),
222 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700223 for line in output.split('\n'):
224 try:
225 (sha, ref) = line.split()
226 refs[sha] = ref
227 except ValueError:
228 pass
229
230 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700232
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700234
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500236 super().__init__(config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100237 self.project = self.printer('header', attr='bold')
238 self.branch = self.printer('header', attr='bold')
239 self.nobranch = self.printer('nobranch', fg='red')
240 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241
Anthony King7bdac712014-07-16 12:56:40 +0100242 self.added = self.printer('added', fg='green')
243 self.changed = self.printer('changed', fg='red')
244 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245
246
247class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700248
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700249 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500250 super().__init__(config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100251 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400252 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700254
Anthony King7bdac712014-07-16 12:56:40 +0100255class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700256
James W. Mills24c13082012-04-12 15:04:13 -0500257 def __init__(self, name, value, keep):
258 self.name = name
259 self.value = value
260 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700261
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
Mike Frysingere6a202f2019-08-02 15:57:57 -0400263def _SafeExpandPath(base, subpath, skipfinal=False):
264 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700265
Mike Frysingere6a202f2019-08-02 15:57:57 -0400266 We make sure no intermediate symlinks are traversed, and that the final path
267 is not a special file (e.g. not a socket or fifo).
268
269 NB: We rely on a number of paths already being filtered out while parsing the
270 manifest. See the validation logic in manifest_xml.py for more details.
271 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500272 # Split up the path by its components. We can't use os.path.sep exclusively
273 # as some platforms (like Windows) will convert / to \ and that bypasses all
274 # our constructed logic here. Especially since manifest authors only use
275 # / in their paths.
276 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
277 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400278 if skipfinal:
279 # Whether the caller handles the final component itself.
280 finalpart = components.pop()
281
282 path = base
283 for part in components:
284 if part in {'.', '..'}:
285 raise ManifestInvalidPathError(
286 '%s: "%s" not allowed in paths' % (subpath, part))
287
288 path = os.path.join(path, part)
289 if platform_utils.islink(path):
290 raise ManifestInvalidPathError(
291 '%s: traversing symlinks not allow' % (path,))
292
293 if os.path.exists(path):
294 if not os.path.isfile(path) and not platform_utils.isdir(path):
295 raise ManifestInvalidPathError(
296 '%s: only regular files & directories allowed' % (path,))
297
298 if skipfinal:
299 path = os.path.join(path, finalpart)
300
301 return path
302
303
304class _CopyFile(object):
305 """Container for <copyfile> manifest element."""
306
307 def __init__(self, git_worktree, src, topdir, dest):
308 """Register a <copyfile> request.
309
310 Args:
311 git_worktree: Absolute path to the git project checkout.
312 src: Relative path under |git_worktree| of file to read.
313 topdir: Absolute path to the top of the repo client checkout.
314 dest: Relative path under |topdir| of file to write.
315 """
316 self.git_worktree = git_worktree
317 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700318 self.src = src
319 self.dest = dest
320
321 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400322 src = _SafeExpandPath(self.git_worktree, self.src)
323 dest = _SafeExpandPath(self.topdir, self.dest)
324
325 if platform_utils.isdir(src):
326 raise ManifestInvalidPathError(
327 '%s: copying from directory not supported' % (self.src,))
328 if platform_utils.isdir(dest):
329 raise ManifestInvalidPathError(
330 '%s: copying to directory not allowed' % (self.dest,))
331
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700332 # copy file if it does not exist or is out of date
333 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
334 try:
335 # remove existing file first, since it might be read-only
336 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800337 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400338 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200339 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700340 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200341 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700342 shutil.copy(src, dest)
343 # make the file read-only
344 mode = os.stat(dest)[stat.ST_MODE]
345 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
346 os.chmod(dest, mode)
347 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700348 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700349
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700350
Anthony King7bdac712014-07-16 12:56:40 +0100351class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400352 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700353
Mike Frysingere6a202f2019-08-02 15:57:57 -0400354 def __init__(self, git_worktree, src, topdir, dest):
355 """Register a <linkfile> request.
356
357 Args:
358 git_worktree: Absolute path to the git project checkout.
359 src: Target of symlink relative to path under |git_worktree|.
360 topdir: Absolute path to the top of the repo client checkout.
361 dest: Relative path under |topdir| of symlink to create.
362 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700363 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400364 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500365 self.src = src
366 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500367
Wink Saville4c426ef2015-06-03 08:05:17 -0700368 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500369 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700370 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500371 try:
372 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800373 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800374 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500375 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700376 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700377 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500378 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700379 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500380 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700381 _error('Cannot link file %s to %s', relSrc, absDest)
382
383 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400384 """Link the self.src & self.dest paths.
385
386 Handles wild cards on the src linking all of the files in the source in to
387 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700388 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500389 # Some people use src="." to create stable links to projects. Lets allow
390 # that but reject all other uses of "." to keep things simple.
391 if self.src == '.':
392 src = self.git_worktree
393 else:
394 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400395
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300396 if not glob.has_magic(src):
397 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400398 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
399 # dest & src are absolute paths at this point. Make sure the target of
400 # the symlink is relative in the context of the repo client checkout.
401 relpath = os.path.relpath(src, os.path.dirname(dest))
402 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700403 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400404 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300405 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400406 if os.path.exists(dest) and not platform_utils.isdir(dest):
407 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700408 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400409 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700410 # Create a releative path from source dir to destination dir
411 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400412 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700413
414 # Get the source file name
415 srcFile = os.path.basename(absSrcFile)
416
417 # Now form the final full paths to srcFile. They will be
418 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400419 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700420 relSrc = os.path.join(relSrcDir, srcFile)
421 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500422
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700423
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700424class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700425
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700426 def __init__(self,
427 name,
Anthony King7bdac712014-07-16 12:56:40 +0100428 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700429 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100430 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700431 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700432 orig_name=None,
433 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700434 self.name = name
435 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700436 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700437 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100438 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700439 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700440 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700441
Ian Kasprzak0286e312021-02-05 10:06:18 -0800442
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700443class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600444 # These objects can be shared between several working trees.
445 shareable_files = ['description', 'info']
446 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
447 # These objects can only be used by a single working tree.
448 working_tree_files = ['config', 'packed-refs', 'shallow']
449 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700450
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700451 def __init__(self,
452 manifest,
453 name,
454 remote,
455 gitdir,
David James8d201162013-10-11 17:03:19 -0700456 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700457 worktree,
458 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700459 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800460 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100461 rebase=True,
462 groups=None,
463 sync_c=False,
464 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900465 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100466 clone_depth=None,
467 upstream=None,
468 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500469 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100470 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900471 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700472 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600473 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700474 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800475 """Init a Project object.
476
477 Args:
478 manifest: The XmlManifest object.
479 name: The `name` attribute of manifest.xml's project element.
480 remote: RemoteSpec object specifying its remote's properties.
481 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700482 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800483 worktree: Absolute path of git working tree.
484 relpath: Relative path of git working tree to repo's top directory.
485 revisionExpr: The `revision` attribute of manifest.xml's project element.
486 revisionId: git commit id for checking out.
487 rebase: The `rebase` attribute of manifest.xml's project element.
488 groups: The `groups` attribute of manifest.xml's project element.
489 sync_c: The `sync-c` attribute of manifest.xml's project element.
490 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900491 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800492 upstream: The `upstream` attribute of manifest.xml's project element.
493 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500494 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800495 is_derived: False if the project was explicitly defined in the manifest;
496 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400497 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900498 optimized_fetch: If True, when a project is set to a sha1 revision, only
499 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600500 retry_fetches: Retry remote fetches n times upon receiving transient error
501 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700502 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800503 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400504 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700505 self.name = name
506 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800507 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700508 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800509 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700510 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800511 else:
512 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700513 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700514 self.revisionExpr = revisionExpr
515
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700516 if revisionId is None \
517 and revisionExpr \
518 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700519 self.revisionId = revisionExpr
520 else:
521 self.revisionId = revisionId
522
Mike Pontillod3153822012-02-28 11:53:24 -0800523 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700524 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700525 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800526 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900527 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900528 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700529 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800530 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500531 # NB: Do not use this setting in __init__ to change behavior so that the
532 # manifest.git checkout can inspect & change it after instantiating. See
533 # the XmlManifest init code for more info.
534 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800535 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900536 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600537 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800538 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800539
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700541 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500542 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500543 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700544 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400545 defaults=self.client.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700546
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800547 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700548 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800549 else:
550 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700551 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700552 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700553 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400554 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700555 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700556
Doug Anderson37282b42011-03-04 11:54:18 -0800557 # This will be filled in if a project is later identified to be the
558 # project containing repo hooks.
559 self.enabled_repo_hooks = []
560
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700561 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800562 def Derived(self):
563 return self.is_derived
564
565 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700566 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700567 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700568
569 @property
570 def CurrentBranch(self):
571 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400572
573 The branch name omits the 'refs/heads/' prefix.
574 None is returned if the project is on a detached HEAD, or if the work_git is
575 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700576 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400577 try:
578 b = self.work_git.GetHead()
579 except NoManifestException:
580 # If the local checkout is in a bad state, don't barf. Let the callers
581 # process this like the head is unreadable.
582 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583 if b.startswith(R_HEADS):
584 return b[len(R_HEADS):]
585 return None
586
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700587 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500588 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
589 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
590 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200591
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700592 def IsDirty(self, consider_untracked=True):
593 """Is the working directory modified in some way?
594 """
595 self.work_git.update_index('-q',
596 '--unmerged',
597 '--ignore-missing',
598 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900599 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700600 return True
601 if self.work_git.DiffZ('diff-files'):
602 return True
603 if consider_untracked and self.work_git.LsOthers():
604 return True
605 return False
606
607 _userident_name = None
608 _userident_email = None
609
610 @property
611 def UserName(self):
612 """Obtain the user's personal name.
613 """
614 if self._userident_name is None:
615 self._LoadUserIdentity()
616 return self._userident_name
617
618 @property
619 def UserEmail(self):
620 """Obtain the user's email address. This is very likely
621 to be their Gerrit login.
622 """
623 if self._userident_email is None:
624 self._LoadUserIdentity()
625 return self._userident_email
626
627 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900628 u = self.bare_git.var('GIT_COMMITTER_IDENT')
629 m = re.compile("^(.*) <([^>]*)> ").match(u)
630 if m:
631 self._userident_name = m.group(1)
632 self._userident_email = m.group(2)
633 else:
634 self._userident_name = ''
635 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700636
637 def GetRemote(self, name):
638 """Get the configuration for a single remote.
639 """
640 return self.config.GetRemote(name)
641
642 def GetBranch(self, name):
643 """Get the configuration for a single branch.
644 """
645 return self.config.GetBranch(name)
646
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700647 def GetBranches(self):
648 """Get all existing local branches.
649 """
650 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900651 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700652 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700653
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530654 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700655 if name.startswith(R_HEADS):
656 name = name[len(R_HEADS):]
657 b = self.GetBranch(name)
658 b.current = name == current
659 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900660 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700661 heads[name] = b
662
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530663 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700664 if name.startswith(R_PUB):
665 name = name[len(R_PUB):]
666 b = heads.get(name)
667 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900668 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700669
670 return heads
671
Colin Cross5acde752012-03-28 20:15:45 -0700672 def MatchesGroups(self, manifest_groups):
673 """Returns true if the manifest groups specified at init should cause
674 this project to be synced.
675 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700676 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700677
678 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700679 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700680 manifest_groups: "-group1,group2"
681 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500682
683 The special manifest group "default" will match any project that
684 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700685 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500686 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700687 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700688 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500689 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700690
Conley Owens971de8e2012-04-16 10:36:08 -0700691 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700692 for group in expanded_manifest_groups:
693 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700694 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700695 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700696 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700697
Conley Owens971de8e2012-04-16 10:36:08 -0700698 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700699
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700700# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700701 def UncommitedFiles(self, get_all=True):
702 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700703
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700704 Args:
705 get_all: a boolean, if True - get information about all different
706 uncommitted files. If False - return as soon as any kind of
707 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500708 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700709 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500710 self.work_git.update_index('-q',
711 '--unmerged',
712 '--ignore-missing',
713 '--refresh')
714 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700715 details.append("rebase in progress")
716 if not get_all:
717 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500718
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700719 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
720 if changes:
721 details.extend(changes)
722 if not get_all:
723 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500724
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700725 changes = self.work_git.DiffZ('diff-files').keys()
726 if changes:
727 details.extend(changes)
728 if not get_all:
729 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500730
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700731 changes = self.work_git.LsOthers()
732 if changes:
733 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500734
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700735 return details
736
737 def HasChanges(self):
738 """Returns true if there are uncommitted changes.
739 """
740 if self.UncommitedFiles(get_all=False):
741 return True
742 else:
743 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500744
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600745 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700746 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200747
748 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200749 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600750 quiet: If True then only print the project name. Do not print
751 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700752 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700753 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700754 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200755 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700756 print(file=output_redir)
757 print('project %s/' % self.relpath, file=output_redir)
758 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759 return
760
761 self.work_git.update_index('-q',
762 '--unmerged',
763 '--ignore-missing',
764 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700765 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700766 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
767 df = self.work_git.DiffZ('diff-files')
768 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100769 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700770 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700771
772 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700773 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200774 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700775 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700776
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600777 if quiet:
778 out.nl()
779 return 'DIRTY'
780
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700781 branch = self.CurrentBranch
782 if branch is None:
783 out.nobranch('(*** NO BRANCH ***)')
784 else:
785 out.branch('branch %s', branch)
786 out.nl()
787
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700788 if rb:
789 out.important('prior sync failed; rebase still in progress')
790 out.nl()
791
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700792 paths = list()
793 paths.extend(di.keys())
794 paths.extend(df.keys())
795 paths.extend(do)
796
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530797 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900798 try:
799 i = di[p]
800 except KeyError:
801 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700802
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900803 try:
804 f = df[p]
805 except KeyError:
806 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200807
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900808 if i:
809 i_status = i.status.upper()
810 else:
811 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700812
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900813 if f:
814 f_status = f.status.lower()
815 else:
816 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700817
818 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800819 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700820 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700821 else:
822 line = ' %s%s\t%s' % (i_status, f_status, p)
823
824 if i and not f:
825 out.added('%s', line)
826 elif (i and f) or (not i and f):
827 out.changed('%s', line)
828 elif not i and not f:
829 out.untracked('%s', line)
830 else:
831 out.write('%s', line)
832 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200833
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700834 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500836 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700837 """Prints the status of the repository to stdout.
838 """
839 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500840 if output_redir:
841 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700842 cmd = ['diff']
843 if out.is_on:
844 cmd.append('--color')
845 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300846 if absolute_paths:
847 cmd.append('--src-prefix=a/%s/' % self.relpath)
848 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400850 try:
851 p = GitCommand(self,
852 cmd,
853 capture_stdout=True,
854 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500855 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400856 except GitError as e:
857 out.nl()
858 out.project('project %s/' % self.relpath)
859 out.nl()
860 out.fail('%s', str(e))
861 out.nl()
862 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500863 if p.stdout:
864 out.nl()
865 out.project('project %s/' % self.relpath)
866 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500867 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400868 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700870# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900871 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872 """Was the branch published (uploaded) for code review?
873 If so, returns the SHA-1 hash of the last published
874 state for the branch.
875 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700876 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900877 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700878 try:
879 return self.bare_git.rev_parse(key)
880 except GitError:
881 return None
882 else:
883 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900884 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700885 except KeyError:
886 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700887
David Pursehouse8a68ff92012-09-24 12:15:13 +0900888 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889 """Prunes any stale published refs.
890 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900891 if all_refs is None:
892 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893 heads = set()
894 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530895 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896 if name.startswith(R_HEADS):
897 heads.add(name)
898 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900899 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530901 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902 n = name[len(R_PUB):]
903 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900904 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700906 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700907 """List any branches which can be uploaded for review.
908 """
909 heads = {}
910 pubed = {}
911
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530912 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900914 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700915 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900916 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917
918 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530919 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900920 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700922 if selected_branch and branch != selected_branch:
923 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800925 rb = self.GetUploadableBranch(branch)
926 if rb:
927 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928 return ready
929
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800930 def GetUploadableBranch(self, branch_name):
931 """Get a single uploadable branch, or None.
932 """
933 branch = self.GetBranch(branch_name)
934 base = branch.LocalMerge
935 if branch.LocalMerge:
936 rb = ReviewableBranch(self, branch, base)
937 if rb.commits:
938 return rb
939 return None
940
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700941 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100942 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500943 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700944 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500945 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500946 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200947 private=False,
Vadim Bendebury75bcd242018-10-31 13:48:01 -0700948 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200949 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200950 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800951 validate_certs=True,
952 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953 """Uploads the named branch for code review.
954 """
955 if branch is None:
956 branch = self.CurrentBranch
957 if branch is None:
958 raise GitError('not currently on a branch')
959
960 branch = self.GetBranch(branch)
961 if not branch.LocalMerge:
962 raise GitError('branch %s does not track a remote' % branch.name)
963 if not branch.remote.review:
964 raise GitError('remote %s has no review url' % branch.remote.name)
965
Bryan Jacobsf609f912013-05-06 13:36:24 -0400966 if dest_branch is None:
967 dest_branch = self.dest_branch
968 if dest_branch is None:
969 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970 if not dest_branch.startswith(R_HEADS):
971 dest_branch = R_HEADS + dest_branch
972
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800973 if not branch.remote.projectname:
974 branch.remote.projectname = self.name
975 branch.remote.Save()
976
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200977 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800978 if url is None:
979 raise UploadError('review not configured')
980 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -0500981 if dryrun:
982 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800983
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800984 if url.startswith('ssh://'):
Jonathan Nieder81df7e12018-11-05 13:21:52 -0800985 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700986
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800987 for push_option in (push_options or []):
988 cmd.append('-o')
989 cmd.append(push_option)
990
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800991 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800992
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800993 if dest_branch.startswith(R_HEADS):
994 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700995
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -0500996 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -0800997 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800998 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -0800999 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001000 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001001 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001002
David Pursehousef25a3702018-11-14 19:01:22 -08001003 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder81df7e12018-11-05 13:21:52 -08001004 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendebury75bcd242018-10-31 13:48:01 -07001005 if notify:
1006 opts += ['notify=' + notify]
Jonathan Nieder81df7e12018-11-05 13:21:52 -08001007 if private:
1008 opts += ['private']
1009 if wip:
1010 opts += ['wip']
1011 if opts:
1012 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001013 cmd.append(ref_spec)
1014
Anthony King7bdac712014-07-16 12:56:40 +01001015 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001016 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017
Mike Frysingerd7f86832020-11-19 19:18:46 -05001018 if not dryrun:
1019 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1020 self.bare_git.UpdateRef(R_PUB + branch.name,
1021 R_HEADS + branch.name,
1022 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001024# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001025 def _ExtractArchive(self, tarpath, path=None):
1026 """Extract the given tar on its current location
1027
1028 Args:
1029 - tarpath: The path to the actual tar file
1030
1031 """
1032 try:
1033 with tarfile.open(tarpath, 'r') as tar:
1034 tar.extractall(path=path)
1035 return True
1036 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001037 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001038 return False
1039
Ningning Xiac2fbc782016-08-22 14:24:39 -07001040 def CachePopulate(self, cache_dir, url):
1041 """Populate cache in the cache_dir.
1042
1043 Args:
1044 cache_dir: Directory to cache git files from Google Storage.
1045 url: Git url of current repository.
1046
1047 Raises:
1048 CacheApplyError if it fails to populate the git cache.
1049 """
1050 cmd = ['cache', 'populate', '--ignore_locks', '-v',
1051 '--cache-dir', cache_dir, url]
1052
1053 if GitCommand(self, cmd, cwd=cache_dir).Wait() != 0:
1054 raise CacheApplyError('Failed to populate cache. cache_dir: %s '
1055 'url: %s' % (cache_dir, url))
1056
1057 def CacheExists(self, cache_dir, url):
1058 """Check the existence of the cache files.
1059
1060 Args:
1061 cache_dir: Directory to cache git files.
1062 url: Git url of current repository.
1063
1064 Raises:
1065 CacheApplyError if the cache files do not exist.
1066 """
1067 cmd = ['cache', 'exists', '--quiet', '--cache-dir', cache_dir, url]
1068
1069 exist = GitCommand(self, cmd, cwd=self.gitdir, capture_stdout=True)
1070 if exist.Wait() != 0:
1071 raise CacheApplyError('Failed to execute git cache exists cmd. '
1072 'cache_dir: %s url: %s' % (cache_dir, url))
1073
1074 if not exist.stdout or not exist.stdout.strip():
1075 raise CacheApplyError('Failed to find cache. cache_dir: %s '
1076 'url: %s' % (cache_dir, url))
1077 return exist.stdout.strip()
1078
1079 def CacheApply(self, cache_dir):
1080 """Apply git cache files populated from Google Storage buckets.
1081
1082 Args:
1083 cache_dir: Directory to cache git files.
1084
1085 Raises:
1086 CacheApplyError if it fails to apply git caches.
1087 """
1088 remote = self.GetRemote(self.remote.name)
1089
1090 self.CachePopulate(cache_dir, remote.url)
1091
1092 mirror_dir = self.CacheExists(cache_dir, remote.url)
1093
1094 refspec = RefSpec(True, 'refs/heads/*',
1095 'refs/remotes/%s/*' % remote.name)
1096
1097 fetch_cache_cmd = ['fetch', mirror_dir, str(refspec)]
1098 if GitCommand(self, fetch_cache_cmd, self.gitdir).Wait() != 0:
1099 raise CacheApplyError('Failed to fetch refs %s from %s' %
1100 (mirror_dir, str(refspec)))
1101
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001102 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001103 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001104 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001105 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001106 is_new=None,
1107 current_branch_only=False,
1108 force_sync=False,
1109 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001110 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001111 archive=False,
1112 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001113 retry_fetches=0,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001114 prune=False,
Mike Frysingerde72f6a2018-12-13 03:20:31 -05001115 submodules=False,
Mike Frysingerea6d8692021-04-19 12:52:52 -04001116 cache_dir=None,
Mike Frysinger39ba6312019-07-27 12:45:51 -04001117 clone_filter=None,
Raman Tennetif32f2432021-04-12 20:57:25 -07001118 partial_clone_exclude=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001119 """Perform only the network IO portion of the sync process.
1120 Local working directory/branch state is not affected.
1121 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001122 if archive and not isinstance(self, MetaProject):
1123 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001124 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001125 return False
1126
1127 name = self.relpath.replace('\\', '/')
1128 name = name.replace('/', '_')
1129 tarpath = '%s.tar' % name
1130 topdir = self.manifest.topdir
1131
1132 try:
1133 self._FetchArchive(tarpath, cwd=topdir)
1134 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001135 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001136 return False
1137
1138 # From now on, we only need absolute tarpath
1139 tarpath = os.path.join(topdir, tarpath)
1140
1141 if not self._ExtractArchive(tarpath, path=topdir):
1142 return False
1143 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001144 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001145 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001146 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001147 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001148 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001149
1150 # If the shared object dir already exists, don't try to rebootstrap with a
1151 # clone bundle download. We should have the majority of objects already.
1152 if clone_bundle and os.path.exists(self.objdir):
1153 clone_bundle = False
1154
Raman Tennetif32f2432021-04-12 20:57:25 -07001155 if self.name in partial_clone_exclude:
1156 clone_bundle = True
1157 clone_filter = None
1158
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001159 if is_new is None:
1160 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001161 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001162 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001163 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001164 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001165 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001166
1167 if is_new:
1168 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1169 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001170 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001171 # This works for both absolute and relative alternate directories.
1172 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001173 except IOError:
1174 alt_dir = None
1175 else:
1176 alt_dir = None
1177
Ningning Xiac2fbc782016-08-22 14:24:39 -07001178 applied_cache = False
1179 # If cache_dir is provided, and it's a new repository without
1180 # alternative_dir, bootstrap this project repo with the git
1181 # cache files.
1182 if cache_dir is not None and is_new and alt_dir is None:
1183 try:
1184 self.CacheApply(cache_dir)
1185 applied_cache = True
1186 is_new = False
1187 except CacheApplyError as e:
1188 _error('Could not apply git cache: %s', e)
1189 _error('Please check if you have the right GS credentials.')
1190 _error('Please check if the cache files exist in GS.')
1191
Mike Frysingere50b6a72020-02-19 01:45:48 -05001192 if (clone_bundle
Mike Frysingerd70c6072020-02-25 10:22:02 -05001193 and not applied_cache
Mike Frysingere50b6a72020-02-19 01:45:48 -05001194 and alt_dir is None
1195 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001196 is_new = False
1197
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001198 if not current_branch_only:
1199 if self.sync_c:
1200 current_branch_only = True
1201 elif not self.manifest._loaded:
1202 # Manifest cannot check defaults until it syncs.
1203 current_branch_only = False
1204 elif self.manifest.default.sync_c:
1205 current_branch_only = True
1206
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001207 if not self.sync_tags:
1208 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001209
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001210 if self.clone_depth:
1211 depth = self.clone_depth
1212 else:
1213 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1214
Mike Frysinger521d01b2020-02-17 01:51:49 -05001215 # See if we can skip the network fetch entirely.
1216 if not (optimized_fetch and
1217 (ID_RE.match(self.revisionExpr) and
1218 self._CheckForImmutableRevision())):
1219 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001220 initial=is_new,
1221 quiet=quiet, verbose=verbose, output_redir=output_redir,
1222 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001223 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001224 submodules=submodules, force_sync=force_sync,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001225 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001226 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001227
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001228 mp = self.manifest.manifestProject
1229 dissociate = mp.config.GetBoolean('repo.dissociate')
1230 if dissociate:
1231 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1232 if os.path.exists(alternates_file):
1233 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001234 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1235 merge_output=bool(output_redir))
1236 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001237 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001238 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001239 return False
1240 platform_utils.remove(alternates_file)
1241
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001242 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001243 self._InitMRef()
1244 else:
1245 self._InitMirrorHead()
1246 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001247 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001248 except OSError:
1249 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001250 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001251
1252 def PostRepoUpgrade(self):
1253 self._InitHooks()
1254
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001255 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001256 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001257 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001258 for copyfile in self.copyfiles:
1259 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001260 for linkfile in self.linkfiles:
1261 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001262
Julien Camperguedd654222014-01-09 16:21:37 +01001263 def GetCommitRevisionId(self):
1264 """Get revisionId of a commit.
1265
1266 Use this method instead of GetRevisionId to get the id of the commit rather
1267 than the id of the current git object (for example, a tag)
1268
1269 """
1270 if not self.revisionExpr.startswith(R_TAGS):
1271 return self.GetRevisionId(self._allrefs)
1272
1273 try:
1274 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1275 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001276 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1277 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001278
David Pursehouse8a68ff92012-09-24 12:15:13 +09001279 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001280 if self.revisionId:
1281 return self.revisionId
1282
1283 rem = self.GetRemote(self.remote.name)
1284 rev = rem.ToLocal(self.revisionExpr)
1285
David Pursehouse8a68ff92012-09-24 12:15:13 +09001286 if all_refs is not None and rev in all_refs:
1287 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001288
1289 try:
1290 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1291 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001292 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1293 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001294
Raman Tenneti6a872c92021-01-14 19:17:50 -08001295 def SetRevisionId(self, revisionId):
1296 self.revisionId = revisionId
1297
Martin Kellye4e94d22017-03-21 16:05:12 -07001298 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299 """Perform only the local IO portion of the sync process.
1300 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001302 if not os.path.exists(self.gitdir):
1303 syncbuf.fail(self,
1304 'Cannot checkout %s due to missing network sync; Run '
1305 '`repo sync -n %s` first.' %
1306 (self.name, self.name))
1307 return
1308
Martin Kellye4e94d22017-03-21 16:05:12 -07001309 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001310 all_refs = self.bare_ref.all
1311 self.CleanPublishedCache(all_refs)
1312 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001313
Mike Frysinger0458faa2021-03-10 23:35:44 -05001314 # Special case the root of the repo client checkout. Make sure it doesn't
1315 # contain files being checked out to dirs we don't allow.
1316 if self.relpath == '.':
1317 PROTECTED_PATHS = {'.repo'}
1318 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1319 bad_paths = paths & PROTECTED_PATHS
1320 if bad_paths:
1321 syncbuf.fail(self,
1322 'Refusing to checkout project that writes to protected '
1323 'paths: %s' % (', '.join(bad_paths),))
1324 return
1325
David Pursehouse1d947b32012-10-25 12:23:11 +09001326 def _doff():
1327 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001328 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001329
Martin Kellye4e94d22017-03-21 16:05:12 -07001330 def _dosubmodules():
1331 self._SyncSubmodules(quiet=True)
1332
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001333 head = self.work_git.GetHead()
1334 if head.startswith(R_HEADS):
1335 branch = head[len(R_HEADS):]
1336 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001337 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001338 except KeyError:
1339 head = None
1340 else:
1341 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001343 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344 # Currently on a detached HEAD. The user is assumed to
1345 # not have any local modifications worth worrying about.
1346 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001347 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001348 syncbuf.fail(self, _PriorSyncFailedError())
1349 return
1350
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001351 if head == revid:
1352 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001353 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001354 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001355 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001356 # The copy/linkfile config may have changed.
1357 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001358 return
1359 else:
1360 lost = self._revlist(not_rev(revid), HEAD)
1361 if lost:
1362 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001363
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001365 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001366 if submodules:
1367 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001368 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001369 syncbuf.fail(self, e)
1370 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001371 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001372 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001374 if head == revid:
1375 # No changes; don't do anything further.
1376 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001377 # The copy/linkfile config may have changed.
1378 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001379 return
1380
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001382
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001383 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001384 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001385 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001386 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001387 syncbuf.info(self,
1388 "leaving %s; does not track upstream",
1389 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001390 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001391 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001392 if submodules:
1393 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001394 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001395 syncbuf.fail(self, e)
1396 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001397 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001398 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001400 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001401
1402 # See if we can perform a fast forward merge. This can happen if our
1403 # branch isn't in the exact same state as we last published.
1404 try:
1405 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1406 # Skip the published logic.
1407 pub = False
1408 except GitError:
1409 pub = self.WasPublished(branch.name, all_refs)
1410
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001411 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001412 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001413 if not_merged:
1414 if upstream_gain:
1415 # The user has published this branch and some of those
1416 # commits are not yet merged upstream. We do not want
1417 # to rewrite the published commits so we punt.
1418 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001419 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001420 "branch %s is published (but not merged) and is now "
1421 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001422 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001423 elif pub == head:
1424 # All published commits are merged, and thus we are a
1425 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001426 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001427 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001428 if submodules:
1429 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001430 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001431
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001432 # Examine the local commits not in the remote. Find the
1433 # last one attributed to this user, if any.
1434 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001435 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001436 last_mine = None
1437 cnt_mine = 0
1438 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001439 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001440 if committer_email == self.UserEmail:
1441 last_mine = commit_id
1442 cnt_mine += 1
1443
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001444 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001445 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001446
1447 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001448 syncbuf.fail(self, _DirtyError())
1449 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001450
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001451 # If the upstream switched on us, warn the user.
1452 #
1453 if branch.merge != self.revisionExpr:
1454 if branch.merge and self.revisionExpr:
1455 syncbuf.info(self,
1456 'manifest switched %s...%s',
1457 branch.merge,
1458 self.revisionExpr)
1459 elif branch.merge:
1460 syncbuf.info(self,
1461 'manifest no longer tracks %s',
1462 branch.merge)
1463
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001464 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001465 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001466 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001467 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001468 syncbuf.info(self,
1469 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001470 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001471
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001472 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001473 if not ID_RE.match(self.revisionExpr):
1474 # in case of manifest sync the revisionExpr might be a SHA1
1475 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001476 if not branch.merge.startswith('refs/'):
1477 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001478 branch.Save()
1479
Mike Pontillod3153822012-02-28 11:53:24 -08001480 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001481 def _docopyandlink():
1482 self._CopyAndLinkFiles()
1483
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001484 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001485 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001486 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001487 if submodules:
1488 syncbuf.later2(self, _dosubmodules)
1489 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001490 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001492 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001493 if submodules:
1494 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001495 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001496 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001497 syncbuf.fail(self, e)
1498 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001499 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001500 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001501 if submodules:
1502 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001503
Mike Frysingere6a202f2019-08-02 15:57:57 -04001504 def AddCopyFile(self, src, dest, topdir):
1505 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001506
Mike Frysingere6a202f2019-08-02 15:57:57 -04001507 No filesystem changes occur here. Actual copying happens later on.
1508
1509 Paths should have basic validation run on them before being queued.
1510 Further checking will be handled when the actual copy happens.
1511 """
1512 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1513
1514 def AddLinkFile(self, src, dest, topdir):
1515 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1516
1517 No filesystem changes occur here. Actual linking happens later on.
1518
1519 Paths should have basic validation run on them before being queued.
1520 Further checking will be handled when the actual link happens.
1521 """
1522 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001523
James W. Mills24c13082012-04-12 15:04:13 -05001524 def AddAnnotation(self, name, value, keep):
1525 self.annotations.append(_Annotation(name, value, keep))
1526
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001527 def DownloadPatchSet(self, change_id, patch_id):
1528 """Download a single patch set of a single change to FETCH_HEAD.
1529 """
1530 remote = self.GetRemote(self.remote.name)
1531
1532 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001533 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001534 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001535 if GitCommand(self, cmd, bare=True).Wait() != 0:
1536 return None
1537 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001538 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001539 change_id,
1540 patch_id,
1541 self.bare_git.rev_parse('FETCH_HEAD'))
1542
Mike Frysingerc0d18662020-02-19 19:19:18 -05001543 def DeleteWorktree(self, quiet=False, force=False):
1544 """Delete the source checkout and any other housekeeping tasks.
Mike Frysingerf914edc2020-02-09 03:01:56 -05001545
Mike Frysingerc0d18662020-02-19 19:19:18 -05001546 This currently leaves behind the internal .repo/ cache state. This helps
1547 when switching branches or manifest changes get reverted as we don't have
1548 to redownload all the git objects. But we should do some GC at some point.
1549
1550 Args:
1551 quiet: Whether to hide normal messages.
1552 force: Always delete tree even if dirty.
1553
1554 Returns:
1555 True if the worktree was completely cleaned out.
1556 """
1557 if self.IsDirty():
1558 if force:
1559 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1560 (self.relpath,), file=sys.stderr)
1561 else:
1562 print('error: %s: Cannot remove project: uncommitted changes are '
1563 'present.\n' % (self.relpath,), file=sys.stderr)
1564 return False
1565
1566 if not quiet:
1567 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1568
1569 # Unlock and delink from the main worktree. We don't use git's worktree
1570 # remove because it will recursively delete projects -- we handle that
1571 # ourselves below. https://crbug.com/git/48
1572 if self.use_git_worktrees:
1573 needle = platform_utils.realpath(self.gitdir)
1574 # Find the git worktree commondir under .repo/worktrees/.
1575 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1576 assert output.startswith('worktree '), output
1577 commondir = output[9:]
1578 # Walk each of the git worktrees to see where they point.
1579 configs = os.path.join(commondir, 'worktrees')
1580 for name in os.listdir(configs):
1581 gitdir = os.path.join(configs, name, 'gitdir')
1582 with open(gitdir) as fp:
1583 relpath = fp.read().strip()
1584 # Resolve the checkout path and see if it matches this project.
1585 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1586 if fullpath == needle:
1587 platform_utils.rmtree(os.path.join(configs, name))
1588
1589 # Delete the .git directory first, so we're less likely to have a partially
1590 # working git repository around. There shouldn't be any git projects here,
1591 # so rmtree works.
1592
1593 # Try to remove plain files first in case of git worktrees. If this fails
1594 # for any reason, we'll fall back to rmtree, and that'll display errors if
1595 # it can't remove things either.
1596 try:
1597 platform_utils.remove(self.gitdir)
1598 except OSError:
1599 pass
1600 try:
1601 platform_utils.rmtree(self.gitdir)
1602 except OSError as e:
1603 if e.errno != errno.ENOENT:
1604 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1605 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1606 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1607 return False
1608
1609 # Delete everything under the worktree, except for directories that contain
1610 # another git project.
1611 dirs_to_remove = []
1612 failed = False
1613 for root, dirs, files in platform_utils.walk(self.worktree):
1614 for f in files:
1615 path = os.path.join(root, f)
1616 try:
1617 platform_utils.remove(path)
1618 except OSError as e:
1619 if e.errno != errno.ENOENT:
1620 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1621 failed = True
1622 dirs[:] = [d for d in dirs
1623 if not os.path.lexists(os.path.join(root, d, '.git'))]
1624 dirs_to_remove += [os.path.join(root, d) for d in dirs
1625 if os.path.join(root, d) not in dirs_to_remove]
1626 for d in reversed(dirs_to_remove):
1627 if platform_utils.islink(d):
1628 try:
1629 platform_utils.remove(d)
1630 except OSError as e:
1631 if e.errno != errno.ENOENT:
1632 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1633 failed = True
1634 elif not platform_utils.listdir(d):
1635 try:
1636 platform_utils.rmdir(d)
1637 except OSError as e:
1638 if e.errno != errno.ENOENT:
1639 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1640 failed = True
1641 if failed:
1642 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1643 file=sys.stderr)
1644 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1645 return False
1646
1647 # Try deleting parent dirs if they are empty.
1648 path = self.worktree
1649 while path != self.manifest.topdir:
1650 try:
1651 platform_utils.rmdir(path)
1652 except OSError as e:
1653 if e.errno != errno.ENOENT:
1654 break
1655 path = os.path.dirname(path)
1656
1657 return True
1658
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001659# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001660 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001661 """Create a new branch off the manifest's revision.
1662 """
Simran Basib9a1b732015-08-20 12:19:28 -07001663 if not branch_merge:
1664 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001665 head = self.work_git.GetHead()
1666 if head == (R_HEADS + name):
1667 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001668
David Pursehouse8a68ff92012-09-24 12:15:13 +09001669 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001670 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001671 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001672 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001673 capture_stdout=True,
1674 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001675
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001676 branch = self.GetBranch(name)
1677 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001678 branch.merge = branch_merge
1679 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1680 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001681
1682 if revision is None:
1683 revid = self.GetRevisionId(all_refs)
1684 else:
1685 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001686
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001687 if head.startswith(R_HEADS):
1688 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001689 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001690 except KeyError:
1691 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001692 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001693 ref = R_HEADS + name
1694 self.work_git.update_ref(ref, revid)
1695 self.work_git.symbolic_ref(HEAD, ref)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001696 branch.Save()
1697 return True
1698
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001699 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001700 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001701 capture_stdout=True,
1702 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001703 branch.Save()
1704 return True
1705 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001706
Wink Saville02d79452009-04-10 13:01:24 -07001707 def CheckoutBranch(self, name):
1708 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001709
1710 Args:
1711 name: The name of the branch to checkout.
1712
1713 Returns:
1714 True if the checkout succeeded; False if it didn't; None if the branch
1715 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001716 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001717 rev = R_HEADS + name
1718 head = self.work_git.GetHead()
1719 if head == rev:
1720 # Already on the branch
1721 #
1722 return True
Wink Saville02d79452009-04-10 13:01:24 -07001723
David Pursehouse8a68ff92012-09-24 12:15:13 +09001724 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001725 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001726 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001727 except KeyError:
1728 # Branch does not exist in this project
1729 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001730 return None
Wink Saville02d79452009-04-10 13:01:24 -07001731
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001732 if head.startswith(R_HEADS):
1733 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001734 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001735 except KeyError:
1736 head = None
1737
1738 if head == revid:
1739 # Same revision; just update HEAD to point to the new
1740 # target branch, but otherwise take no other action.
1741 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001742 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1743 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001744 return True
1745
1746 return GitCommand(self,
1747 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001748 capture_stdout=True,
1749 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001750
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001751 def AbandonBranch(self, name):
1752 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001753
1754 Args:
1755 name: The name of the branch to abandon.
1756
1757 Returns:
1758 True if the abandon succeeded; False if it didn't; None if the branch
1759 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001760 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001761 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001762 all_refs = self.bare_ref.all
1763 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001764 # Doesn't exist
1765 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001766
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001767 head = self.work_git.GetHead()
1768 if head == rev:
1769 # We can't destroy the branch while we are sitting
1770 # on it. Switch to a detached HEAD.
1771 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001772 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001773
David Pursehouse8a68ff92012-09-24 12:15:13 +09001774 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001775 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001776 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001777 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001778 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001779
1780 return GitCommand(self,
1781 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001782 capture_stdout=True,
1783 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001784
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001785 def PruneHeads(self):
1786 """Prune any topic branches already merged into upstream.
1787 """
1788 cb = self.CurrentBranch
1789 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001790 left = self._allrefs
1791 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001792 if name.startswith(R_HEADS):
1793 name = name[len(R_HEADS):]
1794 if cb is None or name != cb:
1795 kill.append(name)
1796
Mike Frysingera3794e92021-03-11 23:24:01 -05001797 # Minor optimization: If there's nothing to prune, then don't try to read
1798 # any project state.
1799 if not kill and not cb:
1800 return []
1801
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001802 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001803 if cb is not None \
1804 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001805 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001806 self.work_git.DetachHead(HEAD)
1807 kill.append(cb)
1808
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001809 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001810 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001811
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001812 try:
1813 self.bare_git.DetachHead(rev)
1814
1815 b = ['branch', '-d']
1816 b.extend(kill)
1817 b = GitCommand(self, b, bare=True,
1818 capture_stdout=True,
1819 capture_stderr=True)
1820 b.Wait()
1821 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001822 if ID_RE.match(old):
1823 self.bare_git.DetachHead(old)
1824 else:
1825 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001826 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001827
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001828 for branch in kill:
1829 if (R_HEADS + branch) not in left:
1830 self.CleanPublishedCache()
1831 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001832
1833 if cb and cb not in kill:
1834 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001835 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001836
1837 kept = []
1838 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001839 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001840 branch = self.GetBranch(branch)
1841 base = branch.LocalMerge
1842 if not base:
1843 base = rev
1844 kept.append(ReviewableBranch(self, branch, base))
1845 return kept
1846
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001847# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001848 def GetRegisteredSubprojects(self):
1849 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001850
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001851 def rec(subprojects):
1852 if not subprojects:
1853 return
1854 result.extend(subprojects)
1855 for p in subprojects:
1856 rec(p.subprojects)
1857 rec(self.subprojects)
1858 return result
1859
1860 def _GetSubmodules(self):
1861 # Unfortunately we cannot call `git submodule status --recursive` here
1862 # because the working tree might not exist yet, and it cannot be used
1863 # without a working tree in its current implementation.
1864
1865 def get_submodules(gitdir, rev):
1866 # Parse .gitmodules for submodule sub_paths and sub_urls
1867 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1868 if not sub_paths:
1869 return []
1870 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1871 # revision of submodule repository
1872 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1873 submodules = []
1874 for sub_path, sub_url in zip(sub_paths, sub_urls):
1875 try:
1876 sub_rev = sub_revs[sub_path]
1877 except KeyError:
1878 # Ignore non-exist submodules
1879 continue
1880 submodules.append((sub_rev, sub_path, sub_url))
1881 return submodules
1882
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001883 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1884 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001885
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001886 def parse_gitmodules(gitdir, rev):
1887 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1888 try:
Anthony King7bdac712014-07-16 12:56:40 +01001889 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1890 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001891 except GitError:
1892 return [], []
1893 if p.Wait() != 0:
1894 return [], []
1895
1896 gitmodules_lines = []
1897 fd, temp_gitmodules_path = tempfile.mkstemp()
1898 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001899 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001900 os.close(fd)
1901 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001902 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1903 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001904 if p.Wait() != 0:
1905 return [], []
1906 gitmodules_lines = p.stdout.split('\n')
1907 except GitError:
1908 return [], []
1909 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001910 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001911
1912 names = set()
1913 paths = {}
1914 urls = {}
1915 for line in gitmodules_lines:
1916 if not line:
1917 continue
1918 m = re_path.match(line)
1919 if m:
1920 names.add(m.group(1))
1921 paths[m.group(1)] = m.group(2)
1922 continue
1923 m = re_url.match(line)
1924 if m:
1925 names.add(m.group(1))
1926 urls[m.group(1)] = m.group(2)
1927 continue
1928 names = sorted(names)
1929 return ([paths.get(name, '') for name in names],
1930 [urls.get(name, '') for name in names])
1931
1932 def git_ls_tree(gitdir, rev, paths):
1933 cmd = ['ls-tree', rev, '--']
1934 cmd.extend(paths)
1935 try:
Anthony King7bdac712014-07-16 12:56:40 +01001936 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1937 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001938 except GitError:
1939 return []
1940 if p.Wait() != 0:
1941 return []
1942 objects = {}
1943 for line in p.stdout.split('\n'):
1944 if not line.strip():
1945 continue
1946 object_rev, object_path = line.split()[2:4]
1947 objects[object_path] = object_rev
1948 return objects
1949
1950 try:
1951 rev = self.GetRevisionId()
1952 except GitError:
1953 return []
1954 return get_submodules(self.gitdir, rev)
1955
1956 def GetDerivedSubprojects(self):
1957 result = []
1958 if not self.Exists:
1959 # If git repo does not exist yet, querying its submodules will
1960 # mess up its states; so return here.
1961 return result
1962 for rev, path, url in self._GetSubmodules():
1963 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001964 relpath, worktree, gitdir, objdir = \
1965 self.manifest.GetSubprojectPaths(self, name, path)
1966 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001967 if project:
1968 result.extend(project.GetDerivedSubprojects())
1969 continue
David James8d201162013-10-11 17:03:19 -07001970
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001971 if url.startswith('..'):
1972 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001973 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001974 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001975 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001976 review=self.remote.review,
1977 revision=self.remote.revision)
1978 subproject = Project(manifest=self.manifest,
1979 name=name,
1980 remote=remote,
1981 gitdir=gitdir,
1982 objdir=objdir,
1983 worktree=worktree,
1984 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001985 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001986 revisionId=rev,
1987 rebase=self.rebase,
1988 groups=self.groups,
1989 sync_c=self.sync_c,
1990 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001991 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001992 parent=self,
1993 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001994 result.append(subproject)
1995 result.extend(subproject.GetDerivedSubprojects())
1996 return result
1997
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001998# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001999 def EnableRepositoryExtension(self, key, value='true', version=1):
2000 """Enable git repository extension |key| with |value|.
2001
2002 Args:
2003 key: The extension to enabled. Omit the "extensions." prefix.
2004 value: The value to use for the extension.
2005 version: The minimum git repository version needed.
2006 """
2007 # Make sure the git repo version is new enough already.
2008 found_version = self.config.GetInt('core.repositoryFormatVersion')
2009 if found_version is None:
2010 found_version = 0
2011 if found_version < version:
2012 self.config.SetString('core.repositoryFormatVersion', str(version))
2013
2014 # Enable the extension!
2015 self.config.SetString('extensions.%s' % (key,), value)
2016
Mike Frysinger50a81de2020-09-06 15:51:21 -04002017 def ResolveRemoteHead(self, name=None):
2018 """Find out what the default branch (HEAD) points to.
2019
2020 Normally this points to refs/heads/master, but projects are moving to main.
2021 Support whatever the server uses rather than hardcoding "master" ourselves.
2022 """
2023 if name is None:
2024 name = self.remote.name
2025
2026 # The output will look like (NB: tabs are separators):
2027 # ref: refs/heads/master HEAD
2028 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2029 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2030
2031 for line in output.splitlines():
2032 lhs, rhs = line.split('\t', 1)
2033 if rhs == 'HEAD' and lhs.startswith('ref:'):
2034 return lhs[4:].strip()
2035
2036 return None
2037
Zac Livingstone4332262017-06-16 08:56:09 -06002038 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002039 try:
2040 # if revision (sha or tag) is not present then following function
2041 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002042 self.bare_git.rev_list('-1', '--missing=allow-any',
2043 '%s^0' % self.revisionExpr, '--')
Chris AtLee2fb64662014-01-16 21:32:33 -05002044 return True
2045 except GitError:
2046 # There is no such persistent revision. We have to fetch it.
2047 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002048
Julien Campergue335f5ef2013-10-16 11:02:35 +02002049 def _FetchArchive(self, tarpath, cwd=None):
2050 cmd = ['archive', '-v', '-o', tarpath]
2051 cmd.append('--remote=%s' % self.remote.url)
2052 cmd.append('--prefix=%s/' % self.relpath)
2053 cmd.append(self.revisionExpr)
2054
2055 command = GitCommand(self, cmd, cwd=cwd,
2056 capture_stdout=True,
2057 capture_stderr=True)
2058
2059 if command.Wait() != 0:
2060 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2061
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002062 def _RemoteFetch(self, name=None,
2063 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002064 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002065 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002066 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002067 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002068 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002069 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002070 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002071 depth=None,
Mike Frysinger9d0e84c2019-03-18 21:27:54 -04002072 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002073 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002074 clone_filter=None,
2075 retry_fetches=2,
2076 retry_sleep_initial_sec=4.0,
2077 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002078 is_sha1 = False
2079 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002080 # The depth should not be used when fetching to a mirror because
2081 # it will result in a shallow repository that cannot be cloned or
2082 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002083 # The repo project should also never be synced with partial depth.
2084 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2085 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002086
Shawn Pearce69e04d82014-01-29 12:48:54 -08002087 if depth:
2088 current_branch_only = True
2089
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002090 if ID_RE.match(self.revisionExpr) is not None:
2091 is_sha1 = True
2092
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002093 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002094 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002095 # this is a tag and its sha1 value should never change
2096 tag_name = self.revisionExpr[len(R_TAGS):]
2097
2098 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002099 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002100 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002101 print('Skipped fetching project %s (already have persistent ref)'
2102 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002103 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002104 if is_sha1 and not depth:
2105 # When syncing a specific commit and --depth is not set:
2106 # * if upstream is explicitly specified and is not a sha1, fetch only
2107 # upstream as users expect only upstream to be fetch.
2108 # Note: The commit might not be in upstream in which case the sync
2109 # will fail.
2110 # * otherwise, fetch all branches to make sure we end up with the
2111 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002112 if self.upstream:
2113 current_branch_only = not ID_RE.match(self.upstream)
2114 else:
2115 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002116
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002117 if not name:
2118 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002119
2120 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002121 remote = self.GetRemote(name)
2122 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002123 ssh_proxy = True
2124
Shawn O. Pearce88443382010-10-08 10:02:09 +02002125 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002126 if alt_dir and 'objects' == os.path.basename(alt_dir):
2127 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002128 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2129 remote = self.GetRemote(name)
2130
David Pursehouse8a68ff92012-09-24 12:15:13 +09002131 all_refs = self.bare_ref.all
2132 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002133 tmp = set()
2134
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302135 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002136 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002137 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002138 all_refs[r] = ref_id
2139 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002140 continue
2141
David Pursehouse8a68ff92012-09-24 12:15:13 +09002142 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002143 continue
2144
David Pursehouse8a68ff92012-09-24 12:15:13 +09002145 r = 'refs/_alt/%s' % ref_id
2146 all_refs[r] = ref_id
2147 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002148 tmp.add(r)
2149
heping3d7bbc92017-04-12 19:51:47 +08002150 tmp_packed_lines = []
2151 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002152
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302153 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002154 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002155 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002156 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002157 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002158
heping3d7bbc92017-04-12 19:51:47 +08002159 tmp_packed = ''.join(tmp_packed_lines)
2160 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002161 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002162 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002163 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002164
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002165 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002166
Xin Li745be2e2019-06-03 11:24:30 -07002167 if clone_filter:
2168 git_require((2, 19, 0), fail=True, msg='partial clones')
2169 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002170 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002171
Conley Owensf97e8382015-01-21 11:12:46 -08002172 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002173 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002174 else:
2175 # If this repo has shallow objects, then we don't know which refs have
2176 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2177 # do this with projects that don't have shallow objects, since it is less
2178 # efficient.
2179 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2180 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002181
Mike Frysinger4847e052020-02-22 00:07:35 -05002182 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002183 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002184 if not quiet and sys.stdout.isatty():
2185 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002186 if not self.worktree:
2187 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002188 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002189
Mike Frysinger9d0e84c2019-03-18 21:27:54 -04002190 if force_sync:
2191 cmd.append('--force')
2192
David Pursehouse74cfd272015-10-14 10:50:15 +09002193 if prune:
2194 cmd.append('--prune')
2195
Martin Kellye4e94d22017-03-21 16:05:12 -07002196 if submodules:
2197 cmd.append('--recurse-submodules=on-demand')
2198
Kuang-che Wu6856f982019-11-25 12:37:55 +08002199 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002200 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002201 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002202 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002203 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002204 spec.append('tag')
2205 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002206
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302207 if self.manifest.IsMirror and not current_branch_only:
2208 branch = None
2209 else:
2210 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002211 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002212 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002213 # Shallow checkout of a specific commit, fetch from that commit and not
2214 # the heads only as the commit might be deeper in the history.
2215 spec.append(branch)
2216 else:
2217 if is_sha1:
2218 branch = self.upstream
2219 if branch is not None and branch.strip():
2220 if not branch.startswith('refs/'):
2221 branch = R_HEADS + branch
2222 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2223
2224 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2225 # whole repo.
2226 if self.manifest.IsMirror and not spec:
2227 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2228
2229 # If using depth then we should not get all the tags since they may
2230 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002231 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002232 cmd.append('--no-tags')
2233 else:
2234 cmd.append('--tags')
2235 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2236
Conley Owens80b87fe2014-05-09 17:13:44 -07002237 cmd.extend(spec)
2238
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002239 # At least one retry minimum due to git remote prune.
2240 retry_fetches = max(retry_fetches, 2)
2241 retry_cur_sleep = retry_sleep_initial_sec
2242 ok = prune_tried = False
2243 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002244 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002245 merge_output=True, capture_stdout=quiet or bool(output_redir))
2246 if gitcmd.stdout and not quiet and output_redir:
2247 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002248 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002249 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002250 ok = True
2251 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002252
2253 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002254 elif (gitcmd.stdout and
2255 'error:' in gitcmd.stdout and
2256 'HTTP 429' in gitcmd.stdout):
Mike Frysinger6823bc22021-04-15 02:06:28 -04002257 # Fallthru to sleep+retry logic at the bottom.
2258 pass
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002259
Mike Frysinger6823bc22021-04-15 02:06:28 -04002260 # Try to prune remote branches once in case there are conflicts.
2261 # For example, if the remote had refs/heads/upstream, but deleted that and
2262 # now has refs/heads/upstream/foo.
2263 elif (gitcmd.stdout and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002264 'error:' in gitcmd.stdout and
2265 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002266 not prune_tried):
2267 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002268 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002269 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002270 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002271 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002272 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002273 output_redir.write('retrying fetch after pruning remote branches')
2274 # Continue right away so we don't sleep as we shouldn't need to.
John L. Villalovos126e2982015-01-29 21:58:12 -08002275 continue
Brian Harring14a66742012-09-28 20:21:57 -07002276 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002277 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2278 # in sha1 mode, we just tried sync'ing from the upstream field; it
2279 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002280 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002281 elif ret < 0:
2282 # Git died with a signal, exit immediately
2283 break
Mike Frysinger6823bc22021-04-15 02:06:28 -04002284
2285 # Figure out how long to sleep before the next attempt, if there is one.
Mike Frysinger31990f02020-02-17 01:35:18 -05002286 if not verbose:
Mike Frysinger6823bc22021-04-15 02:06:28 -04002287 output_redir.write('\n%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
2288 if try_n < retry_fetches - 1:
2289 output_redir.write('sleeping %s seconds before retrying' % retry_cur_sleep)
2290 time.sleep(retry_cur_sleep)
2291 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2292 MAXIMUM_RETRY_SLEEP_SEC)
2293 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2294 RETRY_JITTER_PERCENT))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002295
2296 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002297 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002298 if old_packed != '':
2299 _lwrite(packed_refs, old_packed)
2300 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002301 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002302 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002303
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002304 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002305 # We just synced the upstream given branch; verify we
2306 # got what we wanted, else trigger a second run of all
2307 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002308 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002309 # Sync the current branch only with depth set to None.
2310 # We always pass depth=None down to avoid infinite recursion.
2311 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002312 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002313 current_branch_only=current_branch_only and depth,
2314 initial=False, alt_dir=alt_dir,
2315 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002316
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002317 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002318
Mike Frysingere50b6a72020-02-19 01:45:48 -05002319 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002320 if initial and \
2321 (self.manifest.manifestProject.config.GetString('repo.depth') or
2322 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002323 return False
2324
2325 remote = self.GetRemote(self.remote.name)
2326 bundle_url = remote.url + '/clone.bundle'
2327 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002328 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2329 'persistent-http',
2330 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002331 return False
2332
2333 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2334 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2335
2336 exist_dst = os.path.exists(bundle_dst)
2337 exist_tmp = os.path.exists(bundle_tmp)
2338
2339 if not initial and not exist_dst and not exist_tmp:
2340 return False
2341
2342 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002343 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2344 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002345 if not exist_dst:
2346 return False
2347
2348 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002349 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002350 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002351 if not quiet and sys.stdout.isatty():
2352 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002353 if not self.worktree:
2354 cmd.append('--update-head-ok')
2355 cmd.append(bundle_dst)
2356 for f in remote.fetch:
2357 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002358 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002359
2360 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002361 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002362 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002363 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002364 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002365 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002366
Mike Frysingere50b6a72020-02-19 01:45:48 -05002367 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002368 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002369 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002370
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002371 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002372 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002373 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002374 if os.path.exists(tmpPath):
2375 size = os.stat(tmpPath).st_size
2376 if size >= 1024:
2377 cmd += ['--continue-at', '%d' % (size,)]
2378 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002379 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002380 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002381 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002382 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002383 if proxy:
2384 cmd += ['--proxy', proxy]
2385 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2386 cmd += ['--proxy', os.environ['http_proxy']]
2387 if srcUrl.startswith('persistent-https'):
2388 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2389 elif srcUrl.startswith('persistent-http'):
2390 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002391 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002392
Dave Borowitz137d0132015-01-02 11:12:54 -08002393 if IsTrace():
2394 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002395 if verbose:
2396 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2397 stdout = None if verbose else subprocess.PIPE
2398 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002399 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002400 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002401 except OSError:
2402 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002403
Mike Frysingere50b6a72020-02-19 01:45:48 -05002404 (output, _) = proc.communicate()
2405 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002406
Dave Borowitz137d0132015-01-02 11:12:54 -08002407 if curlret == 22:
2408 # From curl man page:
2409 # 22: HTTP page not retrieved. The requested url was not found or
2410 # returned another error with the HTTP error code being 400 or above.
2411 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002412 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002413 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2414 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002415 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002416 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002417 elif curlret and not verbose and output:
2418 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002419
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002420 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002421 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002422 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002423 return True
2424 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002425 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002426 return False
2427 else:
2428 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002429
Kris Giesingc8d882a2014-12-23 13:02:32 -08002430 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002431 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002432 with open(path, 'rb') as f:
2433 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002434 return True
2435 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002436 if not quiet:
2437 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002438 return False
2439 except OSError:
2440 return False
2441
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002442 def _Checkout(self, rev, quiet=False):
2443 cmd = ['checkout']
2444 if quiet:
2445 cmd.append('-q')
2446 cmd.append(rev)
2447 cmd.append('--')
2448 if GitCommand(self, cmd).Wait() != 0:
2449 if self._allrefs:
2450 raise GitError('%s checkout %s ' % (self.name, rev))
2451
Mike Frysinger915fda12020-03-22 12:15:20 -04002452 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002453 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002454 if ffonly:
2455 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002456 if record_origin:
2457 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002458 cmd.append(rev)
2459 cmd.append('--')
2460 if GitCommand(self, cmd).Wait() != 0:
2461 if self._allrefs:
2462 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2463
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302464 def _LsRemote(self, refs):
2465 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302466 p = GitCommand(self, cmd, capture_stdout=True)
2467 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002468 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302469 return None
2470
Anthony King7bdac712014-07-16 12:56:40 +01002471 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002472 cmd = ['revert']
2473 cmd.append('--no-edit')
2474 cmd.append(rev)
2475 cmd.append('--')
2476 if GitCommand(self, cmd).Wait() != 0:
2477 if self._allrefs:
2478 raise GitError('%s revert %s ' % (self.name, rev))
2479
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002480 def _ResetHard(self, rev, quiet=True):
2481 cmd = ['reset', '--hard']
2482 if quiet:
2483 cmd.append('-q')
2484 cmd.append(rev)
2485 if GitCommand(self, cmd).Wait() != 0:
2486 raise GitError('%s reset --hard %s ' % (self.name, rev))
2487
Martin Kellye4e94d22017-03-21 16:05:12 -07002488 def _SyncSubmodules(self, quiet=True):
2489 cmd = ['submodule', 'update', '--init', '--recursive']
2490 if quiet:
2491 cmd.append('-q')
2492 if GitCommand(self, cmd).Wait() != 0:
2493 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2494
Anthony King7bdac712014-07-16 12:56:40 +01002495 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002496 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002497 if onto is not None:
2498 cmd.extend(['--onto', onto])
2499 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002500 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002501 raise GitError('%s rebase %s ' % (self.name, upstream))
2502
Pierre Tardy3d125942012-05-04 12:18:12 +02002503 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002504 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002505 if ffonly:
2506 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002507 if GitCommand(self, cmd).Wait() != 0:
2508 raise GitError('%s merge %s ' % (self.name, head))
2509
David Pursehousee8ace262020-02-13 12:41:15 +09002510 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002511 init_git_dir = not os.path.exists(self.gitdir)
2512 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002513 try:
2514 # Initialize the bare repository, which contains all of the objects.
2515 if init_obj_dir:
2516 os.makedirs(self.objdir)
2517 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002518
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002519 if self.use_git_worktrees:
2520 # Set up the m/ space to point to the worktree-specific ref space.
2521 # We'll update the worktree-specific ref space on each checkout.
2522 if self.manifest.branch:
2523 self.bare_git.symbolic_ref(
2524 '-m', 'redirecting to worktree scope',
2525 R_M + self.manifest.branch,
2526 R_WORKTREE_M + self.manifest.branch)
2527
2528 # Enable per-worktree config file support if possible. This is more a
2529 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002530 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002531 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002532
Kevin Degib1a07b82015-07-27 13:33:43 -06002533 # If we have a separate directory to hold refs, initialize it as well.
2534 if self.objdir != self.gitdir:
2535 if init_git_dir:
2536 os.makedirs(self.gitdir)
2537
2538 if init_obj_dir or init_git_dir:
2539 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2540 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002541 try:
2542 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2543 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002544 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002545 print("Retrying clone after deleting %s" %
2546 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002547 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002548 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2549 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002550 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002551 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002552 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2553 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002554 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002555 raise e
2556 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002557
Kevin Degi384b3c52014-10-16 16:02:58 -06002558 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002559 mp = self.manifest.manifestProject
2560 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002561
Kevin Degib1a07b82015-07-27 13:33:43 -06002562 if ref_dir or mirror_git:
2563 if not mirror_git:
2564 mirror_git = os.path.join(ref_dir, self.name + '.git')
2565 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2566 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002567 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2568 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002569
Kevin Degib1a07b82015-07-27 13:33:43 -06002570 if os.path.exists(mirror_git):
2571 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002572 elif os.path.exists(repo_git):
2573 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002574 elif os.path.exists(worktrees_git):
2575 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002576 else:
2577 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002578
Kevin Degib1a07b82015-07-27 13:33:43 -06002579 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002580 if not os.path.isabs(ref_dir):
2581 # The alternate directory is relative to the object database.
2582 ref_dir = os.path.relpath(ref_dir,
2583 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002584 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2585 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002586
David Pursehousee8ace262020-02-13 12:41:15 +09002587 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002588
2589 m = self.manifest.manifestProject.config
2590 for key in ['user.name', 'user.email']:
2591 if m.Has(key, include_defaults=False):
2592 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002593 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002594 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002595 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002596 except Exception:
2597 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002598 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002599 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002600 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002601 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002602
David Pursehousee8ace262020-02-13 12:41:15 +09002603 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002604 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002605 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002606
David Pursehousee8ace262020-02-13 12:41:15 +09002607 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002608 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002609 if not os.path.exists(hooks):
2610 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002611 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002612 name = os.path.basename(stock_hook)
2613
Victor Boivie65e0f352011-04-18 11:23:29 +02002614 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002615 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002616 # Don't install a Gerrit Code Review hook if this
2617 # project does not appear to use it for reviews.
2618 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002619 # Since the manifest project is one of those, but also
2620 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002621 continue
2622
2623 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002624 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002625 continue
2626 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002627 # If the files are the same, we'll leave it alone. We create symlinks
2628 # below by default but fallback to hardlinks if the OS blocks them.
2629 # So if we're here, it's probably because we made a hardlink below.
2630 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002631 if not quiet:
2632 _warn("%s: Not replacing locally modified %s hook",
2633 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002634 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002635 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002636 platform_utils.symlink(
2637 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002638 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002639 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002640 try:
2641 os.link(stock_hook, dst)
2642 except OSError:
2643 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002644 else:
2645 raise
2646
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002647 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002648 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002649 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002650 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002651 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002652 remote.review = self.remote.review
2653 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002654
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002655 if self.worktree:
2656 remote.ResetFetch(mirror=False)
2657 else:
2658 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002659 remote.Save()
2660
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002661 def _InitMRef(self):
2662 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002663 if self.use_git_worktrees:
2664 # We can't update this ref with git worktrees until it exists.
2665 # We'll wait until the initial checkout to set it.
2666 if not os.path.exists(self.worktree):
2667 return
2668
2669 base = R_WORKTREE_M
2670 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002671
2672 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002673 else:
2674 base = R_M
2675 active_git = self.bare_git
2676
2677 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002678
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002679 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002680 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002681
Remy Böhmer1469c282020-12-15 18:49:02 +01002682 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002683 cur = self.bare_ref.symref(ref)
2684
2685 if self.revisionId:
2686 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2687 msg = 'manifest set to %s' % self.revisionId
2688 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002689 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002690 else:
2691 remote = self.GetRemote(self.remote.name)
2692 dst = remote.ToLocal(self.revisionExpr)
2693 if cur != dst:
2694 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002695 if detach:
2696 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2697 else:
2698 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002699
Kevin Degi384b3c52014-10-16 16:02:58 -06002700 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002701 # Git worktrees don't use symlinks to share at all.
2702 if self.use_git_worktrees:
2703 return
2704
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002705 symlink_files = self.shareable_files[:]
2706 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002707 if share_refs:
2708 symlink_files += self.working_tree_files
2709 symlink_dirs += self.working_tree_dirs
2710 to_symlink = symlink_files + symlink_dirs
2711 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002712 # Try to self-heal a bit in simple cases.
2713 dst_path = os.path.join(destdir, name)
2714 src_path = os.path.join(srcdir, name)
2715
2716 if name in self.working_tree_dirs:
2717 # If the dir is missing under .repo/projects/, create it.
2718 if not os.path.exists(src_path):
2719 os.makedirs(src_path)
2720
2721 elif name in self.working_tree_files:
2722 # If it's a file under the checkout .git/ and the .repo/projects/ has
2723 # nothing, move the file under the .repo/projects/ tree.
2724 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2725 platform_utils.rename(dst_path, src_path)
2726
2727 # If the path exists under the .repo/projects/ and there's no symlink
2728 # under the checkout .git/, recreate the symlink.
2729 if name in self.working_tree_dirs or name in self.working_tree_files:
2730 if os.path.exists(src_path) and not os.path.exists(dst_path):
2731 platform_utils.symlink(
2732 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2733
2734 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002735 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002736 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002737 # Fail if the links are pointing to the wrong place
2738 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002739 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002740 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002741 'work tree. If you\'re comfortable with the '
2742 'possibility of losing the work tree\'s git metadata,'
2743 ' use `repo sync --force-sync {0}` to '
2744 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002745
David James8d201162013-10-11 17:03:19 -07002746 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2747 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2748
2749 Args:
2750 gitdir: The bare git repository. Must already be initialized.
2751 dotgit: The repository you would like to initialize.
2752 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2753 Only one work tree can store refs under a given |gitdir|.
2754 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2755 This saves you the effort of initializing |dotgit| yourself.
2756 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002757 symlink_files = self.shareable_files[:]
2758 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002759 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002760 symlink_files += self.working_tree_files
2761 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002762 to_symlink = symlink_files + symlink_dirs
2763
2764 to_copy = []
2765 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002766 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002767
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002768 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002769 for name in set(to_copy).union(to_symlink):
2770 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002771 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002772 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002773
Kevin Degi384b3c52014-10-16 16:02:58 -06002774 if os.path.lexists(dst):
2775 continue
David James8d201162013-10-11 17:03:19 -07002776
2777 # If the source dir doesn't exist, create an empty dir.
2778 if name in symlink_dirs and not os.path.lexists(src):
2779 os.makedirs(src)
2780
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002781 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002782 platform_utils.symlink(
2783 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002784 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002785 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002786 shutil.copytree(src, dst)
2787 elif os.path.isfile(src):
2788 shutil.copy(src, dst)
2789
Conley Owens80b87fe2014-05-09 17:13:44 -07002790 # If the source file doesn't exist, ensure the destination
2791 # file doesn't either.
2792 if name in symlink_files and not os.path.lexists(src):
2793 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002794 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002795 except OSError:
2796 pass
2797
David James8d201162013-10-11 17:03:19 -07002798 except OSError as e:
2799 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002800 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002801 else:
2802 raise
2803
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002804 def _InitGitWorktree(self):
2805 """Init the project using git worktrees."""
2806 self.bare_git.worktree('prune')
2807 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2808 self.worktree, self.GetRevisionId())
2809
2810 # Rewrite the internal state files to use relative paths between the
2811 # checkouts & worktrees.
2812 dotgit = os.path.join(self.worktree, '.git')
2813 with open(dotgit, 'r') as fp:
2814 # Figure out the checkout->worktree path.
2815 setting = fp.read()
2816 assert setting.startswith('gitdir:')
2817 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002818 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2819 # of file permissions. Delete it and recreate it from scratch to avoid.
2820 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002821 # Use relative path from checkout->worktree & maintain Unix line endings
2822 # on all OS's to match git behavior.
2823 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002824 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2825 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002826 # Use relative path from worktree->checkout & maintain Unix line endings
2827 # on all OS's to match git behavior.
2828 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002829 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2830
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002831 self._InitMRef()
2832
Martin Kellye4e94d22017-03-21 16:05:12 -07002833 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002834 realdotgit = os.path.join(self.worktree, '.git')
2835 tmpdotgit = realdotgit + '.tmp'
2836 init_dotgit = not os.path.exists(realdotgit)
2837 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002838 if self.use_git_worktrees:
2839 self._InitGitWorktree()
2840 self._CopyAndLinkFiles()
2841 return
2842
Mike Frysingerf4545122019-11-11 04:34:16 -05002843 dotgit = tmpdotgit
2844 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2845 os.makedirs(tmpdotgit)
2846 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2847 copy_all=False)
2848 else:
2849 dotgit = realdotgit
2850
Kevin Degib1a07b82015-07-27 13:33:43 -06002851 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002852 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2853 except GitError as e:
2854 if force_sync and not init_dotgit:
2855 try:
2856 platform_utils.rmtree(dotgit)
2857 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002858 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002859 raise e
2860 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002861
Mike Frysingerf4545122019-11-11 04:34:16 -05002862 if init_dotgit:
2863 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002864
Mike Frysingerf4545122019-11-11 04:34:16 -05002865 # Now that the .git dir is fully set up, move it to its final home.
2866 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002867
Mike Frysingerf4545122019-11-11 04:34:16 -05002868 # Finish checking out the worktree.
2869 cmd = ['read-tree', '--reset', '-u']
2870 cmd.append('-v')
2871 cmd.append(HEAD)
2872 if GitCommand(self, cmd).Wait() != 0:
2873 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002874
Mike Frysingerf4545122019-11-11 04:34:16 -05002875 if submodules:
2876 self._SyncSubmodules(quiet=True)
2877 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002878
Renaud Paquay788e9622017-01-27 11:41:12 -08002879 def _get_symlink_error_message(self):
2880 if platform_utils.isWindows():
2881 return ('Unable to create symbolic link. Please re-run the command as '
2882 'Administrator, or see '
2883 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2884 'for other options.')
2885 return 'filesystem must support symlinks'
2886
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002887 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002888 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002889
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002890 def _revlist(self, *args, **kw):
2891 a = []
2892 a.extend(args)
2893 a.append('--')
2894 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002895
2896 @property
2897 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002898 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002899
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002900 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002901 """Get logs between two revisions of this project."""
2902 comp = '..'
2903 if rev1:
2904 revs = [rev1]
2905 if rev2:
2906 revs.extend([comp, rev2])
2907 cmd = ['log', ''.join(revs)]
2908 out = DiffColoring(self.config)
2909 if out.is_on and color:
2910 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002911 if pretty_format is not None:
2912 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002913 if oneline:
2914 cmd.append('--oneline')
2915
2916 try:
2917 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2918 if log.Wait() == 0:
2919 return log.stdout
2920 except GitError:
2921 # worktree may not exist if groups changed for example. In that case,
2922 # try in gitdir instead.
2923 if not os.path.exists(self.worktree):
2924 return self.bare_git.log(*cmd[1:])
2925 else:
2926 raise
2927 return None
2928
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002929 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2930 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002931 """Get the list of logs from this revision to given revisionId"""
2932 logs = {}
2933 selfId = self.GetRevisionId(self._allrefs)
2934 toId = toProject.GetRevisionId(toProject._allrefs)
2935
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002936 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2937 pretty_format=pretty_format)
2938 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2939 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002940 return logs
2941
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002942 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002943
David James8d201162013-10-11 17:03:19 -07002944 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002945 self._project = project
2946 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002947 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002948
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002949 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2950 def __getstate__(self):
2951 return (self._project, self._bare, self._gitdir)
2952
2953 def __setstate__(self, state):
2954 self._project, self._bare, self._gitdir = state
2955
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002956 def LsOthers(self):
2957 p = GitCommand(self._project,
2958 ['ls-files',
2959 '-z',
2960 '--others',
2961 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002962 bare=False,
David James8d201162013-10-11 17:03:19 -07002963 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002964 capture_stdout=True,
2965 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002966 if p.Wait() == 0:
2967 out = p.stdout
2968 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002969 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002970 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002971 return []
2972
2973 def DiffZ(self, name, *args):
2974 cmd = [name]
2975 cmd.append('-z')
Eli Ribble7b4f0192019-05-02 18:21:42 -07002976 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002977 cmd.extend(args)
2978 p = GitCommand(self._project,
2979 cmd,
David James8d201162013-10-11 17:03:19 -07002980 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002981 bare=False,
2982 capture_stdout=True,
2983 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002984 p.Wait()
2985 r = {}
2986 out = p.stdout
2987 if out:
2988 out = iter(out[:-1].split('\0'))
2989 while out:
2990 try:
2991 info = next(out)
2992 path = next(out)
2993 except StopIteration:
2994 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002995
Mike Frysinger84230002021-02-16 17:08:35 -05002996 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002997
Mike Frysinger84230002021-02-16 17:08:35 -05002998 def __init__(self, path, omode, nmode, oid, nid, state):
2999 self.path = path
3000 self.src_path = None
3001 self.old_mode = omode
3002 self.new_mode = nmode
3003 self.old_id = oid
3004 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003005
Mike Frysinger84230002021-02-16 17:08:35 -05003006 if len(state) == 1:
3007 self.status = state
3008 self.level = None
3009 else:
3010 self.status = state[:1]
3011 self.level = state[1:]
3012 while self.level.startswith('0'):
3013 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003014
Mike Frysinger84230002021-02-16 17:08:35 -05003015 info = info[1:].split(' ')
3016 info = _Info(path, *info)
3017 if info.status in ('R', 'C'):
3018 info.src_path = info.path
3019 info.path = next(out)
3020 r[info.path] = info
3021 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003022
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003023 def GetDotgitPath(self, subpath=None):
3024 """Return the full path to the .git dir.
3025
3026 As a convenience, append |subpath| if provided.
3027 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003028 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003029 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003030 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003031 dotgit = os.path.join(self._project.worktree, '.git')
3032 if os.path.isfile(dotgit):
3033 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3034 with open(dotgit) as fp:
3035 setting = fp.read()
3036 assert setting.startswith('gitdir:')
3037 gitdir = setting.split(':', 1)[1].strip()
3038 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3039
3040 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3041
3042 def GetHead(self):
3043 """Return the ref that HEAD points to."""
3044 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003045 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003046 with open(path) as fd:
3047 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003048 except IOError as e:
3049 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003050 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303051 line = line.decode()
3052 except AttributeError:
3053 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003054 if line.startswith('ref: '):
3055 return line[5:-1]
3056 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003057
3058 def SetHead(self, ref, message=None):
3059 cmdv = []
3060 if message is not None:
3061 cmdv.extend(['-m', message])
3062 cmdv.append(HEAD)
3063 cmdv.append(ref)
3064 self.symbolic_ref(*cmdv)
3065
3066 def DetachHead(self, new, message=None):
3067 cmdv = ['--no-deref']
3068 if message is not None:
3069 cmdv.extend(['-m', message])
3070 cmdv.append(HEAD)
3071 cmdv.append(new)
3072 self.update_ref(*cmdv)
3073
3074 def UpdateRef(self, name, new, old=None,
3075 message=None,
3076 detach=False):
3077 cmdv = []
3078 if message is not None:
3079 cmdv.extend(['-m', message])
3080 if detach:
3081 cmdv.append('--no-deref')
3082 cmdv.append(name)
3083 cmdv.append(new)
3084 if old is not None:
3085 cmdv.append(old)
3086 self.update_ref(*cmdv)
3087
3088 def DeleteRef(self, name, old=None):
3089 if not old:
3090 old = self.rev_parse(name)
3091 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003092 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003093
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003094 def rev_list(self, *args, **kw):
3095 if 'format' in kw:
3096 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3097 else:
3098 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003099 cmdv.extend(args)
3100 p = GitCommand(self._project,
3101 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003102 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003103 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003104 capture_stdout=True,
3105 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003106 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003107 raise GitError('%s rev-list %s: %s' %
3108 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003109 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003110
3111 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003112 """Allow arbitrary git commands using pythonic syntax.
3113
3114 This allows you to do things like:
3115 git_obj.rev_parse('HEAD')
3116
3117 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3118 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003119 Any other positional arguments will be passed to the git command, and the
3120 following keyword arguments are supported:
3121 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003122
3123 Args:
3124 name: The name of the git command to call. Any '_' characters will
3125 be replaced with '-'.
3126
3127 Returns:
3128 A callable object that will try to call git with the named command.
3129 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003130 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003131
Dave Borowitz091f8932012-10-23 17:01:04 -07003132 def runner(*args, **kwargs):
3133 cmdv = []
3134 config = kwargs.pop('config', None)
3135 for k in kwargs:
3136 raise TypeError('%s() got an unexpected keyword argument %r'
3137 % (name, k))
3138 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303139 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003140 cmdv.append('-c')
3141 cmdv.append('%s=%s' % (k, v))
3142 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003143 cmdv.extend(args)
3144 p = GitCommand(self._project,
3145 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003146 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003147 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003148 capture_stdout=True,
3149 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003150 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003151 raise GitError('%s %s: %s' %
3152 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003153 r = p.stdout
3154 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3155 return r[:-1]
3156 return r
3157 return runner
3158
3159
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003160class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003161
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003162 def __str__(self):
3163 return 'prior sync failed; rebase still in progress'
3164
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003165
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003166class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003167
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003168 def __str__(self):
3169 return 'contains uncommitted changes'
3170
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003171
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003172class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003173
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003174 def __init__(self, project, text):
3175 self.project = project
3176 self.text = text
3177
3178 def Print(self, syncbuf):
3179 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3180 syncbuf.out.nl()
3181
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003182
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003183class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003184
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003185 def __init__(self, project, why):
3186 self.project = project
3187 self.why = why
3188
3189 def Print(self, syncbuf):
3190 syncbuf.out.fail('error: %s/: %s',
3191 self.project.relpath,
3192 str(self.why))
3193 syncbuf.out.nl()
3194
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003195
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003196class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003197
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003198 def __init__(self, project, action):
3199 self.project = project
3200 self.action = action
3201
3202 def Run(self, syncbuf):
3203 out = syncbuf.out
3204 out.project('project %s/', self.project.relpath)
3205 out.nl()
3206 try:
3207 self.action()
3208 out.nl()
3209 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003210 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003211 out.nl()
3212 return False
3213
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003214
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003215class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003216
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003217 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003218 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003219 self.project = self.printer('header', attr='bold')
3220 self.info = self.printer('info')
3221 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003222
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003223
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003224class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003225
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003226 def __init__(self, config, detach_head=False):
3227 self._messages = []
3228 self._failures = []
3229 self._later_queue1 = []
3230 self._later_queue2 = []
3231
3232 self.out = _SyncColoring(config)
3233 self.out.redirect(sys.stderr)
3234
3235 self.detach_head = detach_head
3236 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003237 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003238
3239 def info(self, project, fmt, *args):
3240 self._messages.append(_InfoMessage(project, fmt % args))
3241
3242 def fail(self, project, err=None):
3243 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003244 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003245
3246 def later1(self, project, what):
3247 self._later_queue1.append(_Later(project, what))
3248
3249 def later2(self, project, what):
3250 self._later_queue2.append(_Later(project, what))
3251
3252 def Finish(self):
3253 self._PrintMessages()
3254 self._RunLater()
3255 self._PrintMessages()
3256 return self.clean
3257
David Rileye0684ad2017-04-05 00:02:59 -07003258 def Recently(self):
3259 recent_clean = self.recent_clean
3260 self.recent_clean = True
3261 return recent_clean
3262
3263 def _MarkUnclean(self):
3264 self.clean = False
3265 self.recent_clean = False
3266
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003267 def _RunLater(self):
3268 for q in ['_later_queue1', '_later_queue2']:
3269 if not self._RunQueue(q):
3270 return
3271
3272 def _RunQueue(self, queue):
3273 for m in getattr(self, queue):
3274 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003275 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003276 return False
3277 setattr(self, queue, [])
3278 return True
3279
3280 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003281 if self._messages or self._failures:
3282 if os.isatty(2):
3283 self.out.write(progress.CSI_ERASE_LINE)
3284 self.out.write('\r')
3285
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003286 for m in self._messages:
3287 m.Print(self)
3288 for m in self._failures:
3289 m.Print(self)
3290
3291 self._messages = []
3292 self._failures = []
3293
3294
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003295class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003296
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003297 """A special project housed under .repo.
3298 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003299
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003300 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003301 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003302 manifest=manifest,
3303 name=name,
3304 gitdir=gitdir,
3305 objdir=gitdir,
3306 worktree=worktree,
3307 remote=RemoteSpec('origin'),
3308 relpath='.repo/%s' % name,
3309 revisionExpr='refs/heads/master',
3310 revisionId=None,
3311 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003312
3313 def PreSync(self):
3314 if self.Exists:
3315 cb = self.CurrentBranch
3316 if cb:
3317 base = self.GetBranch(cb).merge
3318 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003319 self.revisionExpr = base
3320 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003321
Martin Kelly224a31a2017-07-10 14:46:25 -07003322 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003323 """ Prepare MetaProject for manifest branch switch
3324 """
3325
3326 # detach and delete manifest branch, allowing a new
3327 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003328 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003329 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003330 syncbuf.Finish()
3331
3332 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003333 ['update-ref', '-d', 'refs/heads/default'],
3334 capture_stdout=True,
3335 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003336
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003337 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003338 def LastFetch(self):
3339 try:
3340 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3341 return os.path.getmtime(fh)
3342 except OSError:
3343 return 0
3344
3345 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003346 def HasChanges(self):
3347 """Has the remote received new commits not yet checked out?
3348 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003349 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003350 return False
3351
David Pursehouse8a68ff92012-09-24 12:15:13 +09003352 all_refs = self.bare_ref.all
3353 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003354 head = self.work_git.GetHead()
3355 if head.startswith(R_HEADS):
3356 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003357 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003358 except KeyError:
3359 head = None
3360
3361 if revid == head:
3362 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003363 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003364 return True
3365 return False