blob: 2567c57d37ca281f370af2aaa3fe10d5384df5b7 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080015import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070016import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070017import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070019import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import re
21import shutil
22import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070023import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020025import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080026import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070027import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040028import urllib.parse
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070029
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070031from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070032from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
33 ID_RE
Remy Bohmer16c13282020-09-10 10:38:04 +020034from error import GitError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040035from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080036from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070037import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040038import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040039from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050041from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070043
George Engelbrecht9bc283e2020-04-02 12:36:09 -060044# Maximum sleep time allowed during retries.
45MAXIMUM_RETRY_SLEEP_SEC = 3600.0
46# +-10% random jitter is added to each Fetches retry sleep duration.
47RETRY_JITTER_PERCENT = 0.1
48
49
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070050def _lwrite(path, content):
51 lock = '%s.lock' % path
52
Remy Bohmer169b0212020-11-21 10:57:52 +010053 # Maintain Unix line endings on all OS's to match git behavior.
54 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070055 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070056
57 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070058 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070059 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080060 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070061 raise
62
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070063
Shawn O. Pearce48244782009-04-16 08:25:57 -070064def _error(fmt, *args):
65 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070066 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070067
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070068
David Pursehousef33929d2015-08-24 14:39:14 +090069def _warn(fmt, *args):
70 msg = fmt % args
71 print('warn: %s' % msg, file=sys.stderr)
72
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070073
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070074def not_rev(r):
75 return '^' + r
76
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070077
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080078def sq(r):
79 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080080
David Pursehouse819827a2020-02-12 15:20:19 +090081
Jonathan Nieder93719792015-03-17 11:29:58 -070082_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070083
84
Jonathan Nieder93719792015-03-17 11:29:58 -070085def _ProjectHooks():
86 """List the hooks present in the 'hooks' directory.
87
88 These hooks are project hooks and are copied to the '.git/hooks' directory
89 of all subprojects.
90
91 This function caches the list of hooks (based on the contents of the
92 'repo/hooks' directory) on the first call.
93
94 Returns:
95 A list of absolute paths to all of the files in the hooks directory.
96 """
97 global _project_hook_list
98 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -070099 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700100 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700101 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700102 return _project_hook_list
103
104
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700105class DownloadedChange(object):
106 _commit_cache = None
107
108 def __init__(self, project, base, change_id, ps_id, commit):
109 self.project = project
110 self.base = base
111 self.change_id = change_id
112 self.ps_id = ps_id
113 self.commit = commit
114
115 @property
116 def commits(self):
117 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700118 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
119 '--abbrev-commit',
120 '--pretty=oneline',
121 '--reverse',
122 '--date-order',
123 not_rev(self.base),
124 self.commit,
125 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700126 return self._commit_cache
127
128
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700129class ReviewableBranch(object):
130 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400131 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700132
133 def __init__(self, project, branch, base):
134 self.project = project
135 self.branch = branch
136 self.base = base
137
138 @property
139 def name(self):
140 return self.branch.name
141
142 @property
143 def commits(self):
144 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400145 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
146 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
147 try:
148 self._commit_cache = self.project.bare_git.rev_list(*args)
149 except GitError:
150 # We weren't able to probe the commits for this branch. Was it tracking
151 # a branch that no longer exists? If so, return no commits. Otherwise,
152 # rethrow the error as we don't know what's going on.
153 if self.base_exists:
154 raise
155
156 self._commit_cache = []
157
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158 return self._commit_cache
159
160 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800161 def unabbrev_commits(self):
162 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700163 for commit in self.project.bare_git.rev_list(not_rev(self.base),
164 R_HEADS + self.name,
165 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800166 r[commit[0:8]] = commit
167 return r
168
169 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700171 return self.project.bare_git.log('--pretty=format:%cd',
172 '-n', '1',
173 R_HEADS + self.name,
174 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175
Mike Frysinger6da17752019-09-11 18:43:17 -0400176 @property
177 def base_exists(self):
178 """Whether the branch we're tracking exists.
179
180 Normally it should, but sometimes branches we track can get deleted.
181 """
182 if self._base_exists is None:
183 try:
184 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
185 # If we're still here, the base branch exists.
186 self._base_exists = True
187 except GitError:
188 # If we failed to verify, the base branch doesn't exist.
189 self._base_exists = False
190
191 return self._base_exists
192
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700193 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500194 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700195 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500196 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500197 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200198 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700199 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200200 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200201 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800202 validate_certs=True,
203 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500204 self.project.UploadForReview(branch=self.name,
205 people=people,
206 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700207 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500208 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500209 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200210 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700211 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200212 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200213 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800214 validate_certs=validate_certs,
215 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700217 def GetPublishedRefs(self):
218 refs = {}
219 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700220 self.branch.remote.SshReviewUrl(self.project.UserEmail),
221 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700222 for line in output.split('\n'):
223 try:
224 (sha, ref) = line.split()
225 refs[sha] = ref
226 except ValueError:
227 pass
228
229 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700231
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700233
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500235 super().__init__(config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100236 self.project = self.printer('header', attr='bold')
237 self.branch = self.printer('header', attr='bold')
238 self.nobranch = self.printer('nobranch', fg='red')
239 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240
Anthony King7bdac712014-07-16 12:56:40 +0100241 self.added = self.printer('added', fg='green')
242 self.changed = self.printer('changed', fg='red')
243 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
245
246class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700247
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500249 super().__init__(config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100250 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400251 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700253
Anthony King7bdac712014-07-16 12:56:40 +0100254class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700255
James W. Mills24c13082012-04-12 15:04:13 -0500256 def __init__(self, name, value, keep):
257 self.name = name
258 self.value = value
259 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700260
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700261
Mike Frysingere6a202f2019-08-02 15:57:57 -0400262def _SafeExpandPath(base, subpath, skipfinal=False):
263 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700264
Mike Frysingere6a202f2019-08-02 15:57:57 -0400265 We make sure no intermediate symlinks are traversed, and that the final path
266 is not a special file (e.g. not a socket or fifo).
267
268 NB: We rely on a number of paths already being filtered out while parsing the
269 manifest. See the validation logic in manifest_xml.py for more details.
270 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500271 # Split up the path by its components. We can't use os.path.sep exclusively
272 # as some platforms (like Windows) will convert / to \ and that bypasses all
273 # our constructed logic here. Especially since manifest authors only use
274 # / in their paths.
275 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
276 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400277 if skipfinal:
278 # Whether the caller handles the final component itself.
279 finalpart = components.pop()
280
281 path = base
282 for part in components:
283 if part in {'.', '..'}:
284 raise ManifestInvalidPathError(
285 '%s: "%s" not allowed in paths' % (subpath, part))
286
287 path = os.path.join(path, part)
288 if platform_utils.islink(path):
289 raise ManifestInvalidPathError(
290 '%s: traversing symlinks not allow' % (path,))
291
292 if os.path.exists(path):
293 if not os.path.isfile(path) and not platform_utils.isdir(path):
294 raise ManifestInvalidPathError(
295 '%s: only regular files & directories allowed' % (path,))
296
297 if skipfinal:
298 path = os.path.join(path, finalpart)
299
300 return path
301
302
303class _CopyFile(object):
304 """Container for <copyfile> manifest element."""
305
306 def __init__(self, git_worktree, src, topdir, dest):
307 """Register a <copyfile> request.
308
309 Args:
310 git_worktree: Absolute path to the git project checkout.
311 src: Relative path under |git_worktree| of file to read.
312 topdir: Absolute path to the top of the repo client checkout.
313 dest: Relative path under |topdir| of file to write.
314 """
315 self.git_worktree = git_worktree
316 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700317 self.src = src
318 self.dest = dest
319
320 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400321 src = _SafeExpandPath(self.git_worktree, self.src)
322 dest = _SafeExpandPath(self.topdir, self.dest)
323
324 if platform_utils.isdir(src):
325 raise ManifestInvalidPathError(
326 '%s: copying from directory not supported' % (self.src,))
327 if platform_utils.isdir(dest):
328 raise ManifestInvalidPathError(
329 '%s: copying to directory not allowed' % (self.dest,))
330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700331 # copy file if it does not exist or is out of date
332 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
333 try:
334 # remove existing file first, since it might be read-only
335 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800336 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400337 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200338 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700339 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200340 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700341 shutil.copy(src, dest)
342 # make the file read-only
343 mode = os.stat(dest)[stat.ST_MODE]
344 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
345 os.chmod(dest, mode)
346 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700347 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700349
Anthony King7bdac712014-07-16 12:56:40 +0100350class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400351 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700352
Mike Frysingere6a202f2019-08-02 15:57:57 -0400353 def __init__(self, git_worktree, src, topdir, dest):
354 """Register a <linkfile> request.
355
356 Args:
357 git_worktree: Absolute path to the git project checkout.
358 src: Target of symlink relative to path under |git_worktree|.
359 topdir: Absolute path to the top of the repo client checkout.
360 dest: Relative path under |topdir| of symlink to create.
361 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700362 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400363 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500364 self.src = src
365 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500366
Wink Saville4c426ef2015-06-03 08:05:17 -0700367 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500368 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700369 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500370 try:
371 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800372 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800373 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500374 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700375 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700376 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500377 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700378 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500379 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700380 _error('Cannot link file %s to %s', relSrc, absDest)
381
382 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400383 """Link the self.src & self.dest paths.
384
385 Handles wild cards on the src linking all of the files in the source in to
386 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700387 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500388 # Some people use src="." to create stable links to projects. Lets allow
389 # that but reject all other uses of "." to keep things simple.
390 if self.src == '.':
391 src = self.git_worktree
392 else:
393 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400394
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300395 if not glob.has_magic(src):
396 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400397 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
398 # dest & src are absolute paths at this point. Make sure the target of
399 # the symlink is relative in the context of the repo client checkout.
400 relpath = os.path.relpath(src, os.path.dirname(dest))
401 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700402 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400403 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300404 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400405 if os.path.exists(dest) and not platform_utils.isdir(dest):
406 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700407 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400408 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700409 # Create a releative path from source dir to destination dir
410 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400411 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700412
413 # Get the source file name
414 srcFile = os.path.basename(absSrcFile)
415
416 # Now form the final full paths to srcFile. They will be
417 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400418 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700419 relSrc = os.path.join(relSrcDir, srcFile)
420 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500421
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700422
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700423class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700424
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700425 def __init__(self,
426 name,
Anthony King7bdac712014-07-16 12:56:40 +0100427 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700428 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100429 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700430 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700431 orig_name=None,
432 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700433 self.name = name
434 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700435 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700436 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100437 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700438 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700439 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700440
Ian Kasprzak0286e312021-02-05 10:06:18 -0800441
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700442class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600443 # These objects can be shared between several working trees.
444 shareable_files = ['description', 'info']
445 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
446 # These objects can only be used by a single working tree.
447 working_tree_files = ['config', 'packed-refs', 'shallow']
448 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700449
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700450 def __init__(self,
451 manifest,
452 name,
453 remote,
454 gitdir,
David James8d201162013-10-11 17:03:19 -0700455 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700456 worktree,
457 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700458 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800459 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100460 rebase=True,
461 groups=None,
462 sync_c=False,
463 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900464 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100465 clone_depth=None,
466 upstream=None,
467 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500468 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100469 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900470 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700471 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600472 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700473 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800474 """Init a Project object.
475
476 Args:
477 manifest: The XmlManifest object.
478 name: The `name` attribute of manifest.xml's project element.
479 remote: RemoteSpec object specifying its remote's properties.
480 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700481 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800482 worktree: Absolute path of git working tree.
483 relpath: Relative path of git working tree to repo's top directory.
484 revisionExpr: The `revision` attribute of manifest.xml's project element.
485 revisionId: git commit id for checking out.
486 rebase: The `rebase` attribute of manifest.xml's project element.
487 groups: The `groups` attribute of manifest.xml's project element.
488 sync_c: The `sync-c` attribute of manifest.xml's project element.
489 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900490 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800491 upstream: The `upstream` attribute of manifest.xml's project element.
492 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500493 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800494 is_derived: False if the project was explicitly defined in the manifest;
495 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400496 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900497 optimized_fetch: If True, when a project is set to a sha1 revision, only
498 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600499 retry_fetches: Retry remote fetches n times upon receiving transient error
500 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700501 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800502 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400503 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700504 self.name = name
505 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800506 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700507 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800508 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700509 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800510 else:
511 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700513 self.revisionExpr = revisionExpr
514
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700515 if revisionId is None \
516 and revisionExpr \
517 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700518 self.revisionId = revisionExpr
519 else:
520 self.revisionId = revisionId
521
Mike Pontillod3153822012-02-28 11:53:24 -0800522 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700523 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700524 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800525 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900526 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900527 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700528 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800529 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500530 # NB: Do not use this setting in __init__ to change behavior so that the
531 # manifest.git checkout can inspect & change it after instantiating. See
532 # the XmlManifest init code for more info.
533 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800534 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900535 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600536 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800537 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800538
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700539 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500541 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500542 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700543 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400544 defaults=self.client.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700545
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800546 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700547 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800548 else:
549 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700550 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700551 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700552 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400553 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700554 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700555
Doug Anderson37282b42011-03-04 11:54:18 -0800556 # This will be filled in if a project is later identified to be the
557 # project containing repo hooks.
558 self.enabled_repo_hooks = []
559
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700560 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800561 def Derived(self):
562 return self.is_derived
563
564 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700565 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700566 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567
568 @property
569 def CurrentBranch(self):
570 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400571
572 The branch name omits the 'refs/heads/' prefix.
573 None is returned if the project is on a detached HEAD, or if the work_git is
574 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700575 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400576 try:
577 b = self.work_git.GetHead()
578 except NoManifestException:
579 # If the local checkout is in a bad state, don't barf. Let the callers
580 # process this like the head is unreadable.
581 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700582 if b.startswith(R_HEADS):
583 return b[len(R_HEADS):]
584 return None
585
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700586 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500587 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
588 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
589 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200590
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700591 def IsDirty(self, consider_untracked=True):
592 """Is the working directory modified in some way?
593 """
594 self.work_git.update_index('-q',
595 '--unmerged',
596 '--ignore-missing',
597 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900598 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700599 return True
600 if self.work_git.DiffZ('diff-files'):
601 return True
602 if consider_untracked and self.work_git.LsOthers():
603 return True
604 return False
605
606 _userident_name = None
607 _userident_email = None
608
609 @property
610 def UserName(self):
611 """Obtain the user's personal name.
612 """
613 if self._userident_name is None:
614 self._LoadUserIdentity()
615 return self._userident_name
616
617 @property
618 def UserEmail(self):
619 """Obtain the user's email address. This is very likely
620 to be their Gerrit login.
621 """
622 if self._userident_email is None:
623 self._LoadUserIdentity()
624 return self._userident_email
625
626 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900627 u = self.bare_git.var('GIT_COMMITTER_IDENT')
628 m = re.compile("^(.*) <([^>]*)> ").match(u)
629 if m:
630 self._userident_name = m.group(1)
631 self._userident_email = m.group(2)
632 else:
633 self._userident_name = ''
634 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635
636 def GetRemote(self, name):
637 """Get the configuration for a single remote.
638 """
639 return self.config.GetRemote(name)
640
641 def GetBranch(self, name):
642 """Get the configuration for a single branch.
643 """
644 return self.config.GetBranch(name)
645
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700646 def GetBranches(self):
647 """Get all existing local branches.
648 """
649 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900650 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700651 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700652
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530653 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700654 if name.startswith(R_HEADS):
655 name = name[len(R_HEADS):]
656 b = self.GetBranch(name)
657 b.current = name == current
658 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900659 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700660 heads[name] = b
661
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530662 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700663 if name.startswith(R_PUB):
664 name = name[len(R_PUB):]
665 b = heads.get(name)
666 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900667 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700668
669 return heads
670
Colin Cross5acde752012-03-28 20:15:45 -0700671 def MatchesGroups(self, manifest_groups):
672 """Returns true if the manifest groups specified at init should cause
673 this project to be synced.
674 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700675 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700676
677 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700678 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700679 manifest_groups: "-group1,group2"
680 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500681
682 The special manifest group "default" will match any project that
683 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700684 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500685 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700686 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700687 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500688 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700689
Conley Owens971de8e2012-04-16 10:36:08 -0700690 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700691 for group in expanded_manifest_groups:
692 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700693 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700694 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700695 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700696
Conley Owens971de8e2012-04-16 10:36:08 -0700697 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700698
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700699# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700700 def UncommitedFiles(self, get_all=True):
701 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700703 Args:
704 get_all: a boolean, if True - get information about all different
705 uncommitted files. If False - return as soon as any kind of
706 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500707 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700708 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500709 self.work_git.update_index('-q',
710 '--unmerged',
711 '--ignore-missing',
712 '--refresh')
713 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700714 details.append("rebase in progress")
715 if not get_all:
716 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500717
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700718 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
719 if changes:
720 details.extend(changes)
721 if not get_all:
722 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500723
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700724 changes = self.work_git.DiffZ('diff-files').keys()
725 if changes:
726 details.extend(changes)
727 if not get_all:
728 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500729
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700730 changes = self.work_git.LsOthers()
731 if changes:
732 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500733
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700734 return details
735
736 def HasChanges(self):
737 """Returns true if there are uncommitted changes.
738 """
739 if self.UncommitedFiles(get_all=False):
740 return True
741 else:
742 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500743
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600744 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700745 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200746
747 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200748 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600749 quiet: If True then only print the project name. Do not print
750 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700752 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700753 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200754 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700755 print(file=output_redir)
756 print('project %s/' % self.relpath, file=output_redir)
757 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700758 return
759
760 self.work_git.update_index('-q',
761 '--unmerged',
762 '--ignore-missing',
763 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700764 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700765 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
766 df = self.work_git.DiffZ('diff-files')
767 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100768 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700769 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770
771 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700772 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200773 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700774 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600776 if quiet:
777 out.nl()
778 return 'DIRTY'
779
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780 branch = self.CurrentBranch
781 if branch is None:
782 out.nobranch('(*** NO BRANCH ***)')
783 else:
784 out.branch('branch %s', branch)
785 out.nl()
786
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700787 if rb:
788 out.important('prior sync failed; rebase still in progress')
789 out.nl()
790
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791 paths = list()
792 paths.extend(di.keys())
793 paths.extend(df.keys())
794 paths.extend(do)
795
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530796 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900797 try:
798 i = di[p]
799 except KeyError:
800 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700801
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900802 try:
803 f = df[p]
804 except KeyError:
805 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200806
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900807 if i:
808 i_status = i.status.upper()
809 else:
810 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900812 if f:
813 f_status = f.status.lower()
814 else:
815 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816
817 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800818 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700819 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700820 else:
821 line = ' %s%s\t%s' % (i_status, f_status, p)
822
823 if i and not f:
824 out.added('%s', line)
825 elif (i and f) or (not i and f):
826 out.changed('%s', line)
827 elif not i and not f:
828 out.untracked('%s', line)
829 else:
830 out.write('%s', line)
831 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200832
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700833 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500835 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 """Prints the status of the repository to stdout.
837 """
838 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500839 if output_redir:
840 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841 cmd = ['diff']
842 if out.is_on:
843 cmd.append('--color')
844 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300845 if absolute_paths:
846 cmd.append('--src-prefix=a/%s/' % self.relpath)
847 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400849 try:
850 p = GitCommand(self,
851 cmd,
852 capture_stdout=True,
853 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500854 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400855 except GitError as e:
856 out.nl()
857 out.project('project %s/' % self.relpath)
858 out.nl()
859 out.fail('%s', str(e))
860 out.nl()
861 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500862 if p.stdout:
863 out.nl()
864 out.project('project %s/' % self.relpath)
865 out.nl()
Mike Frysinger9888acc2021-03-09 11:31:14 -0500866 out.write('%s', p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400867 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700869# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900870 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871 """Was the branch published (uploaded) for code review?
872 If so, returns the SHA-1 hash of the last published
873 state for the branch.
874 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700875 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900876 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700877 try:
878 return self.bare_git.rev_parse(key)
879 except GitError:
880 return None
881 else:
882 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900883 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700884 except KeyError:
885 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886
David Pursehouse8a68ff92012-09-24 12:15:13 +0900887 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888 """Prunes any stale published refs.
889 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900890 if all_refs is None:
891 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892 heads = set()
893 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530894 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700895 if name.startswith(R_HEADS):
896 heads.add(name)
897 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900898 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530900 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901 n = name[len(R_PUB):]
902 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900903 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700905 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906 """List any branches which can be uploaded for review.
907 """
908 heads = {}
909 pubed = {}
910
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530911 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700912 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900913 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900915 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916
917 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530918 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900919 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700921 if selected_branch and branch != selected_branch:
922 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800924 rb = self.GetUploadableBranch(branch)
925 if rb:
926 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 return ready
928
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800929 def GetUploadableBranch(self, branch_name):
930 """Get a single uploadable branch, or None.
931 """
932 branch = self.GetBranch(branch_name)
933 base = branch.LocalMerge
934 if branch.LocalMerge:
935 rb = ReviewableBranch(self, branch, base)
936 if rb.commits:
937 return rb
938 return None
939
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700940 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100941 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500942 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700943 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500944 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500945 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200946 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700947 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200948 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200949 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800950 validate_certs=True,
951 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 """Uploads the named branch for code review.
953 """
954 if branch is None:
955 branch = self.CurrentBranch
956 if branch is None:
957 raise GitError('not currently on a branch')
958
959 branch = self.GetBranch(branch)
960 if not branch.LocalMerge:
961 raise GitError('branch %s does not track a remote' % branch.name)
962 if not branch.remote.review:
963 raise GitError('remote %s has no review url' % branch.remote.name)
964
Bryan Jacobsf609f912013-05-06 13:36:24 -0400965 if dest_branch is None:
966 dest_branch = self.dest_branch
967 if dest_branch is None:
968 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 if not dest_branch.startswith(R_HEADS):
970 dest_branch = R_HEADS + dest_branch
971
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800972 if not branch.remote.projectname:
973 branch.remote.projectname = self.name
974 branch.remote.Save()
975
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200976 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800977 if url is None:
978 raise UploadError('review not configured')
979 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -0500980 if dryrun:
981 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800982
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800983 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -0800984 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700985
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800986 for push_option in (push_options or []):
987 cmd.append('-o')
988 cmd.append(push_option)
989
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800990 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800991
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800992 if dest_branch.startswith(R_HEADS):
993 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700994
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -0500995 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -0800996 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800997 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -0800998 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -0500999 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001000 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001001
David Pursehousef25a3702018-11-14 19:01:22 -08001002 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001003 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001004 if notify:
1005 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001006 if private:
1007 opts += ['private']
1008 if wip:
1009 opts += ['wip']
1010 if opts:
1011 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001012 cmd.append(ref_spec)
1013
Anthony King7bdac712014-07-16 12:56:40 +01001014 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001015 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016
Mike Frysingerd7f86832020-11-19 19:18:46 -05001017 if not dryrun:
1018 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1019 self.bare_git.UpdateRef(R_PUB + branch.name,
1020 R_HEADS + branch.name,
1021 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001023# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001024 def _ExtractArchive(self, tarpath, path=None):
1025 """Extract the given tar on its current location
1026
1027 Args:
1028 - tarpath: The path to the actual tar file
1029
1030 """
1031 try:
1032 with tarfile.open(tarpath, 'r') as tar:
1033 tar.extractall(path=path)
1034 return True
1035 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001036 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001037 return False
1038
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001039 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001040 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001041 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001042 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001043 is_new=None,
1044 current_branch_only=False,
1045 force_sync=False,
1046 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001047 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001048 archive=False,
1049 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001050 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001051 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001052 submodules=False,
1053 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054 """Perform only the network IO portion of the sync process.
1055 Local working directory/branch state is not affected.
1056 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001057 if archive and not isinstance(self, MetaProject):
1058 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001059 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001060 return False
1061
1062 name = self.relpath.replace('\\', '/')
1063 name = name.replace('/', '_')
1064 tarpath = '%s.tar' % name
1065 topdir = self.manifest.topdir
1066
1067 try:
1068 self._FetchArchive(tarpath, cwd=topdir)
1069 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001070 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001071 return False
1072
1073 # From now on, we only need absolute tarpath
1074 tarpath = os.path.join(topdir, tarpath)
1075
1076 if not self._ExtractArchive(tarpath, path=topdir):
1077 return False
1078 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001079 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001080 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001081 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001082 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001083 return True
Mike Frysinger76844ba2021-02-28 17:08:55 -05001084
1085 # If the shared object dir already exists, don't try to rebootstrap with a
1086 # clone bundle download. We should have the majority of objects already.
1087 if clone_bundle and os.path.exists(self.objdir):
1088 clone_bundle = False
1089
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001090 if is_new is None:
1091 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001092 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001093 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001094 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001095 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001096 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001097
1098 if is_new:
1099 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1100 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001101 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001102 # This works for both absolute and relative alternate directories.
1103 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001104 except IOError:
1105 alt_dir = None
1106 else:
1107 alt_dir = None
1108
Mike Frysingere50b6a72020-02-19 01:45:48 -05001109 if (clone_bundle
1110 and alt_dir is None
1111 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001112 is_new = False
1113
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001114 if not current_branch_only:
1115 if self.sync_c:
1116 current_branch_only = True
1117 elif not self.manifest._loaded:
1118 # Manifest cannot check defaults until it syncs.
1119 current_branch_only = False
1120 elif self.manifest.default.sync_c:
1121 current_branch_only = True
1122
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001123 if not self.sync_tags:
1124 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001125
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001126 if self.clone_depth:
1127 depth = self.clone_depth
1128 else:
1129 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1130
Mike Frysinger521d01b2020-02-17 01:51:49 -05001131 # See if we can skip the network fetch entirely.
1132 if not (optimized_fetch and
1133 (ID_RE.match(self.revisionExpr) and
1134 self._CheckForImmutableRevision())):
1135 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001136 initial=is_new,
1137 quiet=quiet, verbose=verbose, output_redir=output_redir,
1138 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001139 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001140 submodules=submodules, force_sync=force_sync,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001141 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001142 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001143
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001144 mp = self.manifest.manifestProject
1145 dissociate = mp.config.GetBoolean('repo.dissociate')
1146 if dissociate:
1147 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1148 if os.path.exists(alternates_file):
1149 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001150 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1151 merge_output=bool(output_redir))
1152 if p.stdout and output_redir:
Mike Frysinger3c093122021-03-03 11:17:27 -05001153 output_redir.write(p.stdout)
Mike Frysinger7b586f22021-02-23 18:38:39 -05001154 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001155 return False
1156 platform_utils.remove(alternates_file)
1157
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001158 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001159 self._InitMRef()
1160 else:
1161 self._InitMirrorHead()
1162 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001163 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001164 except OSError:
1165 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001166 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001167
1168 def PostRepoUpgrade(self):
1169 self._InitHooks()
1170
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001171 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001172 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001173 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001174 for copyfile in self.copyfiles:
1175 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001176 for linkfile in self.linkfiles:
1177 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001178
Julien Camperguedd654222014-01-09 16:21:37 +01001179 def GetCommitRevisionId(self):
1180 """Get revisionId of a commit.
1181
1182 Use this method instead of GetRevisionId to get the id of the commit rather
1183 than the id of the current git object (for example, a tag)
1184
1185 """
1186 if not self.revisionExpr.startswith(R_TAGS):
1187 return self.GetRevisionId(self._allrefs)
1188
1189 try:
1190 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1191 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001192 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1193 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001194
David Pursehouse8a68ff92012-09-24 12:15:13 +09001195 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001196 if self.revisionId:
1197 return self.revisionId
1198
1199 rem = self.GetRemote(self.remote.name)
1200 rev = rem.ToLocal(self.revisionExpr)
1201
David Pursehouse8a68ff92012-09-24 12:15:13 +09001202 if all_refs is not None and rev in all_refs:
1203 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001204
1205 try:
1206 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1207 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001208 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1209 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001210
Raman Tenneti6a872c92021-01-14 19:17:50 -08001211 def SetRevisionId(self, revisionId):
1212 self.revisionId = revisionId
1213
Martin Kellye4e94d22017-03-21 16:05:12 -07001214 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001215 """Perform only the local IO portion of the sync process.
1216 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001217 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001218 if not os.path.exists(self.gitdir):
1219 syncbuf.fail(self,
1220 'Cannot checkout %s due to missing network sync; Run '
1221 '`repo sync -n %s` first.' %
1222 (self.name, self.name))
1223 return
1224
Martin Kellye4e94d22017-03-21 16:05:12 -07001225 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001226 all_refs = self.bare_ref.all
1227 self.CleanPublishedCache(all_refs)
1228 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001229
Mike Frysinger0458faa2021-03-10 23:35:44 -05001230 # Special case the root of the repo client checkout. Make sure it doesn't
1231 # contain files being checked out to dirs we don't allow.
1232 if self.relpath == '.':
1233 PROTECTED_PATHS = {'.repo'}
1234 paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
1235 bad_paths = paths & PROTECTED_PATHS
1236 if bad_paths:
1237 syncbuf.fail(self,
1238 'Refusing to checkout project that writes to protected '
1239 'paths: %s' % (', '.join(bad_paths),))
1240 return
1241
David Pursehouse1d947b32012-10-25 12:23:11 +09001242 def _doff():
1243 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001244 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001245
Martin Kellye4e94d22017-03-21 16:05:12 -07001246 def _dosubmodules():
1247 self._SyncSubmodules(quiet=True)
1248
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001249 head = self.work_git.GetHead()
1250 if head.startswith(R_HEADS):
1251 branch = head[len(R_HEADS):]
1252 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001253 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001254 except KeyError:
1255 head = None
1256 else:
1257 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001259 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001260 # Currently on a detached HEAD. The user is assumed to
1261 # not have any local modifications worth worrying about.
1262 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001263 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001264 syncbuf.fail(self, _PriorSyncFailedError())
1265 return
1266
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001267 if head == revid:
1268 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001269 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001270 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001271 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001272 # The copy/linkfile config may have changed.
1273 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001274 return
1275 else:
1276 lost = self._revlist(not_rev(revid), HEAD)
1277 if lost:
1278 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001279
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001281 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001282 if submodules:
1283 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001284 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001285 syncbuf.fail(self, e)
1286 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001287 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001288 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001289
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001290 if head == revid:
1291 # No changes; don't do anything further.
1292 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001293 # The copy/linkfile config may have changed.
1294 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001295 return
1296
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001299 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001301 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001303 syncbuf.info(self,
1304 "leaving %s; does not track upstream",
1305 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001307 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001308 if submodules:
1309 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001310 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001311 syncbuf.fail(self, e)
1312 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001313 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001314 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001316 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001317
1318 # See if we can perform a fast forward merge. This can happen if our
1319 # branch isn't in the exact same state as we last published.
1320 try:
1321 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1322 # Skip the published logic.
1323 pub = False
1324 except GitError:
1325 pub = self.WasPublished(branch.name, all_refs)
1326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001328 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329 if not_merged:
1330 if upstream_gain:
1331 # The user has published this branch and some of those
1332 # commits are not yet merged upstream. We do not want
1333 # to rewrite the published commits so we punt.
1334 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001335 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001336 "branch %s is published (but not merged) and is now "
1337 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001338 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001339 elif pub == head:
1340 # All published commits are merged, and thus we are a
1341 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001342 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001343 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001344 if submodules:
1345 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001346 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001347
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001348 # Examine the local commits not in the remote. Find the
1349 # last one attributed to this user, if any.
1350 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001351 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001352 last_mine = None
1353 cnt_mine = 0
1354 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001355 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001356 if committer_email == self.UserEmail:
1357 last_mine = commit_id
1358 cnt_mine += 1
1359
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001360 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001361 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001362
1363 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001364 syncbuf.fail(self, _DirtyError())
1365 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001366
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001367 # If the upstream switched on us, warn the user.
1368 #
1369 if branch.merge != self.revisionExpr:
1370 if branch.merge and self.revisionExpr:
1371 syncbuf.info(self,
1372 'manifest switched %s...%s',
1373 branch.merge,
1374 self.revisionExpr)
1375 elif branch.merge:
1376 syncbuf.info(self,
1377 'manifest no longer tracks %s',
1378 branch.merge)
1379
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001380 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001381 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001382 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001383 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001384 syncbuf.info(self,
1385 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001386 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001387
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001388 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001389 if not ID_RE.match(self.revisionExpr):
1390 # in case of manifest sync the revisionExpr might be a SHA1
1391 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001392 if not branch.merge.startswith('refs/'):
1393 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001394 branch.Save()
1395
Mike Pontillod3153822012-02-28 11:53:24 -08001396 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001397 def _docopyandlink():
1398 self._CopyAndLinkFiles()
1399
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001400 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001401 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001402 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001403 if submodules:
1404 syncbuf.later2(self, _dosubmodules)
1405 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001406 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001408 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001409 if submodules:
1410 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001411 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001412 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001413 syncbuf.fail(self, e)
1414 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001415 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001416 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001417 if submodules:
1418 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001419
Mike Frysingere6a202f2019-08-02 15:57:57 -04001420 def AddCopyFile(self, src, dest, topdir):
1421 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001422
Mike Frysingere6a202f2019-08-02 15:57:57 -04001423 No filesystem changes occur here. Actual copying happens later on.
1424
1425 Paths should have basic validation run on them before being queued.
1426 Further checking will be handled when the actual copy happens.
1427 """
1428 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1429
1430 def AddLinkFile(self, src, dest, topdir):
1431 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1432
1433 No filesystem changes occur here. Actual linking happens later on.
1434
1435 Paths should have basic validation run on them before being queued.
1436 Further checking will be handled when the actual link happens.
1437 """
1438 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001439
James W. Mills24c13082012-04-12 15:04:13 -05001440 def AddAnnotation(self, name, value, keep):
1441 self.annotations.append(_Annotation(name, value, keep))
1442
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001443 def DownloadPatchSet(self, change_id, patch_id):
1444 """Download a single patch set of a single change to FETCH_HEAD.
1445 """
1446 remote = self.GetRemote(self.remote.name)
1447
1448 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001449 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001450 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001451 if GitCommand(self, cmd, bare=True).Wait() != 0:
1452 return None
1453 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001454 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001455 change_id,
1456 patch_id,
1457 self.bare_git.rev_parse('FETCH_HEAD'))
1458
Mike Frysingerc0d18662020-02-19 19:19:18 -05001459 def DeleteWorktree(self, quiet=False, force=False):
1460 """Delete the source checkout and any other housekeeping tasks.
1461
1462 This currently leaves behind the internal .repo/ cache state. This helps
1463 when switching branches or manifest changes get reverted as we don't have
1464 to redownload all the git objects. But we should do some GC at some point.
1465
1466 Args:
1467 quiet: Whether to hide normal messages.
1468 force: Always delete tree even if dirty.
1469
1470 Returns:
1471 True if the worktree was completely cleaned out.
1472 """
1473 if self.IsDirty():
1474 if force:
1475 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1476 (self.relpath,), file=sys.stderr)
1477 else:
1478 print('error: %s: Cannot remove project: uncommitted changes are '
1479 'present.\n' % (self.relpath,), file=sys.stderr)
1480 return False
1481
1482 if not quiet:
1483 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1484
1485 # Unlock and delink from the main worktree. We don't use git's worktree
1486 # remove because it will recursively delete projects -- we handle that
1487 # ourselves below. https://crbug.com/git/48
1488 if self.use_git_worktrees:
1489 needle = platform_utils.realpath(self.gitdir)
1490 # Find the git worktree commondir under .repo/worktrees/.
1491 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1492 assert output.startswith('worktree '), output
1493 commondir = output[9:]
1494 # Walk each of the git worktrees to see where they point.
1495 configs = os.path.join(commondir, 'worktrees')
1496 for name in os.listdir(configs):
1497 gitdir = os.path.join(configs, name, 'gitdir')
1498 with open(gitdir) as fp:
1499 relpath = fp.read().strip()
1500 # Resolve the checkout path and see if it matches this project.
1501 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1502 if fullpath == needle:
1503 platform_utils.rmtree(os.path.join(configs, name))
1504
1505 # Delete the .git directory first, so we're less likely to have a partially
1506 # working git repository around. There shouldn't be any git projects here,
1507 # so rmtree works.
1508
1509 # Try to remove plain files first in case of git worktrees. If this fails
1510 # for any reason, we'll fall back to rmtree, and that'll display errors if
1511 # it can't remove things either.
1512 try:
1513 platform_utils.remove(self.gitdir)
1514 except OSError:
1515 pass
1516 try:
1517 platform_utils.rmtree(self.gitdir)
1518 except OSError as e:
1519 if e.errno != errno.ENOENT:
1520 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1521 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1522 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1523 return False
1524
1525 # Delete everything under the worktree, except for directories that contain
1526 # another git project.
1527 dirs_to_remove = []
1528 failed = False
1529 for root, dirs, files in platform_utils.walk(self.worktree):
1530 for f in files:
1531 path = os.path.join(root, f)
1532 try:
1533 platform_utils.remove(path)
1534 except OSError as e:
1535 if e.errno != errno.ENOENT:
1536 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1537 failed = True
1538 dirs[:] = [d for d in dirs
1539 if not os.path.lexists(os.path.join(root, d, '.git'))]
1540 dirs_to_remove += [os.path.join(root, d) for d in dirs
1541 if os.path.join(root, d) not in dirs_to_remove]
1542 for d in reversed(dirs_to_remove):
1543 if platform_utils.islink(d):
1544 try:
1545 platform_utils.remove(d)
1546 except OSError as e:
1547 if e.errno != errno.ENOENT:
1548 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1549 failed = True
1550 elif not platform_utils.listdir(d):
1551 try:
1552 platform_utils.rmdir(d)
1553 except OSError as e:
1554 if e.errno != errno.ENOENT:
1555 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1556 failed = True
1557 if failed:
1558 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1559 file=sys.stderr)
1560 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1561 return False
1562
1563 # Try deleting parent dirs if they are empty.
1564 path = self.worktree
1565 while path != self.manifest.topdir:
1566 try:
1567 platform_utils.rmdir(path)
1568 except OSError as e:
1569 if e.errno != errno.ENOENT:
1570 break
1571 path = os.path.dirname(path)
1572
1573 return True
1574
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001575# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001576 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001577 """Create a new branch off the manifest's revision.
1578 """
Simran Basib9a1b732015-08-20 12:19:28 -07001579 if not branch_merge:
1580 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001581 head = self.work_git.GetHead()
1582 if head == (R_HEADS + name):
1583 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001584
David Pursehouse8a68ff92012-09-24 12:15:13 +09001585 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001586 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001587 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001588 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001589 capture_stdout=True,
1590 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001591
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001592 branch = self.GetBranch(name)
1593 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001594 branch.merge = branch_merge
1595 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1596 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001597
1598 if revision is None:
1599 revid = self.GetRevisionId(all_refs)
1600 else:
1601 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001602
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001603 if head.startswith(R_HEADS):
1604 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001605 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001606 except KeyError:
1607 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001608 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001609 ref = R_HEADS + name
1610 self.work_git.update_ref(ref, revid)
1611 self.work_git.symbolic_ref(HEAD, ref)
1612 branch.Save()
1613 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001614
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001615 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001616 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001617 capture_stdout=True,
1618 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001619 branch.Save()
1620 return True
1621 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001622
Wink Saville02d79452009-04-10 13:01:24 -07001623 def CheckoutBranch(self, name):
1624 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001625
1626 Args:
1627 name: The name of the branch to checkout.
1628
1629 Returns:
1630 True if the checkout succeeded; False if it didn't; None if the branch
1631 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001632 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001633 rev = R_HEADS + name
1634 head = self.work_git.GetHead()
1635 if head == rev:
1636 # Already on the branch
1637 #
1638 return True
Wink Saville02d79452009-04-10 13:01:24 -07001639
David Pursehouse8a68ff92012-09-24 12:15:13 +09001640 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001641 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001642 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001643 except KeyError:
1644 # Branch does not exist in this project
1645 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001646 return None
Wink Saville02d79452009-04-10 13:01:24 -07001647
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001648 if head.startswith(R_HEADS):
1649 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001650 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001651 except KeyError:
1652 head = None
1653
1654 if head == revid:
1655 # Same revision; just update HEAD to point to the new
1656 # target branch, but otherwise take no other action.
1657 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001658 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1659 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001660 return True
1661
1662 return GitCommand(self,
1663 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001664 capture_stdout=True,
1665 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001666
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001667 def AbandonBranch(self, name):
1668 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001669
1670 Args:
1671 name: The name of the branch to abandon.
1672
1673 Returns:
1674 True if the abandon succeeded; False if it didn't; None if the branch
1675 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001676 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001677 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001678 all_refs = self.bare_ref.all
1679 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001680 # Doesn't exist
1681 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001682
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001683 head = self.work_git.GetHead()
1684 if head == rev:
1685 # We can't destroy the branch while we are sitting
1686 # on it. Switch to a detached HEAD.
1687 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001688 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001689
David Pursehouse8a68ff92012-09-24 12:15:13 +09001690 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001691 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001692 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001693 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001694 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001695
1696 return GitCommand(self,
1697 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001698 capture_stdout=True,
1699 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001700
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001701 def PruneHeads(self):
1702 """Prune any topic branches already merged into upstream.
1703 """
1704 cb = self.CurrentBranch
1705 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001706 left = self._allrefs
1707 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001708 if name.startswith(R_HEADS):
1709 name = name[len(R_HEADS):]
1710 if cb is None or name != cb:
1711 kill.append(name)
1712
Mike Frysingera3794e92021-03-11 23:24:01 -05001713 # Minor optimization: If there's nothing to prune, then don't try to read
1714 # any project state.
1715 if not kill and not cb:
1716 return []
1717
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001718 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001719 if cb is not None \
1720 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001721 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001722 self.work_git.DetachHead(HEAD)
1723 kill.append(cb)
1724
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001725 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001726 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001727
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001728 try:
1729 self.bare_git.DetachHead(rev)
1730
1731 b = ['branch', '-d']
1732 b.extend(kill)
1733 b = GitCommand(self, b, bare=True,
1734 capture_stdout=True,
1735 capture_stderr=True)
1736 b.Wait()
1737 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001738 if ID_RE.match(old):
1739 self.bare_git.DetachHead(old)
1740 else:
1741 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001742 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001743
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001744 for branch in kill:
1745 if (R_HEADS + branch) not in left:
1746 self.CleanPublishedCache()
1747 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001748
1749 if cb and cb not in kill:
1750 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001751 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001752
1753 kept = []
1754 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001755 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001756 branch = self.GetBranch(branch)
1757 base = branch.LocalMerge
1758 if not base:
1759 base = rev
1760 kept.append(ReviewableBranch(self, branch, base))
1761 return kept
1762
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001763# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001764 def GetRegisteredSubprojects(self):
1765 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001766
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001767 def rec(subprojects):
1768 if not subprojects:
1769 return
1770 result.extend(subprojects)
1771 for p in subprojects:
1772 rec(p.subprojects)
1773 rec(self.subprojects)
1774 return result
1775
1776 def _GetSubmodules(self):
1777 # Unfortunately we cannot call `git submodule status --recursive` here
1778 # because the working tree might not exist yet, and it cannot be used
1779 # without a working tree in its current implementation.
1780
1781 def get_submodules(gitdir, rev):
1782 # Parse .gitmodules for submodule sub_paths and sub_urls
1783 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1784 if not sub_paths:
1785 return []
1786 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1787 # revision of submodule repository
1788 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1789 submodules = []
1790 for sub_path, sub_url in zip(sub_paths, sub_urls):
1791 try:
1792 sub_rev = sub_revs[sub_path]
1793 except KeyError:
1794 # Ignore non-exist submodules
1795 continue
1796 submodules.append((sub_rev, sub_path, sub_url))
1797 return submodules
1798
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001799 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1800 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001801
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001802 def parse_gitmodules(gitdir, rev):
1803 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1804 try:
Anthony King7bdac712014-07-16 12:56:40 +01001805 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1806 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001807 except GitError:
1808 return [], []
1809 if p.Wait() != 0:
1810 return [], []
1811
1812 gitmodules_lines = []
1813 fd, temp_gitmodules_path = tempfile.mkstemp()
1814 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001815 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001816 os.close(fd)
1817 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001818 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1819 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001820 if p.Wait() != 0:
1821 return [], []
1822 gitmodules_lines = p.stdout.split('\n')
1823 except GitError:
1824 return [], []
1825 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001826 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001827
1828 names = set()
1829 paths = {}
1830 urls = {}
1831 for line in gitmodules_lines:
1832 if not line:
1833 continue
1834 m = re_path.match(line)
1835 if m:
1836 names.add(m.group(1))
1837 paths[m.group(1)] = m.group(2)
1838 continue
1839 m = re_url.match(line)
1840 if m:
1841 names.add(m.group(1))
1842 urls[m.group(1)] = m.group(2)
1843 continue
1844 names = sorted(names)
1845 return ([paths.get(name, '') for name in names],
1846 [urls.get(name, '') for name in names])
1847
1848 def git_ls_tree(gitdir, rev, paths):
1849 cmd = ['ls-tree', rev, '--']
1850 cmd.extend(paths)
1851 try:
Anthony King7bdac712014-07-16 12:56:40 +01001852 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1853 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001854 except GitError:
1855 return []
1856 if p.Wait() != 0:
1857 return []
1858 objects = {}
1859 for line in p.stdout.split('\n'):
1860 if not line.strip():
1861 continue
1862 object_rev, object_path = line.split()[2:4]
1863 objects[object_path] = object_rev
1864 return objects
1865
1866 try:
1867 rev = self.GetRevisionId()
1868 except GitError:
1869 return []
1870 return get_submodules(self.gitdir, rev)
1871
1872 def GetDerivedSubprojects(self):
1873 result = []
1874 if not self.Exists:
1875 # If git repo does not exist yet, querying its submodules will
1876 # mess up its states; so return here.
1877 return result
1878 for rev, path, url in self._GetSubmodules():
1879 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001880 relpath, worktree, gitdir, objdir = \
1881 self.manifest.GetSubprojectPaths(self, name, path)
1882 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001883 if project:
1884 result.extend(project.GetDerivedSubprojects())
1885 continue
David James8d201162013-10-11 17:03:19 -07001886
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001887 if url.startswith('..'):
1888 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001889 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001890 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001891 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001892 review=self.remote.review,
1893 revision=self.remote.revision)
1894 subproject = Project(manifest=self.manifest,
1895 name=name,
1896 remote=remote,
1897 gitdir=gitdir,
1898 objdir=objdir,
1899 worktree=worktree,
1900 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001901 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001902 revisionId=rev,
1903 rebase=self.rebase,
1904 groups=self.groups,
1905 sync_c=self.sync_c,
1906 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001907 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001908 parent=self,
1909 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001910 result.append(subproject)
1911 result.extend(subproject.GetDerivedSubprojects())
1912 return result
1913
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001914# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001915 def EnableRepositoryExtension(self, key, value='true', version=1):
1916 """Enable git repository extension |key| with |value|.
1917
1918 Args:
1919 key: The extension to enabled. Omit the "extensions." prefix.
1920 value: The value to use for the extension.
1921 version: The minimum git repository version needed.
1922 """
1923 # Make sure the git repo version is new enough already.
1924 found_version = self.config.GetInt('core.repositoryFormatVersion')
1925 if found_version is None:
1926 found_version = 0
1927 if found_version < version:
1928 self.config.SetString('core.repositoryFormatVersion', str(version))
1929
1930 # Enable the extension!
1931 self.config.SetString('extensions.%s' % (key,), value)
1932
Mike Frysinger50a81de2020-09-06 15:51:21 -04001933 def ResolveRemoteHead(self, name=None):
1934 """Find out what the default branch (HEAD) points to.
1935
1936 Normally this points to refs/heads/master, but projects are moving to main.
1937 Support whatever the server uses rather than hardcoding "master" ourselves.
1938 """
1939 if name is None:
1940 name = self.remote.name
1941
1942 # The output will look like (NB: tabs are separators):
1943 # ref: refs/heads/master HEAD
1944 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1945 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1946
1947 for line in output.splitlines():
1948 lhs, rhs = line.split('\t', 1)
1949 if rhs == 'HEAD' and lhs.startswith('ref:'):
1950 return lhs[4:].strip()
1951
1952 return None
1953
Zac Livingstone4332262017-06-16 08:56:09 -06001954 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001955 try:
1956 # if revision (sha or tag) is not present then following function
1957 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08001958 self.bare_git.rev_list('-1', '--missing=allow-any',
1959 '%s^0' % self.revisionExpr, '--')
Chris AtLee2fb64662014-01-16 21:32:33 -05001960 return True
1961 except GitError:
1962 # There is no such persistent revision. We have to fetch it.
1963 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001964
Julien Campergue335f5ef2013-10-16 11:02:35 +02001965 def _FetchArchive(self, tarpath, cwd=None):
1966 cmd = ['archive', '-v', '-o', tarpath]
1967 cmd.append('--remote=%s' % self.remote.url)
1968 cmd.append('--prefix=%s/' % self.relpath)
1969 cmd.append(self.revisionExpr)
1970
1971 command = GitCommand(self, cmd, cwd=cwd,
1972 capture_stdout=True,
1973 capture_stderr=True)
1974
1975 if command.Wait() != 0:
1976 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1977
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001978 def _RemoteFetch(self, name=None,
1979 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001980 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001981 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001982 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001983 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001984 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001985 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001986 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001987 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04001988 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07001989 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001990 clone_filter=None,
1991 retry_fetches=2,
1992 retry_sleep_initial_sec=4.0,
1993 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001994 is_sha1 = False
1995 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001996 # The depth should not be used when fetching to a mirror because
1997 # it will result in a shallow repository that cannot be cloned or
1998 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001999 # The repo project should also never be synced with partial depth.
2000 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2001 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002002
Shawn Pearce69e04d82014-01-29 12:48:54 -08002003 if depth:
2004 current_branch_only = True
2005
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002006 if ID_RE.match(self.revisionExpr) is not None:
2007 is_sha1 = True
2008
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002009 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002010 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002011 # this is a tag and its sha1 value should never change
2012 tag_name = self.revisionExpr[len(R_TAGS):]
2013
2014 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002015 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002016 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002017 print('Skipped fetching project %s (already have persistent ref)'
2018 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002019 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002020 if is_sha1 and not depth:
2021 # When syncing a specific commit and --depth is not set:
2022 # * if upstream is explicitly specified and is not a sha1, fetch only
2023 # upstream as users expect only upstream to be fetch.
2024 # Note: The commit might not be in upstream in which case the sync
2025 # will fail.
2026 # * otherwise, fetch all branches to make sure we end up with the
2027 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002028 if self.upstream:
2029 current_branch_only = not ID_RE.match(self.upstream)
2030 else:
2031 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002032
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002033 if not name:
2034 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002035
2036 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002037 remote = self.GetRemote(name)
2038 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002039 ssh_proxy = True
2040
Shawn O. Pearce88443382010-10-08 10:02:09 +02002041 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002042 if alt_dir and 'objects' == os.path.basename(alt_dir):
2043 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002044 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2045 remote = self.GetRemote(name)
2046
David Pursehouse8a68ff92012-09-24 12:15:13 +09002047 all_refs = self.bare_ref.all
2048 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002049 tmp = set()
2050
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302051 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002052 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002053 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002054 all_refs[r] = ref_id
2055 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002056 continue
2057
David Pursehouse8a68ff92012-09-24 12:15:13 +09002058 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002059 continue
2060
David Pursehouse8a68ff92012-09-24 12:15:13 +09002061 r = 'refs/_alt/%s' % ref_id
2062 all_refs[r] = ref_id
2063 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002064 tmp.add(r)
2065
heping3d7bbc92017-04-12 19:51:47 +08002066 tmp_packed_lines = []
2067 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002068
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302069 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002070 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002071 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002072 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002073 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002074
heping3d7bbc92017-04-12 19:51:47 +08002075 tmp_packed = ''.join(tmp_packed_lines)
2076 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002077 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002078 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002079 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002080
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002081 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002082
Xin Li745be2e2019-06-03 11:24:30 -07002083 if clone_filter:
2084 git_require((2, 19, 0), fail=True, msg='partial clones')
2085 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002086 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002087
Conley Owensf97e8382015-01-21 11:12:46 -08002088 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002089 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002090 else:
2091 # If this repo has shallow objects, then we don't know which refs have
2092 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2093 # do this with projects that don't have shallow objects, since it is less
2094 # efficient.
2095 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2096 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002097
Mike Frysinger4847e052020-02-22 00:07:35 -05002098 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002099 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002100 if not quiet and sys.stdout.isatty():
2101 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002102 if not self.worktree:
2103 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002104 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002105
Mike Frysingere57f1142019-03-18 21:27:54 -04002106 if force_sync:
2107 cmd.append('--force')
2108
David Pursehouse74cfd272015-10-14 10:50:15 +09002109 if prune:
2110 cmd.append('--prune')
2111
Martin Kellye4e94d22017-03-21 16:05:12 -07002112 if submodules:
2113 cmd.append('--recurse-submodules=on-demand')
2114
Kuang-che Wu6856f982019-11-25 12:37:55 +08002115 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002116 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002117 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002118 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002119 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002120 spec.append('tag')
2121 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002122
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302123 if self.manifest.IsMirror and not current_branch_only:
2124 branch = None
2125 else:
2126 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002127 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002128 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002129 # Shallow checkout of a specific commit, fetch from that commit and not
2130 # the heads only as the commit might be deeper in the history.
2131 spec.append(branch)
2132 else:
2133 if is_sha1:
2134 branch = self.upstream
2135 if branch is not None and branch.strip():
2136 if not branch.startswith('refs/'):
2137 branch = R_HEADS + branch
2138 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2139
2140 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2141 # whole repo.
2142 if self.manifest.IsMirror and not spec:
2143 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2144
2145 # If using depth then we should not get all the tags since they may
2146 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002147 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002148 cmd.append('--no-tags')
2149 else:
2150 cmd.append('--tags')
2151 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2152
Conley Owens80b87fe2014-05-09 17:13:44 -07002153 cmd.extend(spec)
2154
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002155 # At least one retry minimum due to git remote prune.
2156 retry_fetches = max(retry_fetches, 2)
2157 retry_cur_sleep = retry_sleep_initial_sec
2158 ok = prune_tried = False
2159 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002160 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002161 merge_output=True, capture_stdout=quiet or bool(output_redir))
2162 if gitcmd.stdout and not quiet and output_redir:
2163 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002164 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002165 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002166 ok = True
2167 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002168
2169 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002170 elif (gitcmd.stdout and
2171 'error:' in gitcmd.stdout and
2172 'HTTP 429' in gitcmd.stdout):
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002173 if not quiet:
2174 print('429 received, sleeping: %s sec' % retry_cur_sleep,
2175 file=sys.stderr)
2176 time.sleep(retry_cur_sleep)
2177 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2178 MAXIMUM_RETRY_SLEEP_SEC)
2179 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2180 RETRY_JITTER_PERCENT))
2181 continue
2182
2183 # If this is not last attempt, try 'git remote prune'.
2184 elif (try_n < retry_fetches - 1 and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002185 gitcmd.stdout and
2186 'error:' in gitcmd.stdout and
2187 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002188 not prune_tried):
2189 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002190 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002191 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002192 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002193 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002194 break
2195 continue
Brian Harring14a66742012-09-28 20:21:57 -07002196 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002197 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2198 # in sha1 mode, we just tried sync'ing from the upstream field; it
2199 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002200 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002201 elif ret < 0:
2202 # Git died with a signal, exit immediately
2203 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002204 if not verbose:
Mike Frysinger7b586f22021-02-23 18:38:39 -05002205 print('\n%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002206 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002207
2208 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002209 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002210 if old_packed != '':
2211 _lwrite(packed_refs, old_packed)
2212 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002213 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002214 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002215
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002216 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002217 # We just synced the upstream given branch; verify we
2218 # got what we wanted, else trigger a second run of all
2219 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002220 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002221 # Sync the current branch only with depth set to None.
2222 # We always pass depth=None down to avoid infinite recursion.
2223 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002224 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002225 current_branch_only=current_branch_only and depth,
2226 initial=False, alt_dir=alt_dir,
2227 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002228
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002229 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002230
Mike Frysingere50b6a72020-02-19 01:45:48 -05002231 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002232 if initial and \
2233 (self.manifest.manifestProject.config.GetString('repo.depth') or
2234 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002235 return False
2236
2237 remote = self.GetRemote(self.remote.name)
2238 bundle_url = remote.url + '/clone.bundle'
2239 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002240 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2241 'persistent-http',
2242 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002243 return False
2244
2245 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2246 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2247
2248 exist_dst = os.path.exists(bundle_dst)
2249 exist_tmp = os.path.exists(bundle_tmp)
2250
2251 if not initial and not exist_dst and not exist_tmp:
2252 return False
2253
2254 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002255 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2256 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002257 if not exist_dst:
2258 return False
2259
2260 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002261 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002262 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002263 if not quiet and sys.stdout.isatty():
2264 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002265 if not self.worktree:
2266 cmd.append('--update-head-ok')
2267 cmd.append(bundle_dst)
2268 for f in remote.fetch:
2269 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002270 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002271
2272 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002273 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002274 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002275 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002276 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002277 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002278
Mike Frysingere50b6a72020-02-19 01:45:48 -05002279 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002280 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002281 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002282
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002283 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002284 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002285 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002286 if os.path.exists(tmpPath):
2287 size = os.stat(tmpPath).st_size
2288 if size >= 1024:
2289 cmd += ['--continue-at', '%d' % (size,)]
2290 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002291 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002292 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002293 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002294 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002295 if proxy:
2296 cmd += ['--proxy', proxy]
2297 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2298 cmd += ['--proxy', os.environ['http_proxy']]
2299 if srcUrl.startswith('persistent-https'):
2300 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2301 elif srcUrl.startswith('persistent-http'):
2302 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002303 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002304
Dave Borowitz137d0132015-01-02 11:12:54 -08002305 if IsTrace():
2306 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002307 if verbose:
2308 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2309 stdout = None if verbose else subprocess.PIPE
2310 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002311 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002312 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002313 except OSError:
2314 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002315
Mike Frysingere50b6a72020-02-19 01:45:48 -05002316 (output, _) = proc.communicate()
2317 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002318
Dave Borowitz137d0132015-01-02 11:12:54 -08002319 if curlret == 22:
2320 # From curl man page:
2321 # 22: HTTP page not retrieved. The requested url was not found or
2322 # returned another error with the HTTP error code being 400 or above.
2323 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002324 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002325 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2326 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002327 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002328 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002329 elif curlret and not verbose and output:
2330 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002331
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002332 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002333 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002334 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002335 return True
2336 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002337 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002338 return False
2339 else:
2340 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002341
Kris Giesingc8d882a2014-12-23 13:02:32 -08002342 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002343 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002344 with open(path, 'rb') as f:
2345 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002346 return True
2347 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002348 if not quiet:
2349 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002350 return False
2351 except OSError:
2352 return False
2353
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002354 def _Checkout(self, rev, quiet=False):
2355 cmd = ['checkout']
2356 if quiet:
2357 cmd.append('-q')
2358 cmd.append(rev)
2359 cmd.append('--')
2360 if GitCommand(self, cmd).Wait() != 0:
2361 if self._allrefs:
2362 raise GitError('%s checkout %s ' % (self.name, rev))
2363
Mike Frysinger915fda12020-03-22 12:15:20 -04002364 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002365 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002366 if ffonly:
2367 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002368 if record_origin:
2369 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002370 cmd.append(rev)
2371 cmd.append('--')
2372 if GitCommand(self, cmd).Wait() != 0:
2373 if self._allrefs:
2374 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2375
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302376 def _LsRemote(self, refs):
2377 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302378 p = GitCommand(self, cmd, capture_stdout=True)
2379 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002380 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302381 return None
2382
Anthony King7bdac712014-07-16 12:56:40 +01002383 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002384 cmd = ['revert']
2385 cmd.append('--no-edit')
2386 cmd.append(rev)
2387 cmd.append('--')
2388 if GitCommand(self, cmd).Wait() != 0:
2389 if self._allrefs:
2390 raise GitError('%s revert %s ' % (self.name, rev))
2391
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002392 def _ResetHard(self, rev, quiet=True):
2393 cmd = ['reset', '--hard']
2394 if quiet:
2395 cmd.append('-q')
2396 cmd.append(rev)
2397 if GitCommand(self, cmd).Wait() != 0:
2398 raise GitError('%s reset --hard %s ' % (self.name, rev))
2399
Martin Kellye4e94d22017-03-21 16:05:12 -07002400 def _SyncSubmodules(self, quiet=True):
2401 cmd = ['submodule', 'update', '--init', '--recursive']
2402 if quiet:
2403 cmd.append('-q')
2404 if GitCommand(self, cmd).Wait() != 0:
2405 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2406
Anthony King7bdac712014-07-16 12:56:40 +01002407 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002408 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002409 if onto is not None:
2410 cmd.extend(['--onto', onto])
2411 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002412 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002413 raise GitError('%s rebase %s ' % (self.name, upstream))
2414
Pierre Tardy3d125942012-05-04 12:18:12 +02002415 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002416 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002417 if ffonly:
2418 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002419 if GitCommand(self, cmd).Wait() != 0:
2420 raise GitError('%s merge %s ' % (self.name, head))
2421
David Pursehousee8ace262020-02-13 12:41:15 +09002422 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002423 init_git_dir = not os.path.exists(self.gitdir)
2424 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002425 try:
2426 # Initialize the bare repository, which contains all of the objects.
2427 if init_obj_dir:
2428 os.makedirs(self.objdir)
2429 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002430
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002431 if self.use_git_worktrees:
2432 # Set up the m/ space to point to the worktree-specific ref space.
2433 # We'll update the worktree-specific ref space on each checkout.
2434 if self.manifest.branch:
2435 self.bare_git.symbolic_ref(
2436 '-m', 'redirecting to worktree scope',
2437 R_M + self.manifest.branch,
2438 R_WORKTREE_M + self.manifest.branch)
2439
2440 # Enable per-worktree config file support if possible. This is more a
2441 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002442 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002443 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002444
Kevin Degib1a07b82015-07-27 13:33:43 -06002445 # If we have a separate directory to hold refs, initialize it as well.
2446 if self.objdir != self.gitdir:
2447 if init_git_dir:
2448 os.makedirs(self.gitdir)
2449
2450 if init_obj_dir or init_git_dir:
2451 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2452 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002453 try:
2454 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2455 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002456 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002457 print("Retrying clone after deleting %s" %
2458 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002459 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002460 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2461 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002462 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002463 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002464 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2465 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002466 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002467 raise e
2468 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002469
Kevin Degi384b3c52014-10-16 16:02:58 -06002470 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002471 mp = self.manifest.manifestProject
2472 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002473
Kevin Degib1a07b82015-07-27 13:33:43 -06002474 if ref_dir or mirror_git:
2475 if not mirror_git:
2476 mirror_git = os.path.join(ref_dir, self.name + '.git')
2477 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2478 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002479 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2480 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002481
Kevin Degib1a07b82015-07-27 13:33:43 -06002482 if os.path.exists(mirror_git):
2483 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002484 elif os.path.exists(repo_git):
2485 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002486 elif os.path.exists(worktrees_git):
2487 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002488 else:
2489 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002490
Kevin Degib1a07b82015-07-27 13:33:43 -06002491 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002492 if not os.path.isabs(ref_dir):
2493 # The alternate directory is relative to the object database.
2494 ref_dir = os.path.relpath(ref_dir,
2495 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002496 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2497 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002498
David Pursehousee8ace262020-02-13 12:41:15 +09002499 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002500
2501 m = self.manifest.manifestProject.config
2502 for key in ['user.name', 'user.email']:
2503 if m.Has(key, include_defaults=False):
2504 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002505 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002506 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002507 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002508 except Exception:
2509 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002510 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002511 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002512 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002513 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002514
David Pursehousee8ace262020-02-13 12:41:15 +09002515 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002516 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002517 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002518
David Pursehousee8ace262020-02-13 12:41:15 +09002519 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002520 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002521 if not os.path.exists(hooks):
2522 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002523 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002524 name = os.path.basename(stock_hook)
2525
Victor Boivie65e0f352011-04-18 11:23:29 +02002526 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002527 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002528 # Don't install a Gerrit Code Review hook if this
2529 # project does not appear to use it for reviews.
2530 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002531 # Since the manifest project is one of those, but also
2532 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002533 continue
2534
2535 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002536 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002537 continue
2538 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002539 # If the files are the same, we'll leave it alone. We create symlinks
2540 # below by default but fallback to hardlinks if the OS blocks them.
2541 # So if we're here, it's probably because we made a hardlink below.
2542 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002543 if not quiet:
2544 _warn("%s: Not replacing locally modified %s hook",
2545 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002546 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002547 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002548 platform_utils.symlink(
2549 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002550 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002551 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002552 try:
2553 os.link(stock_hook, dst)
2554 except OSError:
2555 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002556 else:
2557 raise
2558
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002559 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002560 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002561 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002562 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002563 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002564 remote.review = self.remote.review
2565 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002566
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002567 if self.worktree:
2568 remote.ResetFetch(mirror=False)
2569 else:
2570 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002571 remote.Save()
2572
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002573 def _InitMRef(self):
2574 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002575 if self.use_git_worktrees:
2576 # We can't update this ref with git worktrees until it exists.
2577 # We'll wait until the initial checkout to set it.
2578 if not os.path.exists(self.worktree):
2579 return
2580
2581 base = R_WORKTREE_M
2582 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002583
2584 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002585 else:
2586 base = R_M
2587 active_git = self.bare_git
2588
2589 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002590
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002591 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002592 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002593
Remy Böhmer1469c282020-12-15 18:49:02 +01002594 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002595 cur = self.bare_ref.symref(ref)
2596
2597 if self.revisionId:
2598 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2599 msg = 'manifest set to %s' % self.revisionId
2600 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002601 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002602 else:
2603 remote = self.GetRemote(self.remote.name)
2604 dst = remote.ToLocal(self.revisionExpr)
2605 if cur != dst:
2606 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002607 if detach:
2608 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2609 else:
2610 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002611
Kevin Degi384b3c52014-10-16 16:02:58 -06002612 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002613 # Git worktrees don't use symlinks to share at all.
2614 if self.use_git_worktrees:
2615 return
2616
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002617 symlink_files = self.shareable_files[:]
2618 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002619 if share_refs:
2620 symlink_files += self.working_tree_files
2621 symlink_dirs += self.working_tree_dirs
2622 to_symlink = symlink_files + symlink_dirs
2623 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002624 # Try to self-heal a bit in simple cases.
2625 dst_path = os.path.join(destdir, name)
2626 src_path = os.path.join(srcdir, name)
2627
2628 if name in self.working_tree_dirs:
2629 # If the dir is missing under .repo/projects/, create it.
2630 if not os.path.exists(src_path):
2631 os.makedirs(src_path)
2632
2633 elif name in self.working_tree_files:
2634 # If it's a file under the checkout .git/ and the .repo/projects/ has
2635 # nothing, move the file under the .repo/projects/ tree.
2636 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2637 platform_utils.rename(dst_path, src_path)
2638
2639 # If the path exists under the .repo/projects/ and there's no symlink
2640 # under the checkout .git/, recreate the symlink.
2641 if name in self.working_tree_dirs or name in self.working_tree_files:
2642 if os.path.exists(src_path) and not os.path.exists(dst_path):
2643 platform_utils.symlink(
2644 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2645
2646 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002647 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002648 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002649 # Fail if the links are pointing to the wrong place
2650 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002651 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002652 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002653 'work tree. If you\'re comfortable with the '
2654 'possibility of losing the work tree\'s git metadata,'
2655 ' use `repo sync --force-sync {0}` to '
2656 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002657
David James8d201162013-10-11 17:03:19 -07002658 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2659 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2660
2661 Args:
2662 gitdir: The bare git repository. Must already be initialized.
2663 dotgit: The repository you would like to initialize.
2664 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2665 Only one work tree can store refs under a given |gitdir|.
2666 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2667 This saves you the effort of initializing |dotgit| yourself.
2668 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002669 symlink_files = self.shareable_files[:]
2670 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002671 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002672 symlink_files += self.working_tree_files
2673 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002674 to_symlink = symlink_files + symlink_dirs
2675
2676 to_copy = []
2677 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002678 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002679
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002680 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002681 for name in set(to_copy).union(to_symlink):
2682 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002683 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002684 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002685
Kevin Degi384b3c52014-10-16 16:02:58 -06002686 if os.path.lexists(dst):
2687 continue
David James8d201162013-10-11 17:03:19 -07002688
2689 # If the source dir doesn't exist, create an empty dir.
2690 if name in symlink_dirs and not os.path.lexists(src):
2691 os.makedirs(src)
2692
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002693 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002694 platform_utils.symlink(
2695 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002696 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002697 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002698 shutil.copytree(src, dst)
2699 elif os.path.isfile(src):
2700 shutil.copy(src, dst)
2701
Conley Owens80b87fe2014-05-09 17:13:44 -07002702 # If the source file doesn't exist, ensure the destination
2703 # file doesn't either.
2704 if name in symlink_files and not os.path.lexists(src):
2705 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002706 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002707 except OSError:
2708 pass
2709
David James8d201162013-10-11 17:03:19 -07002710 except OSError as e:
2711 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002712 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002713 else:
2714 raise
2715
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002716 def _InitGitWorktree(self):
2717 """Init the project using git worktrees."""
2718 self.bare_git.worktree('prune')
2719 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2720 self.worktree, self.GetRevisionId())
2721
2722 # Rewrite the internal state files to use relative paths between the
2723 # checkouts & worktrees.
2724 dotgit = os.path.join(self.worktree, '.git')
2725 with open(dotgit, 'r') as fp:
2726 # Figure out the checkout->worktree path.
2727 setting = fp.read()
2728 assert setting.startswith('gitdir:')
2729 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002730 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2731 # of file permissions. Delete it and recreate it from scratch to avoid.
2732 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002733 # Use relative path from checkout->worktree & maintain Unix line endings
2734 # on all OS's to match git behavior.
2735 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002736 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2737 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002738 # Use relative path from worktree->checkout & maintain Unix line endings
2739 # on all OS's to match git behavior.
2740 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002741 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2742
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002743 self._InitMRef()
2744
Martin Kellye4e94d22017-03-21 16:05:12 -07002745 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002746 realdotgit = os.path.join(self.worktree, '.git')
2747 tmpdotgit = realdotgit + '.tmp'
2748 init_dotgit = not os.path.exists(realdotgit)
2749 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002750 if self.use_git_worktrees:
2751 self._InitGitWorktree()
2752 self._CopyAndLinkFiles()
2753 return
2754
Mike Frysingerf4545122019-11-11 04:34:16 -05002755 dotgit = tmpdotgit
2756 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2757 os.makedirs(tmpdotgit)
2758 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2759 copy_all=False)
2760 else:
2761 dotgit = realdotgit
2762
Kevin Degib1a07b82015-07-27 13:33:43 -06002763 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002764 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2765 except GitError as e:
2766 if force_sync and not init_dotgit:
2767 try:
2768 platform_utils.rmtree(dotgit)
2769 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002770 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002771 raise e
2772 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002773
Mike Frysingerf4545122019-11-11 04:34:16 -05002774 if init_dotgit:
2775 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002776
Mike Frysingerf4545122019-11-11 04:34:16 -05002777 # Now that the .git dir is fully set up, move it to its final home.
2778 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002779
Mike Frysingerf4545122019-11-11 04:34:16 -05002780 # Finish checking out the worktree.
2781 cmd = ['read-tree', '--reset', '-u']
2782 cmd.append('-v')
2783 cmd.append(HEAD)
2784 if GitCommand(self, cmd).Wait() != 0:
2785 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002786
Mike Frysingerf4545122019-11-11 04:34:16 -05002787 if submodules:
2788 self._SyncSubmodules(quiet=True)
2789 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002790
Renaud Paquay788e9622017-01-27 11:41:12 -08002791 def _get_symlink_error_message(self):
2792 if platform_utils.isWindows():
2793 return ('Unable to create symbolic link. Please re-run the command as '
2794 'Administrator, or see '
2795 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2796 'for other options.')
2797 return 'filesystem must support symlinks'
2798
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002799 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002800 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002801
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002802 def _revlist(self, *args, **kw):
2803 a = []
2804 a.extend(args)
2805 a.append('--')
2806 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002807
2808 @property
2809 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002810 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002811
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002812 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002813 """Get logs between two revisions of this project."""
2814 comp = '..'
2815 if rev1:
2816 revs = [rev1]
2817 if rev2:
2818 revs.extend([comp, rev2])
2819 cmd = ['log', ''.join(revs)]
2820 out = DiffColoring(self.config)
2821 if out.is_on and color:
2822 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002823 if pretty_format is not None:
2824 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002825 if oneline:
2826 cmd.append('--oneline')
2827
2828 try:
2829 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2830 if log.Wait() == 0:
2831 return log.stdout
2832 except GitError:
2833 # worktree may not exist if groups changed for example. In that case,
2834 # try in gitdir instead.
2835 if not os.path.exists(self.worktree):
2836 return self.bare_git.log(*cmd[1:])
2837 else:
2838 raise
2839 return None
2840
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002841 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2842 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002843 """Get the list of logs from this revision to given revisionId"""
2844 logs = {}
2845 selfId = self.GetRevisionId(self._allrefs)
2846 toId = toProject.GetRevisionId(toProject._allrefs)
2847
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002848 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2849 pretty_format=pretty_format)
2850 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2851 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002852 return logs
2853
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002854 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002855
David James8d201162013-10-11 17:03:19 -07002856 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002857 self._project = project
2858 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002859 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002860
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002861 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2862 def __getstate__(self):
2863 return (self._project, self._bare, self._gitdir)
2864
2865 def __setstate__(self, state):
2866 self._project, self._bare, self._gitdir = state
2867
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002868 def LsOthers(self):
2869 p = GitCommand(self._project,
2870 ['ls-files',
2871 '-z',
2872 '--others',
2873 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002874 bare=False,
David James8d201162013-10-11 17:03:19 -07002875 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002876 capture_stdout=True,
2877 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002878 if p.Wait() == 0:
2879 out = p.stdout
2880 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002881 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002882 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002883 return []
2884
2885 def DiffZ(self, name, *args):
2886 cmd = [name]
2887 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002888 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002889 cmd.extend(args)
2890 p = GitCommand(self._project,
2891 cmd,
David James8d201162013-10-11 17:03:19 -07002892 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002893 bare=False,
2894 capture_stdout=True,
2895 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002896 p.Wait()
2897 r = {}
2898 out = p.stdout
2899 if out:
2900 out = iter(out[:-1].split('\0'))
2901 while out:
2902 try:
2903 info = next(out)
2904 path = next(out)
2905 except StopIteration:
2906 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002907
Mike Frysinger84230002021-02-16 17:08:35 -05002908 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002909
Mike Frysinger84230002021-02-16 17:08:35 -05002910 def __init__(self, path, omode, nmode, oid, nid, state):
2911 self.path = path
2912 self.src_path = None
2913 self.old_mode = omode
2914 self.new_mode = nmode
2915 self.old_id = oid
2916 self.new_id = nid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002917
Mike Frysinger84230002021-02-16 17:08:35 -05002918 if len(state) == 1:
2919 self.status = state
2920 self.level = None
2921 else:
2922 self.status = state[:1]
2923 self.level = state[1:]
2924 while self.level.startswith('0'):
2925 self.level = self.level[1:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002926
Mike Frysinger84230002021-02-16 17:08:35 -05002927 info = info[1:].split(' ')
2928 info = _Info(path, *info)
2929 if info.status in ('R', 'C'):
2930 info.src_path = info.path
2931 info.path = next(out)
2932 r[info.path] = info
2933 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002934
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002935 def GetDotgitPath(self, subpath=None):
2936 """Return the full path to the .git dir.
2937
2938 As a convenience, append |subpath| if provided.
2939 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002940 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002941 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002942 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002943 dotgit = os.path.join(self._project.worktree, '.git')
2944 if os.path.isfile(dotgit):
2945 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
2946 with open(dotgit) as fp:
2947 setting = fp.read()
2948 assert setting.startswith('gitdir:')
2949 gitdir = setting.split(':', 1)[1].strip()
2950 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
2951
2952 return dotgit if subpath is None else os.path.join(dotgit, subpath)
2953
2954 def GetHead(self):
2955 """Return the ref that HEAD points to."""
2956 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002957 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05002958 with open(path) as fd:
2959 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04002960 except IOError as e:
2961 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002962 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302963 line = line.decode()
2964 except AttributeError:
2965 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002966 if line.startswith('ref: '):
2967 return line[5:-1]
2968 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002969
2970 def SetHead(self, ref, message=None):
2971 cmdv = []
2972 if message is not None:
2973 cmdv.extend(['-m', message])
2974 cmdv.append(HEAD)
2975 cmdv.append(ref)
2976 self.symbolic_ref(*cmdv)
2977
2978 def DetachHead(self, new, message=None):
2979 cmdv = ['--no-deref']
2980 if message is not None:
2981 cmdv.extend(['-m', message])
2982 cmdv.append(HEAD)
2983 cmdv.append(new)
2984 self.update_ref(*cmdv)
2985
2986 def UpdateRef(self, name, new, old=None,
2987 message=None,
2988 detach=False):
2989 cmdv = []
2990 if message is not None:
2991 cmdv.extend(['-m', message])
2992 if detach:
2993 cmdv.append('--no-deref')
2994 cmdv.append(name)
2995 cmdv.append(new)
2996 if old is not None:
2997 cmdv.append(old)
2998 self.update_ref(*cmdv)
2999
3000 def DeleteRef(self, name, old=None):
3001 if not old:
3002 old = self.rev_parse(name)
3003 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003004 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003005
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003006 def rev_list(self, *args, **kw):
3007 if 'format' in kw:
3008 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3009 else:
3010 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003011 cmdv.extend(args)
3012 p = GitCommand(self._project,
3013 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003014 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003015 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003016 capture_stdout=True,
3017 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003018 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003019 raise GitError('%s rev-list %s: %s' %
3020 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003021 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003022
3023 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003024 """Allow arbitrary git commands using pythonic syntax.
3025
3026 This allows you to do things like:
3027 git_obj.rev_parse('HEAD')
3028
3029 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3030 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003031 Any other positional arguments will be passed to the git command, and the
3032 following keyword arguments are supported:
3033 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003034
3035 Args:
3036 name: The name of the git command to call. Any '_' characters will
3037 be replaced with '-'.
3038
3039 Returns:
3040 A callable object that will try to call git with the named command.
3041 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003042 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003043
Dave Borowitz091f8932012-10-23 17:01:04 -07003044 def runner(*args, **kwargs):
3045 cmdv = []
3046 config = kwargs.pop('config', None)
3047 for k in kwargs:
3048 raise TypeError('%s() got an unexpected keyword argument %r'
3049 % (name, k))
3050 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303051 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003052 cmdv.append('-c')
3053 cmdv.append('%s=%s' % (k, v))
3054 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003055 cmdv.extend(args)
3056 p = GitCommand(self._project,
3057 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003058 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003059 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003060 capture_stdout=True,
3061 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003062 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003063 raise GitError('%s %s: %s' %
3064 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003065 r = p.stdout
3066 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3067 return r[:-1]
3068 return r
3069 return runner
3070
3071
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003072class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003073
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003074 def __str__(self):
3075 return 'prior sync failed; rebase still in progress'
3076
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003077
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003078class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003079
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003080 def __str__(self):
3081 return 'contains uncommitted changes'
3082
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003083
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003084class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003085
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003086 def __init__(self, project, text):
3087 self.project = project
3088 self.text = text
3089
3090 def Print(self, syncbuf):
3091 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3092 syncbuf.out.nl()
3093
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003094
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003095class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003096
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003097 def __init__(self, project, why):
3098 self.project = project
3099 self.why = why
3100
3101 def Print(self, syncbuf):
3102 syncbuf.out.fail('error: %s/: %s',
3103 self.project.relpath,
3104 str(self.why))
3105 syncbuf.out.nl()
3106
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003107
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003108class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003109
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003110 def __init__(self, project, action):
3111 self.project = project
3112 self.action = action
3113
3114 def Run(self, syncbuf):
3115 out = syncbuf.out
3116 out.project('project %s/', self.project.relpath)
3117 out.nl()
3118 try:
3119 self.action()
3120 out.nl()
3121 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003122 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003123 out.nl()
3124 return False
3125
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003126
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003127class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003128
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003129 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003130 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003131 self.project = self.printer('header', attr='bold')
3132 self.info = self.printer('info')
3133 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003134
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003135
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003136class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003137
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003138 def __init__(self, config, detach_head=False):
3139 self._messages = []
3140 self._failures = []
3141 self._later_queue1 = []
3142 self._later_queue2 = []
3143
3144 self.out = _SyncColoring(config)
3145 self.out.redirect(sys.stderr)
3146
3147 self.detach_head = detach_head
3148 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003149 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003150
3151 def info(self, project, fmt, *args):
3152 self._messages.append(_InfoMessage(project, fmt % args))
3153
3154 def fail(self, project, err=None):
3155 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003156 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003157
3158 def later1(self, project, what):
3159 self._later_queue1.append(_Later(project, what))
3160
3161 def later2(self, project, what):
3162 self._later_queue2.append(_Later(project, what))
3163
3164 def Finish(self):
3165 self._PrintMessages()
3166 self._RunLater()
3167 self._PrintMessages()
3168 return self.clean
3169
David Rileye0684ad2017-04-05 00:02:59 -07003170 def Recently(self):
3171 recent_clean = self.recent_clean
3172 self.recent_clean = True
3173 return recent_clean
3174
3175 def _MarkUnclean(self):
3176 self.clean = False
3177 self.recent_clean = False
3178
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003179 def _RunLater(self):
3180 for q in ['_later_queue1', '_later_queue2']:
3181 if not self._RunQueue(q):
3182 return
3183
3184 def _RunQueue(self, queue):
3185 for m in getattr(self, queue):
3186 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003187 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003188 return False
3189 setattr(self, queue, [])
3190 return True
3191
3192 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003193 if self._messages or self._failures:
3194 if os.isatty(2):
3195 self.out.write(progress.CSI_ERASE_LINE)
3196 self.out.write('\r')
3197
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003198 for m in self._messages:
3199 m.Print(self)
3200 for m in self._failures:
3201 m.Print(self)
3202
3203 self._messages = []
3204 self._failures = []
3205
3206
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003207class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003208
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003209 """A special project housed under .repo.
3210 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003211
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003212 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003213 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003214 manifest=manifest,
3215 name=name,
3216 gitdir=gitdir,
3217 objdir=gitdir,
3218 worktree=worktree,
3219 remote=RemoteSpec('origin'),
3220 relpath='.repo/%s' % name,
3221 revisionExpr='refs/heads/master',
3222 revisionId=None,
3223 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003224
3225 def PreSync(self):
3226 if self.Exists:
3227 cb = self.CurrentBranch
3228 if cb:
3229 base = self.GetBranch(cb).merge
3230 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003231 self.revisionExpr = base
3232 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003233
Martin Kelly224a31a2017-07-10 14:46:25 -07003234 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003235 """ Prepare MetaProject for manifest branch switch
3236 """
3237
3238 # detach and delete manifest branch, allowing a new
3239 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003240 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003241 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003242 syncbuf.Finish()
3243
3244 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003245 ['update-ref', '-d', 'refs/heads/default'],
3246 capture_stdout=True,
3247 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003248
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003249 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003250 def LastFetch(self):
3251 try:
3252 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3253 return os.path.getmtime(fh)
3254 except OSError:
3255 return 0
3256
3257 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003258 def HasChanges(self):
3259 """Has the remote received new commits not yet checked out?
3260 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003261 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003262 return False
3263
David Pursehouse8a68ff92012-09-24 12:15:13 +09003264 all_refs = self.bare_ref.all
3265 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003266 head = self.work_git.GetHead()
3267 if head.startswith(R_HEADS):
3268 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003269 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003270 except KeyError:
3271 head = None
3272
3273 if revid == head:
3274 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003275 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003276 return True
3277 return False