blob: 7f6e2367afba160e8cf3335215b501a96e37f72e [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 Frysinger39ba6312019-07-27 12:45:51 -04001116 clone_filter=None,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001117 cache_dir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001118 """Perform only the network IO portion of the sync process.
1119 Local working directory/branch state is not affected.
1120 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001121 if archive and not isinstance(self, MetaProject):
1122 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001123 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001124 return False
1125
1126 name = self.relpath.replace('\\', '/')
1127 name = name.replace('/', '_')
1128 tarpath = '%s.tar' % name
1129 topdir = self.manifest.topdir
1130
1131 try:
1132 self._FetchArchive(tarpath, cwd=topdir)
1133 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001134 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001135 return False
1136
1137 # From now on, we only need absolute tarpath
1138 tarpath = os.path.join(topdir, tarpath)
1139
1140 if not self._ExtractArchive(tarpath, path=topdir):
1141 return False
1142 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001143 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001144 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001145 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001146 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001147 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001148
1149 # If the shared object dir already exists, don't try to rebootstrap with a
1150 # clone bundle download. We should have the majority of objects already.
1151 if clone_bundle and os.path.exists(self.objdir):
1152 clone_bundle = False
1153
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001154 if is_new is None:
1155 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001156 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001157 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001158 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001159 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001161
1162 if is_new:
1163 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1164 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001165 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001166 # This works for both absolute and relative alternate directories.
1167 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001168 except IOError:
1169 alt_dir = None
1170 else:
1171 alt_dir = None
1172
Ningning Xiac2fbc782016-08-22 14:24:39 -07001173 applied_cache = False
1174 # If cache_dir is provided, and it's a new repository without
1175 # alternative_dir, bootstrap this project repo with the git
1176 # cache files.
1177 if cache_dir is not None and is_new and alt_dir is None:
1178 try:
1179 self.CacheApply(cache_dir)
1180 applied_cache = True
1181 is_new = False
1182 except CacheApplyError as e:
1183 _error('Could not apply git cache: %s', e)
1184 _error('Please check if you have the right GS credentials.')
1185 _error('Please check if the cache files exist in GS.')
1186
Mike Frysingere50b6a72020-02-19 01:45:48 -05001187 if (clone_bundle
Mike Frysingerd70c6072020-02-25 10:22:02 -05001188 and not applied_cache
Mike Frysingere50b6a72020-02-19 01:45:48 -05001189 and alt_dir is None
1190 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001191 is_new = False
1192
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001193 if not current_branch_only:
1194 if self.sync_c:
1195 current_branch_only = True
1196 elif not self.manifest._loaded:
1197 # Manifest cannot check defaults until it syncs.
1198 current_branch_only = False
1199 elif self.manifest.default.sync_c:
1200 current_branch_only = True
1201
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001202 if not self.sync_tags:
1203 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001204
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001205 if self.clone_depth:
1206 depth = self.clone_depth
1207 else:
1208 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1209
Mike Frysinger521d01b2020-02-17 01:51:49 -05001210 # See if we can skip the network fetch entirely.
1211 if not (optimized_fetch and
1212 (ID_RE.match(self.revisionExpr) and
1213 self._CheckForImmutableRevision())):
1214 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001215 initial=is_new,
1216 quiet=quiet, verbose=verbose, output_redir=output_redir,
1217 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001218 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001219 submodules=submodules, force_sync=force_sync,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001220 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001221 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001222
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001223 mp = self.manifest.manifestProject
1224 dissociate = mp.config.GetBoolean('repo.dissociate')
1225 if dissociate:
1226 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1227 if os.path.exists(alternates_file):
1228 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001229 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1230 merge_output=bool(output_redir))
1231 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001232 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001233 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001234 return False
1235 platform_utils.remove(alternates_file)
1236
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001237 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001238 self._InitMRef()
1239 else:
1240 self._InitMirrorHead()
1241 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001242 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001243 except OSError:
1244 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001245 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001246
1247 def PostRepoUpgrade(self):
1248 self._InitHooks()
1249
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001250 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001251 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001252 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001253 for copyfile in self.copyfiles:
1254 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001255 for linkfile in self.linkfiles:
1256 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001257
Julien Camperguedd654222014-01-09 16:21:37 +01001258 def GetCommitRevisionId(self):
1259 """Get revisionId of a commit.
1260
1261 Use this method instead of GetRevisionId to get the id of the commit rather
1262 than the id of the current git object (for example, a tag)
1263
1264 """
1265 if not self.revisionExpr.startswith(R_TAGS):
1266 return self.GetRevisionId(self._allrefs)
1267
1268 try:
1269 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1270 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001271 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1272 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001273
David Pursehouse8a68ff92012-09-24 12:15:13 +09001274 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001275 if self.revisionId:
1276 return self.revisionId
1277
1278 rem = self.GetRemote(self.remote.name)
1279 rev = rem.ToLocal(self.revisionExpr)
1280
David Pursehouse8a68ff92012-09-24 12:15:13 +09001281 if all_refs is not None and rev in all_refs:
1282 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001283
1284 try:
1285 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1286 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001287 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1288 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001289
Raman Tenneti6a872c92021-01-14 19:17:50 -08001290 def SetRevisionId(self, revisionId):
1291 self.revisionId = revisionId
1292
Martin Kellye4e94d22017-03-21 16:05:12 -07001293 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 """Perform only the local IO portion of the sync process.
1295 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001297 if not os.path.exists(self.gitdir):
1298 syncbuf.fail(self,
1299 'Cannot checkout %s due to missing network sync; Run '
1300 '`repo sync -n %s` first.' %
1301 (self.name, self.name))
1302 return
1303
Martin Kellye4e94d22017-03-21 16:05:12 -07001304 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001305 all_refs = self.bare_ref.all
1306 self.CleanPublishedCache(all_refs)
1307 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001308
Mike Frysinger0458faa2021-03-10 23:35:44 -05001309 # Special case the root of the repo client checkout. Make sure it doesn't
1310 # contain files being checked out to dirs we don't allow.
1311 if self.relpath == '.':
1312 PROTECTED_PATHS = {'.repo'}
1313 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1314 bad_paths = paths & PROTECTED_PATHS
1315 if bad_paths:
1316 syncbuf.fail(self,
1317 'Refusing to checkout project that writes to protected '
1318 'paths: %s' % (', '.join(bad_paths),))
1319 return
1320
David Pursehouse1d947b32012-10-25 12:23:11 +09001321 def _doff():
1322 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001323 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001324
Martin Kellye4e94d22017-03-21 16:05:12 -07001325 def _dosubmodules():
1326 self._SyncSubmodules(quiet=True)
1327
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001328 head = self.work_git.GetHead()
1329 if head.startswith(R_HEADS):
1330 branch = head[len(R_HEADS):]
1331 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001332 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001333 except KeyError:
1334 head = None
1335 else:
1336 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001338 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001339 # Currently on a detached HEAD. The user is assumed to
1340 # not have any local modifications worth worrying about.
1341 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001342 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001343 syncbuf.fail(self, _PriorSyncFailedError())
1344 return
1345
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001346 if head == revid:
1347 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001348 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001349 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001350 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001351 # The copy/linkfile config may have changed.
1352 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001353 return
1354 else:
1355 lost = self._revlist(not_rev(revid), HEAD)
1356 if lost:
1357 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001358
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001360 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001361 if submodules:
1362 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001363 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001364 syncbuf.fail(self, e)
1365 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001366 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001367 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001369 if head == revid:
1370 # No changes; don't do anything further.
1371 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001372 # The copy/linkfile config may have changed.
1373 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001374 return
1375
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001377
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001378 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001379 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001380 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001382 syncbuf.info(self,
1383 "leaving %s; does not track upstream",
1384 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001385 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001386 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001387 if submodules:
1388 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001389 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001390 syncbuf.fail(self, e)
1391 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001392 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001393 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001395 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001396
1397 # See if we can perform a fast forward merge. This can happen if our
1398 # branch isn't in the exact same state as we last published.
1399 try:
1400 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1401 # Skip the published logic.
1402 pub = False
1403 except GitError:
1404 pub = self.WasPublished(branch.name, all_refs)
1405
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001406 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001407 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001408 if not_merged:
1409 if upstream_gain:
1410 # The user has published this branch and some of those
1411 # commits are not yet merged upstream. We do not want
1412 # to rewrite the published commits so we punt.
1413 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001414 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001415 "branch %s is published (but not merged) and is now "
1416 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001417 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001418 elif pub == head:
1419 # All published commits are merged, and thus we are a
1420 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001421 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001422 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001423 if submodules:
1424 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001425 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001426
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001427 # Examine the local commits not in the remote. Find the
1428 # last one attributed to this user, if any.
1429 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001430 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001431 last_mine = None
1432 cnt_mine = 0
1433 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001434 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001435 if committer_email == self.UserEmail:
1436 last_mine = commit_id
1437 cnt_mine += 1
1438
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001439 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001440 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001441
1442 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001443 syncbuf.fail(self, _DirtyError())
1444 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001445
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001446 # If the upstream switched on us, warn the user.
1447 #
1448 if branch.merge != self.revisionExpr:
1449 if branch.merge and self.revisionExpr:
1450 syncbuf.info(self,
1451 'manifest switched %s...%s',
1452 branch.merge,
1453 self.revisionExpr)
1454 elif branch.merge:
1455 syncbuf.info(self,
1456 'manifest no longer tracks %s',
1457 branch.merge)
1458
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001459 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001460 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001461 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001462 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001463 syncbuf.info(self,
1464 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001465 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001466
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001467 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001468 if not ID_RE.match(self.revisionExpr):
1469 # in case of manifest sync the revisionExpr might be a SHA1
1470 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001471 if not branch.merge.startswith('refs/'):
1472 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001473 branch.Save()
1474
Mike Pontillod3153822012-02-28 11:53:24 -08001475 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001476 def _docopyandlink():
1477 self._CopyAndLinkFiles()
1478
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001479 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001480 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001481 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001482 if submodules:
1483 syncbuf.later2(self, _dosubmodules)
1484 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001485 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001486 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001487 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001488 if submodules:
1489 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001490 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001491 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001492 syncbuf.fail(self, e)
1493 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001494 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001495 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001496 if submodules:
1497 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001498
Mike Frysingere6a202f2019-08-02 15:57:57 -04001499 def AddCopyFile(self, src, dest, topdir):
1500 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001501
Mike Frysingere6a202f2019-08-02 15:57:57 -04001502 No filesystem changes occur here. Actual copying happens later on.
1503
1504 Paths should have basic validation run on them before being queued.
1505 Further checking will be handled when the actual copy happens.
1506 """
1507 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1508
1509 def AddLinkFile(self, src, dest, topdir):
1510 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1511
1512 No filesystem changes occur here. Actual linking happens later on.
1513
1514 Paths should have basic validation run on them before being queued.
1515 Further checking will be handled when the actual link happens.
1516 """
1517 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001518
James W. Mills24c13082012-04-12 15:04:13 -05001519 def AddAnnotation(self, name, value, keep):
1520 self.annotations.append(_Annotation(name, value, keep))
1521
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001522 def DownloadPatchSet(self, change_id, patch_id):
1523 """Download a single patch set of a single change to FETCH_HEAD.
1524 """
1525 remote = self.GetRemote(self.remote.name)
1526
1527 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001528 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001529 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001530 if GitCommand(self, cmd, bare=True).Wait() != 0:
1531 return None
1532 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001533 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001534 change_id,
1535 patch_id,
1536 self.bare_git.rev_parse('FETCH_HEAD'))
1537
Mike Frysingerc0d18662020-02-19 19:19:18 -05001538 def DeleteWorktree(self, quiet=False, force=False):
1539 """Delete the source checkout and any other housekeeping tasks.
Mike Frysingerf914edc2020-02-09 03:01:56 -05001540
Mike Frysingerc0d18662020-02-19 19:19:18 -05001541 This currently leaves behind the internal .repo/ cache state. This helps
1542 when switching branches or manifest changes get reverted as we don't have
1543 to redownload all the git objects. But we should do some GC at some point.
1544
1545 Args:
1546 quiet: Whether to hide normal messages.
1547 force: Always delete tree even if dirty.
1548
1549 Returns:
1550 True if the worktree was completely cleaned out.
1551 """
1552 if self.IsDirty():
1553 if force:
1554 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1555 (self.relpath,), file=sys.stderr)
1556 else:
1557 print('error: %s: Cannot remove project: uncommitted changes are '
1558 'present.\n' % (self.relpath,), file=sys.stderr)
1559 return False
1560
1561 if not quiet:
1562 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1563
1564 # Unlock and delink from the main worktree. We don't use git's worktree
1565 # remove because it will recursively delete projects -- we handle that
1566 # ourselves below. https://crbug.com/git/48
1567 if self.use_git_worktrees:
1568 needle = platform_utils.realpath(self.gitdir)
1569 # Find the git worktree commondir under .repo/worktrees/.
1570 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1571 assert output.startswith('worktree '), output
1572 commondir = output[9:]
1573 # Walk each of the git worktrees to see where they point.
1574 configs = os.path.join(commondir, 'worktrees')
1575 for name in os.listdir(configs):
1576 gitdir = os.path.join(configs, name, 'gitdir')
1577 with open(gitdir) as fp:
1578 relpath = fp.read().strip()
1579 # Resolve the checkout path and see if it matches this project.
1580 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1581 if fullpath == needle:
1582 platform_utils.rmtree(os.path.join(configs, name))
1583
1584 # Delete the .git directory first, so we're less likely to have a partially
1585 # working git repository around. There shouldn't be any git projects here,
1586 # so rmtree works.
1587
1588 # Try to remove plain files first in case of git worktrees. If this fails
1589 # for any reason, we'll fall back to rmtree, and that'll display errors if
1590 # it can't remove things either.
1591 try:
1592 platform_utils.remove(self.gitdir)
1593 except OSError:
1594 pass
1595 try:
1596 platform_utils.rmtree(self.gitdir)
1597 except OSError as e:
1598 if e.errno != errno.ENOENT:
1599 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1600 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1601 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1602 return False
1603
1604 # Delete everything under the worktree, except for directories that contain
1605 # another git project.
1606 dirs_to_remove = []
1607 failed = False
1608 for root, dirs, files in platform_utils.walk(self.worktree):
1609 for f in files:
1610 path = os.path.join(root, f)
1611 try:
1612 platform_utils.remove(path)
1613 except OSError as e:
1614 if e.errno != errno.ENOENT:
1615 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1616 failed = True
1617 dirs[:] = [d for d in dirs
1618 if not os.path.lexists(os.path.join(root, d, '.git'))]
1619 dirs_to_remove += [os.path.join(root, d) for d in dirs
1620 if os.path.join(root, d) not in dirs_to_remove]
1621 for d in reversed(dirs_to_remove):
1622 if platform_utils.islink(d):
1623 try:
1624 platform_utils.remove(d)
1625 except OSError as e:
1626 if e.errno != errno.ENOENT:
1627 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1628 failed = True
1629 elif not platform_utils.listdir(d):
1630 try:
1631 platform_utils.rmdir(d)
1632 except OSError as e:
1633 if e.errno != errno.ENOENT:
1634 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1635 failed = True
1636 if failed:
1637 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1638 file=sys.stderr)
1639 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1640 return False
1641
1642 # Try deleting parent dirs if they are empty.
1643 path = self.worktree
1644 while path != self.manifest.topdir:
1645 try:
1646 platform_utils.rmdir(path)
1647 except OSError as e:
1648 if e.errno != errno.ENOENT:
1649 break
1650 path = os.path.dirname(path)
1651
1652 return True
1653
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001654# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001655 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001656 """Create a new branch off the manifest's revision.
1657 """
Simran Basib9a1b732015-08-20 12:19:28 -07001658 if not branch_merge:
1659 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001660 head = self.work_git.GetHead()
1661 if head == (R_HEADS + name):
1662 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001663
David Pursehouse8a68ff92012-09-24 12:15:13 +09001664 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001665 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001666 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001667 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001668 capture_stdout=True,
1669 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001670
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001671 branch = self.GetBranch(name)
1672 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001673 branch.merge = branch_merge
1674 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1675 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001676
1677 if revision is None:
1678 revid = self.GetRevisionId(all_refs)
1679 else:
1680 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001681
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001682 if head.startswith(R_HEADS):
1683 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001684 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001685 except KeyError:
1686 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001687 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001688 ref = R_HEADS + name
1689 self.work_git.update_ref(ref, revid)
1690 self.work_git.symbolic_ref(HEAD, ref)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001691 branch.Save()
1692 return True
1693
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001694 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001695 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001696 capture_stdout=True,
1697 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001698 branch.Save()
1699 return True
1700 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001701
Wink Saville02d79452009-04-10 13:01:24 -07001702 def CheckoutBranch(self, name):
1703 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001704
1705 Args:
1706 name: The name of the branch to checkout.
1707
1708 Returns:
1709 True if the checkout succeeded; False if it didn't; None if the branch
1710 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001711 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001712 rev = R_HEADS + name
1713 head = self.work_git.GetHead()
1714 if head == rev:
1715 # Already on the branch
1716 #
1717 return True
Wink Saville02d79452009-04-10 13:01:24 -07001718
David Pursehouse8a68ff92012-09-24 12:15:13 +09001719 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001720 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001721 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001722 except KeyError:
1723 # Branch does not exist in this project
1724 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001725 return None
Wink Saville02d79452009-04-10 13:01:24 -07001726
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001727 if head.startswith(R_HEADS):
1728 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001729 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001730 except KeyError:
1731 head = None
1732
1733 if head == revid:
1734 # Same revision; just update HEAD to point to the new
1735 # target branch, but otherwise take no other action.
1736 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001737 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1738 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001739 return True
1740
1741 return GitCommand(self,
1742 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001743 capture_stdout=True,
1744 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001745
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001746 def AbandonBranch(self, name):
1747 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001748
1749 Args:
1750 name: The name of the branch to abandon.
1751
1752 Returns:
1753 True if the abandon succeeded; False if it didn't; None if the branch
1754 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001755 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001756 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001757 all_refs = self.bare_ref.all
1758 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001759 # Doesn't exist
1760 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001761
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001762 head = self.work_git.GetHead()
1763 if head == rev:
1764 # We can't destroy the branch while we are sitting
1765 # on it. Switch to a detached HEAD.
1766 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001767 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001768
David Pursehouse8a68ff92012-09-24 12:15:13 +09001769 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001770 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001771 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001772 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001773 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001774
1775 return GitCommand(self,
1776 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001777 capture_stdout=True,
1778 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001779
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780 def PruneHeads(self):
1781 """Prune any topic branches already merged into upstream.
1782 """
1783 cb = self.CurrentBranch
1784 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001785 left = self._allrefs
1786 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001787 if name.startswith(R_HEADS):
1788 name = name[len(R_HEADS):]
1789 if cb is None or name != cb:
1790 kill.append(name)
1791
Mike Frysingera3794e92021-03-11 23:24:01 -05001792 # Minor optimization: If there's nothing to prune, then don't try to read
1793 # any project state.
1794 if not kill and not cb:
1795 return []
1796
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001797 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001798 if cb is not None \
1799 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001800 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001801 self.work_git.DetachHead(HEAD)
1802 kill.append(cb)
1803
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001804 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001805 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001806
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001807 try:
1808 self.bare_git.DetachHead(rev)
1809
1810 b = ['branch', '-d']
1811 b.extend(kill)
1812 b = GitCommand(self, b, bare=True,
1813 capture_stdout=True,
1814 capture_stderr=True)
1815 b.Wait()
1816 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001817 if ID_RE.match(old):
1818 self.bare_git.DetachHead(old)
1819 else:
1820 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001821 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001822
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001823 for branch in kill:
1824 if (R_HEADS + branch) not in left:
1825 self.CleanPublishedCache()
1826 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001827
1828 if cb and cb not in kill:
1829 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001830 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001831
1832 kept = []
1833 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001834 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001835 branch = self.GetBranch(branch)
1836 base = branch.LocalMerge
1837 if not base:
1838 base = rev
1839 kept.append(ReviewableBranch(self, branch, base))
1840 return kept
1841
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001842# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001843 def GetRegisteredSubprojects(self):
1844 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001845
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001846 def rec(subprojects):
1847 if not subprojects:
1848 return
1849 result.extend(subprojects)
1850 for p in subprojects:
1851 rec(p.subprojects)
1852 rec(self.subprojects)
1853 return result
1854
1855 def _GetSubmodules(self):
1856 # Unfortunately we cannot call `git submodule status --recursive` here
1857 # because the working tree might not exist yet, and it cannot be used
1858 # without a working tree in its current implementation.
1859
1860 def get_submodules(gitdir, rev):
1861 # Parse .gitmodules for submodule sub_paths and sub_urls
1862 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1863 if not sub_paths:
1864 return []
1865 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1866 # revision of submodule repository
1867 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1868 submodules = []
1869 for sub_path, sub_url in zip(sub_paths, sub_urls):
1870 try:
1871 sub_rev = sub_revs[sub_path]
1872 except KeyError:
1873 # Ignore non-exist submodules
1874 continue
1875 submodules.append((sub_rev, sub_path, sub_url))
1876 return submodules
1877
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001878 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1879 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001880
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001881 def parse_gitmodules(gitdir, rev):
1882 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1883 try:
Anthony King7bdac712014-07-16 12:56:40 +01001884 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1885 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001886 except GitError:
1887 return [], []
1888 if p.Wait() != 0:
1889 return [], []
1890
1891 gitmodules_lines = []
1892 fd, temp_gitmodules_path = tempfile.mkstemp()
1893 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001894 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001895 os.close(fd)
1896 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001897 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1898 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001899 if p.Wait() != 0:
1900 return [], []
1901 gitmodules_lines = p.stdout.split('\n')
1902 except GitError:
1903 return [], []
1904 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001905 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001906
1907 names = set()
1908 paths = {}
1909 urls = {}
1910 for line in gitmodules_lines:
1911 if not line:
1912 continue
1913 m = re_path.match(line)
1914 if m:
1915 names.add(m.group(1))
1916 paths[m.group(1)] = m.group(2)
1917 continue
1918 m = re_url.match(line)
1919 if m:
1920 names.add(m.group(1))
1921 urls[m.group(1)] = m.group(2)
1922 continue
1923 names = sorted(names)
1924 return ([paths.get(name, '') for name in names],
1925 [urls.get(name, '') for name in names])
1926
1927 def git_ls_tree(gitdir, rev, paths):
1928 cmd = ['ls-tree', rev, '--']
1929 cmd.extend(paths)
1930 try:
Anthony King7bdac712014-07-16 12:56:40 +01001931 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1932 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001933 except GitError:
1934 return []
1935 if p.Wait() != 0:
1936 return []
1937 objects = {}
1938 for line in p.stdout.split('\n'):
1939 if not line.strip():
1940 continue
1941 object_rev, object_path = line.split()[2:4]
1942 objects[object_path] = object_rev
1943 return objects
1944
1945 try:
1946 rev = self.GetRevisionId()
1947 except GitError:
1948 return []
1949 return get_submodules(self.gitdir, rev)
1950
1951 def GetDerivedSubprojects(self):
1952 result = []
1953 if not self.Exists:
1954 # If git repo does not exist yet, querying its submodules will
1955 # mess up its states; so return here.
1956 return result
1957 for rev, path, url in self._GetSubmodules():
1958 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001959 relpath, worktree, gitdir, objdir = \
1960 self.manifest.GetSubprojectPaths(self, name, path)
1961 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001962 if project:
1963 result.extend(project.GetDerivedSubprojects())
1964 continue
David James8d201162013-10-11 17:03:19 -07001965
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001966 if url.startswith('..'):
1967 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001968 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001969 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001970 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001971 review=self.remote.review,
1972 revision=self.remote.revision)
1973 subproject = Project(manifest=self.manifest,
1974 name=name,
1975 remote=remote,
1976 gitdir=gitdir,
1977 objdir=objdir,
1978 worktree=worktree,
1979 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001980 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001981 revisionId=rev,
1982 rebase=self.rebase,
1983 groups=self.groups,
1984 sync_c=self.sync_c,
1985 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001986 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001987 parent=self,
1988 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001989 result.append(subproject)
1990 result.extend(subproject.GetDerivedSubprojects())
1991 return result
1992
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001993# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001994 def EnableRepositoryExtension(self, key, value='true', version=1):
1995 """Enable git repository extension |key| with |value|.
1996
1997 Args:
1998 key: The extension to enabled. Omit the "extensions." prefix.
1999 value: The value to use for the extension.
2000 version: The minimum git repository version needed.
2001 """
2002 # Make sure the git repo version is new enough already.
2003 found_version = self.config.GetInt('core.repositoryFormatVersion')
2004 if found_version is None:
2005 found_version = 0
2006 if found_version < version:
2007 self.config.SetString('core.repositoryFormatVersion', str(version))
2008
2009 # Enable the extension!
2010 self.config.SetString('extensions.%s' % (key,), value)
2011
Mike Frysinger50a81de2020-09-06 15:51:21 -04002012 def ResolveRemoteHead(self, name=None):
2013 """Find out what the default branch (HEAD) points to.
2014
2015 Normally this points to refs/heads/master, but projects are moving to main.
2016 Support whatever the server uses rather than hardcoding "master" ourselves.
2017 """
2018 if name is None:
2019 name = self.remote.name
2020
2021 # The output will look like (NB: tabs are separators):
2022 # ref: refs/heads/master HEAD
2023 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2024 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2025
2026 for line in output.splitlines():
2027 lhs, rhs = line.split('\t', 1)
2028 if rhs == 'HEAD' and lhs.startswith('ref:'):
2029 return lhs[4:].strip()
2030
2031 return None
2032
Zac Livingstone4332262017-06-16 08:56:09 -06002033 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002034 try:
2035 # if revision (sha or tag) is not present then following function
2036 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08002037 self.bare_git.rev_list('-1', '--missing=allow-any',
2038 '%s^0' % self.revisionExpr, '--')
Chris AtLee2fb64662014-01-16 21:32:33 -05002039 return True
2040 except GitError:
2041 # There is no such persistent revision. We have to fetch it.
2042 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002043
Julien Campergue335f5ef2013-10-16 11:02:35 +02002044 def _FetchArchive(self, tarpath, cwd=None):
2045 cmd = ['archive', '-v', '-o', tarpath]
2046 cmd.append('--remote=%s' % self.remote.url)
2047 cmd.append('--prefix=%s/' % self.relpath)
2048 cmd.append(self.revisionExpr)
2049
2050 command = GitCommand(self, cmd, cwd=cwd,
2051 capture_stdout=True,
2052 capture_stderr=True)
2053
2054 if command.Wait() != 0:
2055 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2056
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002057 def _RemoteFetch(self, name=None,
2058 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002059 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002060 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002061 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002062 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002063 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002064 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002065 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002066 depth=None,
Mike Frysinger9d0e84c2019-03-18 21:27:54 -04002067 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002068 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002069 clone_filter=None,
2070 retry_fetches=2,
2071 retry_sleep_initial_sec=4.0,
2072 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002073 is_sha1 = False
2074 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002075 # The depth should not be used when fetching to a mirror because
2076 # it will result in a shallow repository that cannot be cloned or
2077 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002078 # The repo project should also never be synced with partial depth.
2079 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2080 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002081
Shawn Pearce69e04d82014-01-29 12:48:54 -08002082 if depth:
2083 current_branch_only = True
2084
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002085 if ID_RE.match(self.revisionExpr) is not None:
2086 is_sha1 = True
2087
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002088 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002089 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002090 # this is a tag and its sha1 value should never change
2091 tag_name = self.revisionExpr[len(R_TAGS):]
2092
2093 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002094 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002095 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002096 print('Skipped fetching project %s (already have persistent ref)'
2097 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002098 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002099 if is_sha1 and not depth:
2100 # When syncing a specific commit and --depth is not set:
2101 # * if upstream is explicitly specified and is not a sha1, fetch only
2102 # upstream as users expect only upstream to be fetch.
2103 # Note: The commit might not be in upstream in which case the sync
2104 # will fail.
2105 # * otherwise, fetch all branches to make sure we end up with the
2106 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002107 if self.upstream:
2108 current_branch_only = not ID_RE.match(self.upstream)
2109 else:
2110 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002111
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002112 if not name:
2113 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002114
2115 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002116 remote = self.GetRemote(name)
2117 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002118 ssh_proxy = True
2119
Shawn O. Pearce88443382010-10-08 10:02:09 +02002120 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002121 if alt_dir and 'objects' == os.path.basename(alt_dir):
2122 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002123 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2124 remote = self.GetRemote(name)
2125
David Pursehouse8a68ff92012-09-24 12:15:13 +09002126 all_refs = self.bare_ref.all
2127 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002128 tmp = set()
2129
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302130 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002131 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002132 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002133 all_refs[r] = ref_id
2134 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002135 continue
2136
David Pursehouse8a68ff92012-09-24 12:15:13 +09002137 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002138 continue
2139
David Pursehouse8a68ff92012-09-24 12:15:13 +09002140 r = 'refs/_alt/%s' % ref_id
2141 all_refs[r] = ref_id
2142 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002143 tmp.add(r)
2144
heping3d7bbc92017-04-12 19:51:47 +08002145 tmp_packed_lines = []
2146 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002147
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302148 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002149 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002150 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002151 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002152 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002153
heping3d7bbc92017-04-12 19:51:47 +08002154 tmp_packed = ''.join(tmp_packed_lines)
2155 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002156 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002157 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002158 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002159
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002160 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002161
Xin Li745be2e2019-06-03 11:24:30 -07002162 if clone_filter:
2163 git_require((2, 19, 0), fail=True, msg='partial clones')
2164 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002165 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002166
Conley Owensf97e8382015-01-21 11:12:46 -08002167 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002168 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002169 else:
2170 # If this repo has shallow objects, then we don't know which refs have
2171 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2172 # do this with projects that don't have shallow objects, since it is less
2173 # efficient.
2174 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2175 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002176
Mike Frysinger4847e052020-02-22 00:07:35 -05002177 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002178 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002179 if not quiet and sys.stdout.isatty():
2180 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002181 if not self.worktree:
2182 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002183 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002184
Mike Frysinger9d0e84c2019-03-18 21:27:54 -04002185 if force_sync:
2186 cmd.append('--force')
2187
David Pursehouse74cfd272015-10-14 10:50:15 +09002188 if prune:
2189 cmd.append('--prune')
2190
Martin Kellye4e94d22017-03-21 16:05:12 -07002191 if submodules:
2192 cmd.append('--recurse-submodules=on-demand')
2193
Kuang-che Wu6856f982019-11-25 12:37:55 +08002194 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002195 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002196 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002197 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002198 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002199 spec.append('tag')
2200 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002201
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302202 if self.manifest.IsMirror and not current_branch_only:
2203 branch = None
2204 else:
2205 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002206 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002207 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002208 # Shallow checkout of a specific commit, fetch from that commit and not
2209 # the heads only as the commit might be deeper in the history.
2210 spec.append(branch)
2211 else:
2212 if is_sha1:
2213 branch = self.upstream
2214 if branch is not None and branch.strip():
2215 if not branch.startswith('refs/'):
2216 branch = R_HEADS + branch
2217 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2218
2219 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2220 # whole repo.
2221 if self.manifest.IsMirror and not spec:
2222 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2223
2224 # If using depth then we should not get all the tags since they may
2225 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002226 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002227 cmd.append('--no-tags')
2228 else:
2229 cmd.append('--tags')
2230 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2231
Conley Owens80b87fe2014-05-09 17:13:44 -07002232 cmd.extend(spec)
2233
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002234 # At least one retry minimum due to git remote prune.
2235 retry_fetches = max(retry_fetches, 2)
2236 retry_cur_sleep = retry_sleep_initial_sec
2237 ok = prune_tried = False
2238 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002239 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002240 merge_output=True, capture_stdout=quiet or bool(output_redir))
2241 if gitcmd.stdout and not quiet and output_redir:
2242 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002243 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002244 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002245 ok = True
2246 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002247
2248 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002249 elif (gitcmd.stdout and
2250 'error:' in gitcmd.stdout and
2251 'HTTP 429' in gitcmd.stdout):
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002252 if not quiet:
2253 print('429 received, sleeping: %s sec' % retry_cur_sleep,
2254 file=sys.stderr)
2255 time.sleep(retry_cur_sleep)
2256 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2257 MAXIMUM_RETRY_SLEEP_SEC)
2258 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2259 RETRY_JITTER_PERCENT))
2260 continue
2261
2262 # If this is not last attempt, try 'git remote prune'.
2263 elif (try_n < retry_fetches - 1 and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002264 gitcmd.stdout and
2265 'error:' in gitcmd.stdout and
2266 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002267 not prune_tried):
2268 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002269 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002270 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002271 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002272 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002273 break
2274 continue
Brian Harring14a66742012-09-28 20:21:57 -07002275 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002276 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2277 # in sha1 mode, we just tried sync'ing from the upstream field; it
2278 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002279 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002280 elif ret < 0:
2281 # Git died with a signal, exit immediately
2282 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002283 if not verbose:
Mike Frysinger7b586f22021-02-23 18:38:39 -05002284 print('\n%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002285 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002286
2287 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002288 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002289 if old_packed != '':
2290 _lwrite(packed_refs, old_packed)
2291 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002292 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002293 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002294
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002295 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002296 # We just synced the upstream given branch; verify we
2297 # got what we wanted, else trigger a second run of all
2298 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002299 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002300 # Sync the current branch only with depth set to None.
2301 # We always pass depth=None down to avoid infinite recursion.
2302 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002303 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002304 current_branch_only=current_branch_only and depth,
2305 initial=False, alt_dir=alt_dir,
2306 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002307
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002308 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002309
Mike Frysingere50b6a72020-02-19 01:45:48 -05002310 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002311 if initial and \
2312 (self.manifest.manifestProject.config.GetString('repo.depth') or
2313 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002314 return False
2315
2316 remote = self.GetRemote(self.remote.name)
2317 bundle_url = remote.url + '/clone.bundle'
2318 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002319 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2320 'persistent-http',
2321 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002322 return False
2323
2324 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2325 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2326
2327 exist_dst = os.path.exists(bundle_dst)
2328 exist_tmp = os.path.exists(bundle_tmp)
2329
2330 if not initial and not exist_dst and not exist_tmp:
2331 return False
2332
2333 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002334 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2335 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002336 if not exist_dst:
2337 return False
2338
2339 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002340 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002341 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002342 if not quiet and sys.stdout.isatty():
2343 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002344 if not self.worktree:
2345 cmd.append('--update-head-ok')
2346 cmd.append(bundle_dst)
2347 for f in remote.fetch:
2348 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002349 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002350
2351 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002352 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002353 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002354 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002355 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002356 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002357
Mike Frysingere50b6a72020-02-19 01:45:48 -05002358 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002359 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002360 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002361
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002362 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002363 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002364 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002365 if os.path.exists(tmpPath):
2366 size = os.stat(tmpPath).st_size
2367 if size >= 1024:
2368 cmd += ['--continue-at', '%d' % (size,)]
2369 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002370 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002371 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002372 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002373 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002374 if proxy:
2375 cmd += ['--proxy', proxy]
2376 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2377 cmd += ['--proxy', os.environ['http_proxy']]
2378 if srcUrl.startswith('persistent-https'):
2379 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2380 elif srcUrl.startswith('persistent-http'):
2381 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002382 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002383
Dave Borowitz137d0132015-01-02 11:12:54 -08002384 if IsTrace():
2385 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002386 if verbose:
2387 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2388 stdout = None if verbose else subprocess.PIPE
2389 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002390 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002391 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002392 except OSError:
2393 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002394
Mike Frysingere50b6a72020-02-19 01:45:48 -05002395 (output, _) = proc.communicate()
2396 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002397
Dave Borowitz137d0132015-01-02 11:12:54 -08002398 if curlret == 22:
2399 # From curl man page:
2400 # 22: HTTP page not retrieved. The requested url was not found or
2401 # returned another error with the HTTP error code being 400 or above.
2402 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002403 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002404 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2405 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002406 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002407 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002408 elif curlret and not verbose and output:
2409 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002410
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002411 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002412 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002413 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002414 return True
2415 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002416 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002417 return False
2418 else:
2419 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002420
Kris Giesingc8d882a2014-12-23 13:02:32 -08002421 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002422 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002423 with open(path, 'rb') as f:
2424 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002425 return True
2426 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002427 if not quiet:
2428 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002429 return False
2430 except OSError:
2431 return False
2432
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002433 def _Checkout(self, rev, quiet=False):
2434 cmd = ['checkout']
2435 if quiet:
2436 cmd.append('-q')
2437 cmd.append(rev)
2438 cmd.append('--')
2439 if GitCommand(self, cmd).Wait() != 0:
2440 if self._allrefs:
2441 raise GitError('%s checkout %s ' % (self.name, rev))
2442
Mike Frysinger915fda12020-03-22 12:15:20 -04002443 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002444 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002445 if ffonly:
2446 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002447 if record_origin:
2448 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002449 cmd.append(rev)
2450 cmd.append('--')
2451 if GitCommand(self, cmd).Wait() != 0:
2452 if self._allrefs:
2453 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2454
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302455 def _LsRemote(self, refs):
2456 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302457 p = GitCommand(self, cmd, capture_stdout=True)
2458 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002459 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302460 return None
2461
Anthony King7bdac712014-07-16 12:56:40 +01002462 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002463 cmd = ['revert']
2464 cmd.append('--no-edit')
2465 cmd.append(rev)
2466 cmd.append('--')
2467 if GitCommand(self, cmd).Wait() != 0:
2468 if self._allrefs:
2469 raise GitError('%s revert %s ' % (self.name, rev))
2470
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002471 def _ResetHard(self, rev, quiet=True):
2472 cmd = ['reset', '--hard']
2473 if quiet:
2474 cmd.append('-q')
2475 cmd.append(rev)
2476 if GitCommand(self, cmd).Wait() != 0:
2477 raise GitError('%s reset --hard %s ' % (self.name, rev))
2478
Martin Kellye4e94d22017-03-21 16:05:12 -07002479 def _SyncSubmodules(self, quiet=True):
2480 cmd = ['submodule', 'update', '--init', '--recursive']
2481 if quiet:
2482 cmd.append('-q')
2483 if GitCommand(self, cmd).Wait() != 0:
2484 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2485
Anthony King7bdac712014-07-16 12:56:40 +01002486 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002487 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002488 if onto is not None:
2489 cmd.extend(['--onto', onto])
2490 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002491 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002492 raise GitError('%s rebase %s ' % (self.name, upstream))
2493
Pierre Tardy3d125942012-05-04 12:18:12 +02002494 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002495 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002496 if ffonly:
2497 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002498 if GitCommand(self, cmd).Wait() != 0:
2499 raise GitError('%s merge %s ' % (self.name, head))
2500
David Pursehousee8ace262020-02-13 12:41:15 +09002501 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002502 init_git_dir = not os.path.exists(self.gitdir)
2503 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002504 try:
2505 # Initialize the bare repository, which contains all of the objects.
2506 if init_obj_dir:
2507 os.makedirs(self.objdir)
2508 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002509
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002510 if self.use_git_worktrees:
2511 # Set up the m/ space to point to the worktree-specific ref space.
2512 # We'll update the worktree-specific ref space on each checkout.
2513 if self.manifest.branch:
2514 self.bare_git.symbolic_ref(
2515 '-m', 'redirecting to worktree scope',
2516 R_M + self.manifest.branch,
2517 R_WORKTREE_M + self.manifest.branch)
2518
2519 # Enable per-worktree config file support if possible. This is more a
2520 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002521 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002522 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002523
Kevin Degib1a07b82015-07-27 13:33:43 -06002524 # If we have a separate directory to hold refs, initialize it as well.
2525 if self.objdir != self.gitdir:
2526 if init_git_dir:
2527 os.makedirs(self.gitdir)
2528
2529 if init_obj_dir or init_git_dir:
2530 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2531 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002532 try:
2533 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2534 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002535 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002536 print("Retrying clone after deleting %s" %
2537 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002538 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002539 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2540 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002541 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002542 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002543 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2544 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002545 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002546 raise e
2547 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002548
Kevin Degi384b3c52014-10-16 16:02:58 -06002549 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002550 mp = self.manifest.manifestProject
2551 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002552
Kevin Degib1a07b82015-07-27 13:33:43 -06002553 if ref_dir or mirror_git:
2554 if not mirror_git:
2555 mirror_git = os.path.join(ref_dir, self.name + '.git')
2556 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2557 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002558 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2559 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002560
Kevin Degib1a07b82015-07-27 13:33:43 -06002561 if os.path.exists(mirror_git):
2562 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002563 elif os.path.exists(repo_git):
2564 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002565 elif os.path.exists(worktrees_git):
2566 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002567 else:
2568 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002569
Kevin Degib1a07b82015-07-27 13:33:43 -06002570 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002571 if not os.path.isabs(ref_dir):
2572 # The alternate directory is relative to the object database.
2573 ref_dir = os.path.relpath(ref_dir,
2574 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002575 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2576 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002577
David Pursehousee8ace262020-02-13 12:41:15 +09002578 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002579
2580 m = self.manifest.manifestProject.config
2581 for key in ['user.name', 'user.email']:
2582 if m.Has(key, include_defaults=False):
2583 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002584 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002585 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002586 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002587 except Exception:
2588 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002589 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002590 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002591 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002592 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002593
David Pursehousee8ace262020-02-13 12:41:15 +09002594 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002595 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002596 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002597
David Pursehousee8ace262020-02-13 12:41:15 +09002598 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002599 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002600 if not os.path.exists(hooks):
2601 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002602 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002603 name = os.path.basename(stock_hook)
2604
Victor Boivie65e0f352011-04-18 11:23:29 +02002605 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002606 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002607 # Don't install a Gerrit Code Review hook if this
2608 # project does not appear to use it for reviews.
2609 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002610 # Since the manifest project is one of those, but also
2611 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002612 continue
2613
2614 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002615 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002616 continue
2617 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002618 # If the files are the same, we'll leave it alone. We create symlinks
2619 # below by default but fallback to hardlinks if the OS blocks them.
2620 # So if we're here, it's probably because we made a hardlink below.
2621 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002622 if not quiet:
2623 _warn("%s: Not replacing locally modified %s hook",
2624 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002625 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002626 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002627 platform_utils.symlink(
2628 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002629 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002630 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002631 try:
2632 os.link(stock_hook, dst)
2633 except OSError:
2634 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002635 else:
2636 raise
2637
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002638 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002639 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002640 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002641 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002642 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002643 remote.review = self.remote.review
2644 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002645
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002646 if self.worktree:
2647 remote.ResetFetch(mirror=False)
2648 else:
2649 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002650 remote.Save()
2651
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002652 def _InitMRef(self):
2653 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002654 if self.use_git_worktrees:
2655 # We can't update this ref with git worktrees until it exists.
2656 # We'll wait until the initial checkout to set it.
2657 if not os.path.exists(self.worktree):
2658 return
2659
2660 base = R_WORKTREE_M
2661 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002662
2663 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002664 else:
2665 base = R_M
2666 active_git = self.bare_git
2667
2668 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002669
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002670 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002671 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002672
Remy Böhmer1469c282020-12-15 18:49:02 +01002673 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002674 cur = self.bare_ref.symref(ref)
2675
2676 if self.revisionId:
2677 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2678 msg = 'manifest set to %s' % self.revisionId
2679 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002680 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002681 else:
2682 remote = self.GetRemote(self.remote.name)
2683 dst = remote.ToLocal(self.revisionExpr)
2684 if cur != dst:
2685 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002686 if detach:
2687 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2688 else:
2689 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002690
Kevin Degi384b3c52014-10-16 16:02:58 -06002691 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002692 # Git worktrees don't use symlinks to share at all.
2693 if self.use_git_worktrees:
2694 return
2695
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002696 symlink_files = self.shareable_files[:]
2697 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002698 if share_refs:
2699 symlink_files += self.working_tree_files
2700 symlink_dirs += self.working_tree_dirs
2701 to_symlink = symlink_files + symlink_dirs
2702 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002703 # Try to self-heal a bit in simple cases.
2704 dst_path = os.path.join(destdir, name)
2705 src_path = os.path.join(srcdir, name)
2706
2707 if name in self.working_tree_dirs:
2708 # If the dir is missing under .repo/projects/, create it.
2709 if not os.path.exists(src_path):
2710 os.makedirs(src_path)
2711
2712 elif name in self.working_tree_files:
2713 # If it's a file under the checkout .git/ and the .repo/projects/ has
2714 # nothing, move the file under the .repo/projects/ tree.
2715 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2716 platform_utils.rename(dst_path, src_path)
2717
2718 # If the path exists under the .repo/projects/ and there's no symlink
2719 # under the checkout .git/, recreate the symlink.
2720 if name in self.working_tree_dirs or name in self.working_tree_files:
2721 if os.path.exists(src_path) and not os.path.exists(dst_path):
2722 platform_utils.symlink(
2723 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2724
2725 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002726 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002727 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002728 # Fail if the links are pointing to the wrong place
2729 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002730 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002731 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002732 'work tree. If you\'re comfortable with the '
2733 'possibility of losing the work tree\'s git metadata,'
2734 ' use `repo sync --force-sync {0}` to '
2735 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002736
David James8d201162013-10-11 17:03:19 -07002737 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2738 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2739
2740 Args:
2741 gitdir: The bare git repository. Must already be initialized.
2742 dotgit: The repository you would like to initialize.
2743 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2744 Only one work tree can store refs under a given |gitdir|.
2745 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2746 This saves you the effort of initializing |dotgit| yourself.
2747 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002748 symlink_files = self.shareable_files[:]
2749 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002750 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002751 symlink_files += self.working_tree_files
2752 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002753 to_symlink = symlink_files + symlink_dirs
2754
2755 to_copy = []
2756 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002757 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002758
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002759 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002760 for name in set(to_copy).union(to_symlink):
2761 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002762 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002763 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002764
Kevin Degi384b3c52014-10-16 16:02:58 -06002765 if os.path.lexists(dst):
2766 continue
David James8d201162013-10-11 17:03:19 -07002767
2768 # If the source dir doesn't exist, create an empty dir.
2769 if name in symlink_dirs and not os.path.lexists(src):
2770 os.makedirs(src)
2771
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002772 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002773 platform_utils.symlink(
2774 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002775 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002776 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002777 shutil.copytree(src, dst)
2778 elif os.path.isfile(src):
2779 shutil.copy(src, dst)
2780
Conley Owens80b87fe2014-05-09 17:13:44 -07002781 # If the source file doesn't exist, ensure the destination
2782 # file doesn't either.
2783 if name in symlink_files and not os.path.lexists(src):
2784 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002785 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002786 except OSError:
2787 pass
2788
David James8d201162013-10-11 17:03:19 -07002789 except OSError as e:
2790 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002791 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002792 else:
2793 raise
2794
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002795 def _InitGitWorktree(self):
2796 """Init the project using git worktrees."""
2797 self.bare_git.worktree('prune')
2798 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2799 self.worktree, self.GetRevisionId())
2800
2801 # Rewrite the internal state files to use relative paths between the
2802 # checkouts & worktrees.
2803 dotgit = os.path.join(self.worktree, '.git')
2804 with open(dotgit, 'r') as fp:
2805 # Figure out the checkout->worktree path.
2806 setting = fp.read()
2807 assert setting.startswith('gitdir:')
2808 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002809 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2810 # of file permissions. Delete it and recreate it from scratch to avoid.
2811 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002812 # Use relative path from checkout->worktree & maintain Unix line endings
2813 # on all OS's to match git behavior.
2814 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002815 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2816 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002817 # Use relative path from worktree->checkout & maintain Unix line endings
2818 # on all OS's to match git behavior.
2819 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002820 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2821
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002822 self._InitMRef()
2823
Martin Kellye4e94d22017-03-21 16:05:12 -07002824 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002825 realdotgit = os.path.join(self.worktree, '.git')
2826 tmpdotgit = realdotgit + '.tmp'
2827 init_dotgit = not os.path.exists(realdotgit)
2828 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002829 if self.use_git_worktrees:
2830 self._InitGitWorktree()
2831 self._CopyAndLinkFiles()
2832 return
2833
Mike Frysingerf4545122019-11-11 04:34:16 -05002834 dotgit = tmpdotgit
2835 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2836 os.makedirs(tmpdotgit)
2837 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2838 copy_all=False)
2839 else:
2840 dotgit = realdotgit
2841
Kevin Degib1a07b82015-07-27 13:33:43 -06002842 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002843 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2844 except GitError as e:
2845 if force_sync and not init_dotgit:
2846 try:
2847 platform_utils.rmtree(dotgit)
2848 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002849 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002850 raise e
2851 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002852
Mike Frysingerf4545122019-11-11 04:34:16 -05002853 if init_dotgit:
2854 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002855
Mike Frysingerf4545122019-11-11 04:34:16 -05002856 # Now that the .git dir is fully set up, move it to its final home.
2857 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002858
Mike Frysingerf4545122019-11-11 04:34:16 -05002859 # Finish checking out the worktree.
2860 cmd = ['read-tree', '--reset', '-u']
2861 cmd.append('-v')
2862 cmd.append(HEAD)
2863 if GitCommand(self, cmd).Wait() != 0:
2864 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002865
Mike Frysingerf4545122019-11-11 04:34:16 -05002866 if submodules:
2867 self._SyncSubmodules(quiet=True)
2868 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002869
Renaud Paquay788e9622017-01-27 11:41:12 -08002870 def _get_symlink_error_message(self):
2871 if platform_utils.isWindows():
2872 return ('Unable to create symbolic link. Please re-run the command as '
2873 'Administrator, or see '
2874 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2875 'for other options.')
2876 return 'filesystem must support symlinks'
2877
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002878 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002879 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002880
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002881 def _revlist(self, *args, **kw):
2882 a = []
2883 a.extend(args)
2884 a.append('--')
2885 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002886
2887 @property
2888 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002889 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002890
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002891 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002892 """Get logs between two revisions of this project."""
2893 comp = '..'
2894 if rev1:
2895 revs = [rev1]
2896 if rev2:
2897 revs.extend([comp, rev2])
2898 cmd = ['log', ''.join(revs)]
2899 out = DiffColoring(self.config)
2900 if out.is_on and color:
2901 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002902 if pretty_format is not None:
2903 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002904 if oneline:
2905 cmd.append('--oneline')
2906
2907 try:
2908 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2909 if log.Wait() == 0:
2910 return log.stdout
2911 except GitError:
2912 # worktree may not exist if groups changed for example. In that case,
2913 # try in gitdir instead.
2914 if not os.path.exists(self.worktree):
2915 return self.bare_git.log(*cmd[1:])
2916 else:
2917 raise
2918 return None
2919
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002920 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2921 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002922 """Get the list of logs from this revision to given revisionId"""
2923 logs = {}
2924 selfId = self.GetRevisionId(self._allrefs)
2925 toId = toProject.GetRevisionId(toProject._allrefs)
2926
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002927 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2928 pretty_format=pretty_format)
2929 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2930 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002931 return logs
2932
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002933 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002934
David James8d201162013-10-11 17:03:19 -07002935 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002936 self._project = project
2937 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002938 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002939
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002940 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2941 def __getstate__(self):
2942 return (self._project, self._bare, self._gitdir)
2943
2944 def __setstate__(self, state):
2945 self._project, self._bare, self._gitdir = state
2946
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002947 def LsOthers(self):
2948 p = GitCommand(self._project,
2949 ['ls-files',
2950 '-z',
2951 '--others',
2952 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002953 bare=False,
David James8d201162013-10-11 17:03:19 -07002954 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002955 capture_stdout=True,
2956 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002957 if p.Wait() == 0:
2958 out = p.stdout
2959 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002960 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002961 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002962 return []
2963
2964 def DiffZ(self, name, *args):
2965 cmd = [name]
2966 cmd.append('-z')
Eli Ribble7b4f0192019-05-02 18:21:42 -07002967 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002968 cmd.extend(args)
2969 p = GitCommand(self._project,
2970 cmd,
David James8d201162013-10-11 17:03:19 -07002971 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002972 bare=False,
2973 capture_stdout=True,
2974 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002975 p.Wait()
2976 r = {}
2977 out = p.stdout
2978 if out:
2979 out = iter(out[:-1].split('\0'))
2980 while out:
2981 try:
2982 info = next(out)
2983 path = next(out)
2984 except StopIteration:
2985 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002986
Mike Frysinger84230002021-02-16 17:08:35 -05002987 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002988
Mike Frysinger84230002021-02-16 17:08:35 -05002989 def __init__(self, path, omode, nmode, oid, nid, state):
2990 self.path = path
2991 self.src_path = None
2992 self.old_mode = omode
2993 self.new_mode = nmode
2994 self.old_id = oid
2995 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002996
Mike Frysinger84230002021-02-16 17:08:35 -05002997 if len(state) == 1:
2998 self.status = state
2999 self.level = None
3000 else:
3001 self.status = state[:1]
3002 self.level = state[1:]
3003 while self.level.startswith('0'):
3004 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003005
Mike Frysinger84230002021-02-16 17:08:35 -05003006 info = info[1:].split(' ')
3007 info = _Info(path, *info)
3008 if info.status in ('R', 'C'):
3009 info.src_path = info.path
3010 info.path = next(out)
3011 r[info.path] = info
3012 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003013
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003014 def GetDotgitPath(self, subpath=None):
3015 """Return the full path to the .git dir.
3016
3017 As a convenience, append |subpath| if provided.
3018 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003019 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003020 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003021 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003022 dotgit = os.path.join(self._project.worktree, '.git')
3023 if os.path.isfile(dotgit):
3024 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3025 with open(dotgit) as fp:
3026 setting = fp.read()
3027 assert setting.startswith('gitdir:')
3028 gitdir = setting.split(':', 1)[1].strip()
3029 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3030
3031 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3032
3033 def GetHead(self):
3034 """Return the ref that HEAD points to."""
3035 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003036 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003037 with open(path) as fd:
3038 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003039 except IOError as e:
3040 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003041 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303042 line = line.decode()
3043 except AttributeError:
3044 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003045 if line.startswith('ref: '):
3046 return line[5:-1]
3047 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003048
3049 def SetHead(self, ref, message=None):
3050 cmdv = []
3051 if message is not None:
3052 cmdv.extend(['-m', message])
3053 cmdv.append(HEAD)
3054 cmdv.append(ref)
3055 self.symbolic_ref(*cmdv)
3056
3057 def DetachHead(self, new, message=None):
3058 cmdv = ['--no-deref']
3059 if message is not None:
3060 cmdv.extend(['-m', message])
3061 cmdv.append(HEAD)
3062 cmdv.append(new)
3063 self.update_ref(*cmdv)
3064
3065 def UpdateRef(self, name, new, old=None,
3066 message=None,
3067 detach=False):
3068 cmdv = []
3069 if message is not None:
3070 cmdv.extend(['-m', message])
3071 if detach:
3072 cmdv.append('--no-deref')
3073 cmdv.append(name)
3074 cmdv.append(new)
3075 if old is not None:
3076 cmdv.append(old)
3077 self.update_ref(*cmdv)
3078
3079 def DeleteRef(self, name, old=None):
3080 if not old:
3081 old = self.rev_parse(name)
3082 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003083 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003084
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003085 def rev_list(self, *args, **kw):
3086 if 'format' in kw:
3087 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3088 else:
3089 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003090 cmdv.extend(args)
3091 p = GitCommand(self._project,
3092 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003093 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003094 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003095 capture_stdout=True,
3096 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003097 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003098 raise GitError('%s rev-list %s: %s' %
3099 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003100 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003101
3102 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003103 """Allow arbitrary git commands using pythonic syntax.
3104
3105 This allows you to do things like:
3106 git_obj.rev_parse('HEAD')
3107
3108 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3109 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003110 Any other positional arguments will be passed to the git command, and the
3111 following keyword arguments are supported:
3112 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003113
3114 Args:
3115 name: The name of the git command to call. Any '_' characters will
3116 be replaced with '-'.
3117
3118 Returns:
3119 A callable object that will try to call git with the named command.
3120 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003121 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003122
Dave Borowitz091f8932012-10-23 17:01:04 -07003123 def runner(*args, **kwargs):
3124 cmdv = []
3125 config = kwargs.pop('config', None)
3126 for k in kwargs:
3127 raise TypeError('%s() got an unexpected keyword argument %r'
3128 % (name, k))
3129 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303130 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003131 cmdv.append('-c')
3132 cmdv.append('%s=%s' % (k, v))
3133 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003134 cmdv.extend(args)
3135 p = GitCommand(self._project,
3136 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003137 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003138 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003139 capture_stdout=True,
3140 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003141 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003142 raise GitError('%s %s: %s' %
3143 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003144 r = p.stdout
3145 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3146 return r[:-1]
3147 return r
3148 return runner
3149
3150
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003151class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003152
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003153 def __str__(self):
3154 return 'prior sync failed; rebase still in progress'
3155
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003156
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003157class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003158
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003159 def __str__(self):
3160 return 'contains uncommitted changes'
3161
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003162
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003163class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003164
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003165 def __init__(self, project, text):
3166 self.project = project
3167 self.text = text
3168
3169 def Print(self, syncbuf):
3170 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3171 syncbuf.out.nl()
3172
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003173
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003174class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003175
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003176 def __init__(self, project, why):
3177 self.project = project
3178 self.why = why
3179
3180 def Print(self, syncbuf):
3181 syncbuf.out.fail('error: %s/: %s',
3182 self.project.relpath,
3183 str(self.why))
3184 syncbuf.out.nl()
3185
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003186
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003187class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003188
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003189 def __init__(self, project, action):
3190 self.project = project
3191 self.action = action
3192
3193 def Run(self, syncbuf):
3194 out = syncbuf.out
3195 out.project('project %s/', self.project.relpath)
3196 out.nl()
3197 try:
3198 self.action()
3199 out.nl()
3200 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003201 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003202 out.nl()
3203 return False
3204
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003205
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003206class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003207
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003208 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003209 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003210 self.project = self.printer('header', attr='bold')
3211 self.info = self.printer('info')
3212 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003213
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003214
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003215class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003216
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003217 def __init__(self, config, detach_head=False):
3218 self._messages = []
3219 self._failures = []
3220 self._later_queue1 = []
3221 self._later_queue2 = []
3222
3223 self.out = _SyncColoring(config)
3224 self.out.redirect(sys.stderr)
3225
3226 self.detach_head = detach_head
3227 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003228 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003229
3230 def info(self, project, fmt, *args):
3231 self._messages.append(_InfoMessage(project, fmt % args))
3232
3233 def fail(self, project, err=None):
3234 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003235 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003236
3237 def later1(self, project, what):
3238 self._later_queue1.append(_Later(project, what))
3239
3240 def later2(self, project, what):
3241 self._later_queue2.append(_Later(project, what))
3242
3243 def Finish(self):
3244 self._PrintMessages()
3245 self._RunLater()
3246 self._PrintMessages()
3247 return self.clean
3248
David Rileye0684ad2017-04-05 00:02:59 -07003249 def Recently(self):
3250 recent_clean = self.recent_clean
3251 self.recent_clean = True
3252 return recent_clean
3253
3254 def _MarkUnclean(self):
3255 self.clean = False
3256 self.recent_clean = False
3257
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003258 def _RunLater(self):
3259 for q in ['_later_queue1', '_later_queue2']:
3260 if not self._RunQueue(q):
3261 return
3262
3263 def _RunQueue(self, queue):
3264 for m in getattr(self, queue):
3265 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003266 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003267 return False
3268 setattr(self, queue, [])
3269 return True
3270
3271 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003272 if self._messages or self._failures:
3273 if os.isatty(2):
3274 self.out.write(progress.CSI_ERASE_LINE)
3275 self.out.write('\r')
3276
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003277 for m in self._messages:
3278 m.Print(self)
3279 for m in self._failures:
3280 m.Print(self)
3281
3282 self._messages = []
3283 self._failures = []
3284
3285
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003286class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003287
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003288 """A special project housed under .repo.
3289 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003290
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003291 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003292 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003293 manifest=manifest,
3294 name=name,
3295 gitdir=gitdir,
3296 objdir=gitdir,
3297 worktree=worktree,
3298 remote=RemoteSpec('origin'),
3299 relpath='.repo/%s' % name,
3300 revisionExpr='refs/heads/master',
3301 revisionId=None,
3302 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003303
3304 def PreSync(self):
3305 if self.Exists:
3306 cb = self.CurrentBranch
3307 if cb:
3308 base = self.GetBranch(cb).merge
3309 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003310 self.revisionExpr = base
3311 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003312
Martin Kelly224a31a2017-07-10 14:46:25 -07003313 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003314 """ Prepare MetaProject for manifest branch switch
3315 """
3316
3317 # detach and delete manifest branch, allowing a new
3318 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003319 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003320 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003321 syncbuf.Finish()
3322
3323 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003324 ['update-ref', '-d', 'refs/heads/default'],
3325 capture_stdout=True,
3326 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003327
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003328 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003329 def LastFetch(self):
3330 try:
3331 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3332 return os.path.getmtime(fh)
3333 except OSError:
3334 return 0
3335
3336 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003337 def HasChanges(self):
3338 """Has the remote received new commits not yet checked out?
3339 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003340 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003341 return False
3342
David Pursehouse8a68ff92012-09-24 12:15:13 +09003343 all_refs = self.bare_ref.all
3344 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003345 head = self.work_git.GetHead()
3346 if head.startswith(R_HEADS):
3347 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003348 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003349 except KeyError:
3350 head = None
3351
3352 if revid == head:
3353 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003354 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003355 return True
3356 return False